4

I'm trying to assign costs if there is a "reciprocal" assignment in an IntVar[]. Here's how I've accomplished it in the older ConstraintSolver...

IntVar[] assignments = solver.MakeIntVarArray(size, 0, size-1, "assignments");
var cost = Enumerable.Range(0, size)
    .Select(i => 999 * (assignments.Element(assignments[i]) == i))
    .ToArray()
    .ToSum()
    .Var();
var objective = cost.Minimize(1);

Now I'm trying to use the newer CpSolver in Google.OrTools.Sat where the .Element extension is absent (I assume for good reason). I've managed to get it to "work" using four IntVars[] but I suspect this is just a big modeling fail on my part.

foreach (var i in Enumerable.Range(0, size))
{
    model.AddElement(assignments[i], assignments, reciprocals[i]);
    model.Add(reciprocals[i] == i).OnlyEnforceIf(reciprocalBools[i]);
    model.Add(reciprocals[i] != i).OnlyEnforceIf(reciprocalBools[i].Not());
    model.Add(costs[i] == 999).OnlyEnforceIf(reciprocalBools[i]);
    model.Add(costs[i] == 0).OnlyEnforceIf(reciprocalBools[i].Not());
}
model.Minimize(costs.Sum());

Based on my testing, the above seems to be functionally correct, however as size gets larger, the SAT version of my test app performs orders of magnitude worse than the CS version. Any suggestions would be greatly appreciated.

Ed MacDonald
  • 406
  • 4
  • 7

1 Answers1

1

here is a better version

foreach (var i in Enumerable.Range(0, size))
{
    model.AddElement(assignments[i], assignments, reciprocals[i]);
    model.Add(reciprocals[i] == i).OnlyEnforceIf(reciprocalBools[i]);
    model.Add(reciprocals[i] != i).OnlyEnforceIf(reciprocalBools[i].Not());
}
model.Minimize(999 * LinearExpr.Sum(reciprocalBools));

We also have a Inverse constraint (model.AddInverseConstraint(x_array, y_array)) that enforces

x_array[i] == j <=> y_array[j] == i

Still I wonder if you need all these.

If xi = {xi_1, .., xi_n} (mapping to a Boolean variable array) reciprocalBools[i] is true iff exists j, such that (xi_j && xj_i) is true

So you just need to count the pairs (xi_j && xj_i) both true.

This is not straightforward.

given i and j, i != j

Literal implied = model.newBoolVar("");
model.addBoolOr(new Literal[] {xi_j.not(), xj_i.not(), implied});
model.addImplication(implied, xi_j);
model.addImplication(implied, xj_i);

Now, you have implied <=> xi_j && xj_i. And you can count these implied variables.

if i == j, do not create the implied variable, do not add the 3 Boolean constraint, and use xi_i directly.

Laurent Perron
  • 8,594
  • 1
  • 8
  • 22
  • 1
    I would like to take the chance to thank you for your insightful comments of yesterday, it has been a valuable learning chance for me. :) – Patrick Trentin May 24 '19 at 10:35
  • 1
    Thanks. As a valuable source of information, I recommend this slide deck https://people.eng.unimelb.edu.au/pstuckey/PPDP2013.pdf . Although there are some discrepancies with the CP-SAT implementation. The general idea is here. – Laurent Perron May 24 '19 at 12:50
  • Thanks Laurent, but I can't find the `LinearExpr.Sum()` method in 7.1.6720 nuget package. The class `Google.OrTools.LinearSolver.LinearExpr` has no static methods. – Ed MacDonald May 24 '19 at 13:00
  • Why are you using the LinearSolver ? This is a CP-SAT code. Thus `Google.OrTools.Sat.LinearExpr`. – Laurent Perron May 24 '19 at 13:03
  • Your first block is what I ended up with after your comment in @PatrickTrentin 's post to scale the costs outside. I've been trying to work with a jagged array of Boolean instead of the IntVar, but can't seem to do the things you suggest. `model.NewBoolVar` creates `IntVar` and if I try to `(xi_j && xj_i)` or `(xi_j * xj_i)` it won't compile `operator cannot be applied to operands IntVar and IntVar` – Ed MacDonald May 24 '19 at 13:12
  • I'm not using the LinearSolver. `Google.OrTools.Sat.LinearExpr` does not exist in my package so I'm trying to figure out what you are suggesting. – Ed MacDonald May 24 '19 at 13:13
  • There is a `Google.OrTools.Sat.LinearExpression` class, but it doesn't have a static `Sum()` method either. – Ed MacDonald May 24 '19 at 13:15
  • Are you using 7.1 ? This was introduces in 7.1. And in java, it is called sum(). – Laurent Perron May 24 '19 at 13:46
  • About the &&, this was pseudo code. I have added the corresponding code. – Laurent Perron May 24 '19 at 13:52
  • Yes, I'm using 7.1.6720 – Ed MacDonald May 24 '19 at 14:23
  • Thanks a lot Laurent, I was able to get the reciprocals working with the implied variables per your suggestion. Early testing shows the bool matrix performs better than the integer array, but not as good as the version using ConstraintSolver. – Ed MacDonald May 24 '19 at 16:26