Search code examples
arraysminizinc

MiniZinc: zipping pairs of non-zero elements in a list


I have a situation where I am modelling an array S that contains a set of values (a schedule) from a predefined domain 1..t, plus 0, which is a special value for "not there/not used".

I now want to post a constraint to sum a cost function, represented as a 2D array C, for the list S', holding every non-zero element of S in the same order, like so:

constraint x = sum([C[S'[d], S'[d + 1]] | d in 1..max - 1])

However, this cannot be easily done. Things I have tried:

  • Using the function form of roots to get the set of indices to S whose data is non-zero. The problem with that solution is:
    • the result is a set, and so cannot be zipped into pairs or easily cast to a list, even though I know their number from provided instance data.
    • roots seems to require that all values participate in the array, whereas I would like only to have the full domain except 0.
  • Using a list comprehension (e.g. [S[i] | i in 1..max where S[i] != 0]) to select only the elements whose values are non-zero: this also doesn't work, as the where clause on the list comprehension causes the list to be of type opt, and also having the wrong number of elements (where I presume some of them will be <>), essentially reducing the problem of filtering zeroes to the same problem again with <>:s.
  • Treating the cost function as a DFA and 0 values as self-loops: this does not (in any way I can identify) allow for counting; only validating transitions, which I don't care about.

What I would really like here is either filter or zip, which could both easily solve my problem, but I presume that there is some sort of standard solution that I'm missing. Otherwise, I would have to re-design the model.


Solution

  • It is possible to solve your problem by using a recursive function that calculates the costs by iterating over the indices of your array S. I illustrate the function calculate_cost() below in a small example:

    int: t = 10; int: N = 5;
    
    % cost array
    array[1..t,1..t] of int: C = array2d(1..t,1..t,[ i | i in 1..t, j in 1..t]);
    
    % variables
    array[1..N] of var 0..t: S;
    var 0..1000: x;
    
    % constraints
    constraint S[1] = 4; % setting some arbitrary values
    constraint S[2] = 7;
    constraint S[3] = 0;
    constraint S[4] = 6;
    
    constraint x =  calculate_cost(1,2);
    
    function var int: calculate_cost(int: index1, int:index2) =
      if index1 > N then 0 
      elseif index2 > N then 0
      else 
        let {
           var bool: value_at_index1_is_zero = S[index1] == 0;
           var bool: value_at_index2_is_zero = S[index2] == 0;
        }
        in 
          if value_at_index1_is_zero 
             then calculate_cost(index1+1, index1+2)
          elseif value_at_index2_is_zero 
             then calculate_cost(index1, index2 + 1) 
          else 
            C[S[index1],S[index2]] + calculate_cost(index2, index2+1)   
          endif
      endif;
    
    solve satisfy;
    

    This example has S = [4, 7, 0, 6, 0] and calculates costs x = C[4,7] + C[7,6] = 4 + 7 = 11.

    In the function calculate_cost(), I recursively calculate the sum by skipping indices that have a zero value in S. In the first few lines, I check if the indices are out of bounds and return 0 in that case (base case of the recursion). Then I create two local variables that are true if the value at S[index] is zero for index. Then, if case one of those cases are true, I ignore those indices, and recursively call the function again, and increase/adapt the respective index in the recursive call.

    This works, but is probably not a very nice way of solving this issue because it introduces a lot of auxiliary variables in the FlatZinc model, so it might still be better to reformulate the problem.