-1

I'm taking a course on Discrete Optimization, and we're working through constraint programming. In a topic about reification, we're working through the Stable Marriages Problem (SPM).

The model formulation is

enum Men = [George, Hugh, Will, Clive];
enum Women = [Julia, Halle, Angelina, Keira];

# mensRanking[Hugh, Julia] is Hugh's ranking of Julia
# lower the number, the higher the preference
int mensRanking[Men, Women];
int womensRanking[Women, Men];

# Decision variables below
# Array of decision variables called wives that is indexed on men stores the wife
# of man
var{Women} wives[Men]
# Array of decision variables called husbands that is indexed on women stores the
# husband of woman
var{Men} husbands[Women]

# Constraints below
solve {
  # The husband of wife of man must equal man
  forall(m in Men)
    husband[wife[m]] = m;
  # The wife of husband of woman must equal woman
  forall(w in Women)
    wife[husband[w]] = w;

  # NEED HELP with the constraints below
  forall(m in Man)
    forall(w in Women)
      # If man m prefers woman w over his wife, then
      # woman w must prefer her husband over m
      if (mensRanking[m,w] < mensRanking[m,wife[m]])
        womensRanking[w,husband[w]] < womensRanking[w, m]

      # If woman w prefers man m over her husband, then
      # man m must prefer his wife over w
      if (womensRanking[w,m] < womensRanking[w, husband[w]])
        mensRanking[m,wife[m]] < mensRanking[m, w]
}

I can't figure out how to do the ranking comparison. Here's my attempt via or-tools in Python:

def main():
  n = 4
  men = range(n)
  women = range(n)
  # mensRanking[man][woman] is man's ranking of woman.
  # lower the number, the higher the preference
  mensRanking = [random.sample(range(n),n) for _ in men]
  womensRanking = [random.sample(range(n),n) for _ in women]

  model = cp_model.CpModel()
  # For wife 'Julia', who is her husband?
  husbands = [model.NewIntVar(0, n-1, f'woman{i}') for i in women]
  # For husband 'George', who is his wife?
  wives = [model.NewIntVar(0, n-1, f'man{i}') for i in men]

  for man in men:
    # The husband of wife of man must equal man
    # I.e., husbands[wife] = man
    wife = wives[man]
    model.AddElement(wife, husbands, man)

  for woman in women:
    # The wife of husband of woman must equal woman
    # I.e., wives[husband] = woman
    husband = husbands[woman]
    model.AddElement(husband, wives, woman)

  for m in men:
    for w in women:
      womans_rank_of_husband = model.NewIntVar(0, n-1, '')
      model.AddElement(husbands[w], womensRanking[w], womans_rank_of_husband)

      mans_rank_of_wife = model.NewIntVar(0, n-1, '')
      model.AddElement(wives[m], mensRanking[m], mans_rank_of_wife)
      # If man m prefers woman w over his wife, then
      # woman w must prefer her husband over m
      # TypeError: 'BoundedLinearExpression' object is not iterable
      model.Add(womans_rank_of_husband < womensRanking[w][m]).OnlyEnforceIf(
        mensRanking[m][w] < mans_rank_of_wife
      )
      # If woman w prefers man m over her husband, then
      # man m must prefer his wife over w
      # TypeError: 'BoundedLinearExpression' object is not iterable
      model.Add(mans_rank_of_wife < mensRanking[m][w]).OnlyEnforceIf( 
        womensRanking[w][m] < womans_rank_of_husband
      )

  solver = cp_model.CpSolver()
  solution_printer = SolutionPrinter(x)
  status = solver.SearchForAllSolutions(model, solution_printer)
  print(solver.ResponseStats())
  print(status)

Basically, I need to do an inequality check while using a decision variable as an index. I'm familiar with doing an EQUALITY check via model.AddElement(index, array, target) for array[index] == target, but can't figure out how to do array[index] < target when index is a decision variable.

EDIT: I used a temp_var as suggested by @Laurent-Perron, but now I'm getting the error:

# TypeError: 'BoundedLinearExpression' object is not iterable

Any idea why? I also tried AddImplication:

      womans_rank_of_husband = model.NewIntVar(0, n-1, '')
      model.AddElement(husbands[w], womensRanking[w], womans_rank_of_husband)

      mans_rank_of_wife = model.NewIntVar(0, n-1, '')
      model.AddElement(wives[m], mensRanking[m], mans_rank_of_wife)
      # If man m prefers woman w over his wife, then
      # woman w must prefer her husband over m
      model.AddImplication(mensRanking[m][w] < mans_rank_of_wife,
          womans_rank_of_husband < womensRanking[w][m])
      # If woman w prefers man m over her husband, then
      # man m must prefer his wife over w
      model.AddImplication(womensRanking[w][m] < womans_rank_of_husband,
          mans_rank_of_wife < mensRanking[m][w])

But that gave me the error

TypeError: NotSupported: model.GetOrMakeBooleanIndex(unnamed_var_12 <= 1)

at model.AddImplication().

azizj
  • 3,428
  • 2
  • 25
  • 33

1 Answers1

1

Create an intermediate var tmp_var, the use tmp_var in the inequality.

AddElement(index, array, tmp_var)
Add(tmp_var < target)
Laurent Perron
  • 8,594
  • 1
  • 8
  • 22
  • Can you elaborate on this? What do I set this temp var to? – azizj Jan 11 '23 at 21:06
  • My inequality is `womensRanking[w][husbands[w]] < womensRanking[w][m]`, where `husbands[w]` is a decision variable. Would it be something like this: `AddElement([husbands[w], womensRanking[w], tmp_var)` and then `Add(tmp_var < womensRanking[w][m])`? – azizj Jan 11 '23 at 22:32
  • I'm getting the error `# TypeError: 'BoundedLinearExpression' object is not iterable` now. Please see edit above. – azizj Jan 12 '23 at 02:36