poly-eq-k.c 7.11 KB
/* poly-eq-k(C, X, k) == sum(C . X) = k */

// caveat: may not work correctly when there are more than one
// occurrences of some variable

static int fd_poly_eq_k_filter(fd_constraint this)
{
  int k;
  int min, max;
  int terms = this->nvariables;
  int *mins, *maxs;
  int i;
#ifdef CONSTRAINT_TEMPS
  int *base;

  assert(!fd__constraint_data_valid(this));

  if (!constraint_memory[this->index])
    constraint_memory[this->index] = malloc((2 * terms + 2) * sizeof(int));

  base = constraint_memory[this->index];

  mins = base + 2;
  maxs = mins + terms;
#else
  mins = alloca(terms * sizeof(*mins));
  maxs = alloca(terms * sizeof(*maxs));
#endif

  k = this->constants[terms];

  // sum the minima and the maxima of the terms
  min = max = 0;

  for (i = 0; i < terms; ++i)
    {
      int vl, vh;

      if (this->constants[i] > 0)
	{
	  vl = mins[i] = _fd_var_min(VAR(this, i));
	  vh = maxs[i] = _fd_var_max(VAR(this, i));
	}
      else
	{
	  vl = maxs[i] = _fd_var_max(VAR(this, i));
	  vh = mins[i] = _fd_var_min(VAR(this, i));
	}

      min += this->constants[i] * vl;
      max += this->constants[i] * vh;
    }

  if (min > k || max < k)
    return FD_NOSOLUTION;

  if (min == max)
    {
      fd__constraint_set_entailed(this);
    }
  else
    {
      // bounds domain filtering
      // XXX: number of iterations depends on the size of the domains

      int omin, omax;

      do {
	omin = min;
	omax = max;

	for (i = 0; i < terms; ++i)
	  {
	    int c = this->constants[i];
	    int imin = mins[i];
	    int imax = maxs[i];

	    if (c == 0 || imin == imax)  continue;

	    if (c > 0)
	      {
		// enforce max(sum{j!=i}(c[j] * x[j])) + c[i] * min[i] >= k

		if (max + (imin - imax) * c < k)
		  {
		    // imin = ceil((k - max) / c) + imax
		    imin = (k - max) / c + imax;

		    fd_update_domain_and_check(del_lt, imin, VAR(this, i));

		    min += (imin - mins[i]) * c;

		    if (min > k)
		      return FD_NOSOLUTION;

		    mins[i] = imin;
		  }

		// enforce min(sum{j!=i}(c[j] * x[j])) + c[i] * max[i] <= k

		if (min + (imax - imin) * c > k)
		  {
		    // imax = floor((k - min) / c) + imin
		    imax = (k - min) / c + imin;

		    fd_update_domain_and_check(del_gt, imax, VAR(this, i));

		    max -= (maxs[i] - imax) * c;	// max >= k!

		    maxs[i] = imax;
		  }
	      }
	    else
	      {
		// enforce max(sum{j!=i}(c[j] * x[j])) + c[i] * max[i] >= k

		if (max + (imax - imin) * c < k)
		  {
		    // imax = floor((k - max) / c) + imin
		    imax = (k - max) / c + imin;

		    fd_update_domain_and_check(del_gt, imax, VAR(this, i));

		    min += (imax - maxs[i]) * c;

		    if (min > k)
		      return FD_NOSOLUTION;

		    maxs[i] = imax;
		  }

		// enforce min(sum{j!=i}(c[j] * x[j])) + c[i] * min[i] <= k

		if (min + (imin - imax) * c > k)
		  {
		    // imin = ceil((k - min) / c) + imax
		    imin = (k - min) / c + imax;

		    fd_update_domain_and_check(del_lt, imin, VAR(this, i));

		    max -= (mins[i] - imin) * c;	// max >= k!

		    mins[i] = imin;
		  }
	      }
	  }
      } while (min != max && (min != omin || max != omax));

      if (min == max)
	fd__constraint_set_entailed(this);
    }

#ifdef CONSTRAINT_TEMPS
  // save values
  *base = min;
  *(base + 1) = max;

  fd__constraint_remember(this);
#endif

  return FD_OK;
}

static int fd_poly_eq_k_propagate2(fd_constraint this, fd_int culprit)
{
#ifdef CONSTRAINT_TEMPS
  int k;
  int min, max;
  int terms = this->nvariables;
  int *mins, *maxs;
  int i;
  int *base;
  int x, c;
  int nmin, nmin_x, nmax, nmax_x;

  if (!fd__constraint_data_valid(this))
    return fd_poly_eq_k_filter(this);	// ignores culprit

  // bounds filtering

  base = constraint_memory[this->index];

  mins = base + 2;
  maxs = mins + terms;

  min = *base;
  max = *(base + 1);

  k = this->constants[terms];

  // the culprit appears in one of the terms, find out which one(s)
  for (x = 0; culprit != VAR(this, x); ++x)
    ;

  nmin_x = _fd_var_min(VAR(this, x));
  nmax_x = _fd_var_max(VAR(this, x));

  if (nmin_x == mins[x] && nmax_x == maxs[x])
    return FD_OK;

  nmin = min;
  nmax = max;

  do
    {
      c = this->constants[x];

      if (c > 0)
	{
	  nmin = nmin + (nmin_x - mins[x]) * c;
	  nmax = nmax - (maxs[x] - nmax_x) * c;
	}
      else if (c < 0)
	{
	  nmin = nmin - (maxs[x] - nmax_x) * c;
	  nmax = nmax + (nmin_x - mins[x]) * c;
	}

      if (nmin > k || nmax < k)
	return FD_NOSOLUTION;

      mins[x] = nmin_x;
      maxs[x] = nmax_x;

      while (++x < terms && culprit != VAR(this, x))
	;
    }
  while (x < terms);

  if (nmin == min && nmax == max)
    return FD_OK;

  if (nmin == nmax)
    {
      fd__constraint_set_entailed(this);
    }
  else
    {
      // bounds domain propagation
      // XXX: number of iterations depends on the size of the domains

      int onmin, onmax;

      do {
	onmin = nmin;
	onmax = nmax;

	for (i = 0; i < terms; ++i)
	  {
	    int c = this->constants[i];
	    int imin = mins[i];
	    int imax = maxs[i];

	    if (c == 0 || imin == imax)  continue;

	    if (c > 0)
	      {
		// enforce max(sum{j!=i}(c[j] * x[j])) + c[i] * min[i] >= k

		if (nmax + (imin - imax) * c < k)
		  {
		    // imin = ceil((k - nmax) / c) + imax
		    imin = (k - nmax) / c + imax;

		    fd_update_domain_and_check(del_lt, imin, VAR(this, i));

		    nmin += (imin - mins[i]) * c;

		    if (nmin > k)
		      return FD_NOSOLUTION;

		    mins[i] = imin;
		  }

		// enforce min(sum{j!=i}(c[j] * x[j])) + c[i] * max[i] <= k

		if (nmin + (imax - imin) * c > k)
		  {
		    // imax = floor((k - nmin) / c) + imin
		    imax = (k - nmin) / c + imin;

		    fd_update_domain_and_check(del_gt, imax, VAR(this, i));

		    nmax -= (maxs[i] - imax) * c;	// nmax >= k!

		    maxs[i] = imax;
		  }
	      }
	    else
	      {
		// enforce max(sum{j!=i}(c[j] * x[j])) + c[i] * max[i] >= k

		if (nmax + (imax - imin) * c < k)
		  {
		    // imax = floor((k - nmax) / c) + imin
		    imax = (k - nmax) / c + imin;

		    fd_update_domain_and_check(del_gt, imax, VAR(this, i));

		    nmin += (imax - maxs[i]) * c;

		    if (nmin > k)
		      return FD_NOSOLUTION;

		    maxs[i] = imax;
		  }

		// enforce min(sum{j!=i}(c[j] * x[j])) + c[i] * min[i] <= k

		if (nmin + (imin - imax) * c > k)
		  {
		    // imin = ceil((k - nmin) / c) + imax
		    imin = (k - nmin) / c + imax;

		    fd_update_domain_and_check(del_lt, imin, VAR(this, i));

		    nmax -= (mins[i] - imin) * c;	// nmax >= k!

		    mins[i] = imin;
		  }
	      }
	  }
      } while (nmin != nmax && (nmin != onmin || nmax != onmax));

      if (nmin == nmax)
	fd__constraint_set_entailed(this);
    }

  *base = nmin;
  *(base + 1) = nmax;

  return FD_OK;
#else /* CONSTRAINT_TEMPS */
  return fd_poly_eq_k_filter(this);	// ignores culprit
#endif /* CONSTRAINT_TEMPS */
}

fd_constraint fd_poly_eq_k(int cs[], fd_int xs[], int nterms, int k)
{
  fd_constraint c = fd__constraint_new(nterms, nterms + 1);
  int i;

  if (c)
    {
      for (i = 0; i < nterms; ++i)
	c->variables[i] = FD_INT2C_VAR(xs[i]);
      for (i = 0; i < nterms; ++i)
	c->constants[i] = cs[i];
      c->constants[nterms] = k;

      c->kind = FD_CONSTR_POLY_EQ_K;

      for (i = 0; i < c->nvariables; ++i)
	_fd_var_add_constraint(VAR(c, i), c);

      _fd_add_constraint(c);
    }

  return c;
}