Proposed New AMPL Features Draft of August 19, 1996

Constraint Logic Programming Extensions

For combinatorial or discrete optimization, AMPL currently provides the option of including integer-valued variables in algebraic objectives and constraints. This is sufficient to make good use of integer programming solvers that use a classical branch-and-bound procedure.

Optimization systems for constraint logic programming also use a branching or tree search, but are able to work with a much broader variety of combinatorial expressions. In exchange for this advantage, logic programming solvers typically forego the ability to generate the bounds and cuts that are used to great advantage to limit the search in integer programming. The logic programming approach thus tends to be advantageous for problems that are "highly" combinatorial, in the sense that their formulation in terms of integer variables is artificial and hard to work with, or requires a very large number of variables or constraints, or fails to yield strong bounds.

AMPL could be extended in a variety of ways to take better advantage of the strengths of constraint logic programming solvers. Several kinds of constraints useful in logic programming are naturally expressed by allowing variables in certain operands or arguments where only constant expressions are currently permitted. The features of constraint logic programming systems also suggest some operators and functions that should be added to the AMPL language. Possibilities include:

Other extensions would permit a broader variety of expressions for defining a variable's domain: Each of these items corresponds to a section below, in which we motivate a new group of features and propose corresponding AMPL language extensions. A final paragraph or two in each section indicates how the C++ classes and member functions provided by ILOG Solver could be used to solve problems that have been expressed using the proposed features. (There is a further possibility, which we do not pursue here, of defining new classes and member functions to more efficiently handle certain AMPL features.)


Logical operators

Current AMPL constraints consist of numerical-valued expressions connected by < =, > = or . A natural extension would be to allow these constraint expressions to be connected by AMPL's unary and binary logical operators,
constraint-expr1 or constraint-expr2

Satisfied iff at least one of the operands is satisfied.

constraint-expr1 and constraint-expr2

Satisfied iff both of the operands are satisfied.

not constraint-expr

Satisfied iff the operand is not satisfied.

and AMPL's iterated forms of the binary logical operators:
exists {indexing} constraint-expr

Satisfied iff the operand is satisfied for at least one member of the indexing set (the iterated form of or).

forall {indexing} constraint-expr

Satisfied iff the operand is satisfied for all members of the indexing set (the iterated form of and).

If we allow constraint-expr to be any valid expression for a constraint, and permit grouping of constraint expressions by parentheses,
( constraint-expr )

Satisfied iff the constraint-expr is satisfied.

then an AMPL constraint can be any logical combination of equalities and inequalities.

The simplest useful application of this extension would be to specify disjunctive constraints. For example, in multmip3.mod from the AMPL book, we require for every origin i and destination j that total shipments sum {p in PROD} Trans[i,j,p] lie either at zero or between minload and limit[i,j]. This is accomplished in an integer programming framework by introducing auxiliary binary variables Use[i,j],

   var Trans {ORIG,DEST,PROD} > = 0;
   var Use {ORIG,DEST} binary;
and forming the following constraints:
   subject to Multi {i in ORIG, j in DEST}:
      sum {p in PROD} Trans[i,j,p] < = limit[i,j] * Use[i,j];

   subject to Min_Ship {i in ORIG, j in DEST}:
      sum {p in PROD} Trans[i,j,p] > = minload * Use[i,j];
The same restrictions could be stated using the or operator in this way:
   subject to Multi_Min_Ship {i in ORIG, j in DEST}:
      sum {p in PROD} Trans[i,j,p] = 0 or
      minload < = sum {p in PROD} Trans[i,j,p] < = limit[i,j];
Fewer variables and constraints are required, and the statement of the constraint is much closer to its original formulation.

In another example, variables Assign[i1,i2,j] have been defined to represent the number of people of type (i1,i2) assigned to location j. The following declarations define an additional restriction in integer programming terms:

   param upperbnd {(i1,i2) in ISO, j in REST} :=
      min (ceil((number2[i1,i2]/card {PEOPLE}) * hiDine[j]) + give[i1,i2],
           hiTargetTitle[i1,j] + giveTitle[i1],
           hiTargetLoc[i2,j] + giveLoc[i2],
           number2[i1,i2]);

   var Lone {(i1,i2) in ISO, j in REST} binary;

   subj to Isolation1 {(i1,i2) in ISO, j in REST}:
      Assign2[i1,i2,j] < = upperbnd[i1,i2,j] * Lone[i1,i2,j];

   subj to Isolation2a {(i1,i2) in ISO, j in REST}:
      Assign2[i1,i2,j] +
         sum {ii1 in ADJACENT[i1]: (ii1,i2) in TYPE2} Assign2[ii1,i2,j]
            > = 2 * Lone[i1,i2,j];

   subj to Isolation2b {(i1,i2) in ISO, j in REST}:
      Assign2[i1,i2,j] > = Lone[i1,i2,j];
Using the or operator, the same thing can be said much more directly and concisely:
   subj to Isolation {(i1,i2) in ISO, j in REST}:
      Assign2[i1,i2,j] = 0 or
      Assign2[i1,i2,j] +
         sum {ii1 in ADJACENT[i1]: 
                       (ii1,i2) in TYPE2} Assign2[ii1,i2,j] > = 2;
As a further complication under the integer programming approach, there are many other ways that this either-or constraint can be transformed, through addition of binary variables, to inequalities suitable for integer programming. Other transformations differ in the computation of the upper bound parameter, in the details of the constraint expressions, and in the choice of "cuts" such as Isolation2b to tighten the formulation. The transformation exhibited above is not obviously superior to others, though it did prove to be superior -- for the data of interest -- in a series of empirical tests.

Although the use of or could be limited to simple cases like this, it would be desirable to allow the full generality of logical operations on constraint equalities and inequalities. There are thorny issues to be dealt with in the general case, however:

Solver drivers would have to detect and deal with these situations by analyzing the expression tree that they receive from AMPL. There are ways that AMPL could help,however, such as by identifying common subexpressions and by flagging certain common special cases.

ILOG Solver provides analogues for AMPL's and (&&), or (||), and not (!) by overloading existing C++ operators. It also defines operators for exclusive or (!=), equivalence (), and implication (< =). The effect of AMPL's iterated logical operators can be achieved by using its IlcCard function to count the number of instances of a constraint-expr that are true; see also the discussion of counting operators below.


Conditional operators

AMPL already provides an if-then-else operator that returns a value that can be used in expressions:

if logical-expr then object-expr1
if logical-expr then object-expr1 else object-expr2

Takes the value of object-expr1 when the logical-expr is true, and the value of object-expr2 (or 0 if omitted) when the logical-expr is false. Each object-expr may be any expression that evaluates to a legal set member -- that is, either a number or a string.

if logical-expr then set-expr1 else set-expr2

Takes the value of set-expr1 when the logical-expr is true, and the value of set-expr2 when the logical-expr is false.

When this operator appears in a constraint, the logical-expr can contain variables, in which case AMPL handles the constraint like other nonlinear constraints, passing an expression tree to the solver. In particular, the logical-expr may be any valid constraint-expr.

AMPL also has a similar if-then form of indexing expression, which is used in the context of constraints as follows:

subject to {if const-logical-expr}: constraint-expr;

Enforces the constraint specified by the constraint-expr if and only if the logical-expr is true.

Thus for example in section 8.4 of the book we have:
   subject to Time {if avail >  0}:
      sum {p in PROD} (1/rate[p]) * Make[p] < = avail;
It is arguably more natural, howver, to make the if condition part of the constraint expression:
   subject to Time:
      if avail >  0 then
         sum {p in PROD} (1/rate[p]) * Make[p] < = avail;
By allowing variables in the logical-expr following if, we arrive at another appealing form of constraint for logic programming:
if logical-expr then constraint-expr1

Satisfied if the logical-expr is true and constraint-expr1 is satisfied, or if the logical-expr is false.

if logical-expr then constraint-expr1 else constraint-expr2

Satisfied if the logical-expr is true and constraint-expr1 is satisfied, or if the logical-expr is false and constraint-expr2 is satisfied.

By allowing variables following if, these forms would considerably expand the variety of conditional constraints that AMPL could conveniently express. For example, the previously defined Multi_Min_Ship constraint could be written:
   subject to Multi_Min_Ship {i in ORIG, j in DEST}:
      if sum {p in PROD} Trans[i,j,p] >  0 then
         minload < = sum {p in PROD} Trans[i,j,p] < = limit[i,j];
Again, the logical-expr could be any constraint-expr.

ILOG Solver can directly define if-then constraints like Time and Multi_Min_Ship above by use of either a constraint-posting function (IlcIfThen) or an implication operator (< =). There is no direct support for if-then-else constraints, however; they have to be built from two calls to IlcIfThen or an equivalent constraint expression using several logical operators.

AMPL's if-then and if-then-else operators, described at the beginning of this section, have a direct analogue as a C++ operator, but C++ does not allow the overloading of that operator that would be necessary to permit its use with Solver's constraint data types.


Counting operators

AMPL's card operator returns the number of members in a set. When it is applied to a set defined in terms of variables, it can count the number of constraints of a given form that are satisfied. For instance, in the multmip3.mod example previously cited, there is the following constraint on the number of destinations served by any origin:
   subject to Max_Serve {i in ORIG}:
      sum {j in DEST} Use[i,j] < = maxserve;
Using card, the same thing could be expressed in terms of the natural Trans[i,j,p] variables, without recourse to the auxiliary zero-one Use[i,j] variables:
   subject to Max_Serve {i in ORIG}:
      card {j in DEST: sum {p in PROD} Trans[i,j,p] >  0} < = maxserve;
This form might be too general, however. The operand of card could be any set expression. To implement the resulting constraint, the solver would have to receive enough information to enable it to evaluate that set expression given any values of the variables. We could circumvent this difficulty by restricting the ways in which variables may appear in the argument to card, but we prefer to avoid complicating the language design with such restrictions. Instead, we could define a new operator that explicitly counts the number of times that a certain constraint is satisfied:

count {indexing} constraint-expr

The number of members of the indexing set such that the constraint-expr is satisfied.

The above constraint would then be written:
   subject to Max_Serve {i in ORIG}:
      count {j in DEST} (sum {p in PROD} Trans[i,j,p] >  0) < = maxserve;
The constraint-expr could be any valid AMPL constraint. The AMPL translator would instantiate it for each member of the indexing set, and would communicate all of the instantiated constraints to the solver using the conventions already in place.

Additional iterated logical operators might be defined to simplify the descriptions of constraints in some common special cases:

atmost1 {indexing} constraint-expr

Satisfied iff the constraint-expr holds for at most one member of the indexing set.

atleast1 {indexing} constraint-expr

Satisfied iff the constraint-expr holds for at least one member of the indexing set.

exactly1 {indexing} constraint-expr

Satisfied iff the constraint-expr holds for exactly one member of the indexing set.

The atleast1 operator is a synonym for exists, but the other two are not currently available in AMPL. A further generalization would replace the 1 in these operators by a parenthesized number:

atmost(k) {indexing} constraint-expr
atleast(k) {indexing} constraint-expr
exactly(k) {indexing} constraint-expr

Satisfied iff the constraint-expr holds for at most (at least, exactly) k members of the indexing set.

We could restrict k to a positive integer constant, but in principle it could be any constant arithmetic expression that evaluates to a positive (or perhaps nonnegative) integer value.

Another particularly important special case occurs when counting the number of set members at which a given expression takes a particular value. As a simple example, consider the scheduling problem that assigns a number of jobs to a smaller number of machines, so that at most cap[k] jobs are assigned to machine k. The conventional formulation defines a binary (zero-one) variable Assign[j,k] for each job-machine pair, such that Assign[j,k] will be 1 if and only if job j is assigned to machine k (sched1.mod):

   param n integer >  0;

   set JOBS := 1..n;
   set MACHINES := 1..n;

   param cap {MACHINES} integer > = 0;

   var Assign {JOBS,MACHINES} binary;
The requirements of the assignment can then be specified by one algebraic constraint for each job and for each machine:

   subj to OneMachinePerJob {j in JOBS}:
      sum {k in MACHINES} Assign[j,k] = 1;

   subj to CapacityOfMachine {k in MACHINES}:
      sum {j in JOBS} Assign[j,k] < = cap[k];
As an alternative, we could associate with each job only one variable, whose value is taken from the set of machines:
   var MachineForJob {JOBS} integer > = 1, < = n;
For each j in JOBS, the value of MachineForJob[j] would be the number of the machine that is assigned to do job j. This approach requires fewer variables by an order of magnitude, and automatically enforces the requirement that one machine be assigned to each job. To specify that at most cap[k] jobs are assigned to machine k, we could use the proposed count operator (sched2.mod):
   subj to CapacityOfMachine {k in MACHINES}:
      count {j in JOBS} (MachineForJob[j] = k) < = cap[k];
This is not as readable a statement of the constraint as one might like, however, and it is likely to be inefficient. Because the count operator can be applied to any AMPL constraint-expr, its implementation in the AMPL translator would scan through the entire set JOBS for each constraint, testing MachineForJob[j] k for every combination of job j and machine k -- even though only one pass through the jobs is necessary to accumulate the counts for all machines. These circustances suggest that AMPL should instead offer a more specialized iterated operator for counting individual values assumed by an indexed expression (sched3.mod):

   subj to CapacityOfMachine {k in MACHINES}:
      countof(k) {j in JOBS} MachineForJob[j] < = cap[k];
The general form would be:
countof(k) {indexing} object-expr

The number of members of the indexing set such that the object-expr is equal to k.

Although it would still be possible to implement this operator inefficiently, the presence of countof could alert the AMPL translator to the possibility of a more efficient evaluation.

ILOG Solver's analogue to AMPL's cardinality operators is the IlcCard function. IlcCard acts like card when called with one argument representing a set of integers (type IlcIntSetVar) or objects (type IlcAnySetVar). It acts like count when called with an index (type IlcIndex) and a constraint using that index (type IlcConstraint). The special case of the countof(k) operator is implemented efficiently through Solver's IlcDistribute function.

In the case where IlcCard is called with two arguments, an index is implicitly defined to run over the elements of one or more arrays of integers (type IlcIntVarArray) or objects (type IlcAnyVarArray). To get the effect of AMPL's count applied to an arbitrary indexed collection of constraints, Solver must introduce auxiliary binary variables via its metaconstraint feature. The association between constraints and these binary variables is maintained automatically, however, in contrast to the integer programming approach where it is necessary to form additional constraints (such as Multi and Min_Ship above) to enforce the definition of the binary variables.


Pairwise operators

Various assignment and related combinatorial problems require that a collection of entities be pairwise different or disjoint. New iterated operators for these conditions would make them easier to state and would help to make the resulting problems easier to solve.

An example is given by the assignment problem that resembles the one defined above, but with equal numbers of jobs and machines. Each job is assigned to one machine, as before, but also each machine gets one job. In describing the constraints on the machines, we can dispense with the parameters cap[k] (which would all be 1) and can instead simply say that no two variables MachineForJob[j1] and MachineForJob[j2] have the same value. Such a restriction can be stated in AMPL as follows:

   subj to OneJobPerMachine {j1 in JOBS, j2 in JOBS: j1 <  j2}:
      MachineForJob[j1] != MachineForJob[j2];
This is a cumbersome way to express the simple idea of being pairwise different, however, and it gives rise to an order of magnitude more constraints than the conventional formulation (assign1.mod). We would prefer to regard pairwise inequality as a property of the entire collection of MachineForJob variables, rather than as a collection of binary relations between individual variables.

In AMPL, properties of indexed collections are defined by means of iterated operators such as sum and exists. Thus it would make sense to introduce an analogous operator for pairwise difference in an indexed collection of variables (assign2.mod):

   subj to OneJobPerMachine: alldiff {j in JOBS} MachineForJob[j];
In general, this operator could be applied to any collection of expressions:
alldiff {indexing} object-expr

Satisfied iff the object-expr takes different values for all pairs of different members of the indexing set.

Using alldiff makes constraints easier to read, and also conveys more useful information to a solver than a large collection of individual inequalities.

A similar operator could be introduced for indexed collections of set expressions, to express the property of being pairwise disjoint:

alldisjoint {indexing} set-expr

Satisfied iff the set-expr evaluates to nonintersecting sets for all pairs of different members of the indexing set.

To express the same thing in current AMPL, it is necessary to state for each pair of sets that their intersection has cardinality zero.

ILOG Solver provides functions that correspond closely to these iterated pairwise operators. Function IlcAllDiff specifies that the components of an array of integers (type IlcIntVarArray) or an array of objects (type IlcAnyVarArray) must be pairwise different. Function IlcAllNullIntersect specifies that the components of an array of integer sets (type IlcIntSetVarArray) or an array of object sets (type IlcAnySetVarArray) must be pairwise disjoint. Solver's search procedure handles these relationships directly in an efficient way.


Variables in subscripts

When we use zero-one variables for the assignment model, it is easy to express the objective as a conventional linear programming expression:
   minimize TotalCost:
      sum {j in JOBS, k in MACHINES} cost[j,k] * Assign[j,k];
To use the MachineForJob variables, however, we are currently forced to resort to a more awkward formulation:
   minimize TotalCost:
      sum {j in JOBS, k in MACHINES} 
         if MachineForJob[j] = k then cost[j,k];
If we could simply write the cost of assigning machine j to job MachineForJob[j] as cost[j,MachineForJob[j]], then the objective could be expressed much more naturally (assign3.mod):
   minimize TotalCost:
      sum {j in JOBS, k in MACHINES} cost[j,MachineForJob[j]];
This would require an extension to AMPL, since currently variables are not allowed to appear in subscripts in the objective of a model.

There is also good reason to want to use variables within subscripts in the constraints, as may be seen by considering the related problem of sequencing. In a representative sequencing problem, we may want to assign a number of jobs to an equal number of "slots" so that each job is assigned to a different slot. The key difference between these slots and the machines of the assignment example is that the slots have a significant ordering. Setup costs and times between jobs are a function of this slot ordering, in ways that can be hard to express using current AMPL features.

To illustrate the difficulties and a prospective remedy, we define data for a simple sequencing problem as follows:

   param n integer >  0;

   set JOBS := 1..n;
   set SLOTS := 1..n;

   param procTime {JOBS} >  0;
   param dueTime {JOBS} >  0;
   param duePenalty {JOBS} >  0;

   set JOBPAIRS := {j1 in JOBS, j2 in JOBS: j1 != j2};
   set PREC within JOBPAIRS;

   param setupCost {JOBPAIRS} > = 0;
   param setupTime {JOBPAIRS} > = 0;
For each job j, procTime[j] is the processing time required for the job, dueTime[j] is the deadline for completion of the job, and duePenalty[j] is a cost per unit of time that the job is finished early. For each pair of jobs j1 and j2, (j1,j2) is in the set PREC if and only if j1 must be finished before j2; setupCost[j1,j2] and setupTime[j1,j2] are the cost and time required to set up for job j2, if it is assigned to the slot immediately following job j1's slot.

Adopting the approach introduced in the assignment example, we could define the variables as follows:

   var SlotForJob {JOBS} integer > = 1, < = n;
   var JobForSlot {SLOTS} integer > = 1, < = n;

   var FinishTime {{0} union JOBS};
SlotForJob[j] is the slot assigned to job j, and conversely JobForSlot[k] is the job assigned to slot k. FinishTime[j] is the time that job j is finished, with FinishTime[0] being the same as the start time. The number of variables remains proportional to the number of jobs, but we must figure out how to express the objective and constraints in terms of these variables.

What is the setup cost associated with the kth slot? It is the cost of going from JobForSlot[k-1] to JobForSlot[k], or

   setupCost[JobForSlot[k-1],JobForSlot[k]]
This is not currently a valid AMPL expression, because it uses variables as subscripts to a parameter. It is certainly a very natural expression for this kind of problem, however, when the variables are defined in the efficient way we have given. The setup times are represented by an analogous expression. The completion time of the job in the kth slot is given by the expression
   FinishTime[JobForSlot[k]]
which illustrates that it is equally natural to subscript a variable with another variable. Using these expressions, we can write the lower limit for the completion time of the job in the kth slot as the following constraint:
   FinishTime[JobForSlot[k-1]] 
      + setupTime[JobForSlot[k-1],JobForSlot[k]]
      + procTime[JobForSlot[k]] 
           < = FinishTime[JobForSlot[k]]
The SlotForJob variables are needed for the precedence constraints; for each pair of jobs (j1,j2) in set PREC,
   SlotForJob[j1] <  SlotForJob[j2]
Finally, to ensure that the JobForSlot and SlotForJob variables describe the same valid sequencing, we need one more instance of a variable subscripted by a variable; for each job j,
   JobForSlot[SlotForJob[j]] = j
This constraint also indirectly ensures that each job is assigned a different slot, and each slot is assigned a different job. In the complete model (seq1.mod) the number of constraints is only on the order of the number of jobs (unless there is a huge number of precedence relations).

In general, any subscript in a constraint might be an expression that contains variables. Unlike current AMPL subscripts, these could not simply be instantiated when AMPL generates constraints. Rather, for each such subscript, AMPL would have to provide the solver with instructions for evaluating the expression, in the form of an expression tree or (for linear expressions) a list of coefficients.

ILOG Solver's arrays of integers (type IlcIntArray) and integer variables (type IlcIntVarArray) have a subscripting operator that admits any integer expression (type IlcIntExp) as its argument. Since an integer expression may in general contain variables, subscripts containing variables could be implemented straightforwardly. This observation also extends to Solver's analogous arrays of floating-point numbers (types IlcFloatArray, IlcFloatVarArray) and of objects (types IlcAnyArray, IlcAnyVarArray).


Object-valued variables

Currently a var declaration can use only the > = and < = operators to restrict the domain of a variable. If the in operator were also allowed in this context, then variables could be restricted to taking values from a given set:

var indexingopt var-name in const-set-expr ;

Restricts the defined variable(s) to values that lie within the set specified by the const-set-expr.

In the context of logic programming, the interesting case is where the const-set-expr specifies a set of objects denoted by character strings (or arbitrary numbers). The case where this expression is a union of significant numerical values and/or intervals is naturally handled in the more traditional context of numerical-valued variables and integer programming, as will be explained elsewhere . . .

As an example, in the preceding assignment model we artificially defined JOBS and MACHINES to be sets of the first n integers. If object-valued variables were allowed, however, these could be modeled as sets of objects to be read from the problem data, with a check that each contained the same number of objects:

   set JOBS;
   set MACHINES;
      check card (JOBS) = card (MACHINES);
For each j in JOBS, the variable MachineForJob[j] would then have as its value the member of set MACHINES that is assigned to do job j. Hence it would be declared as an object-valued variable:
   var MachineForJob {JOBS} in MACHINES; 
The remainder of the formulation (assign4.mod) would be the same as before. A similarly minor change to our sequencing model (seq2.mod) would permit it also to use a set of objects for the jobs.

ILOG Solver provides constrained enumerated variables (types IlcAnyVar and IlcAnyVarArray) whose values may be arbitrary objects. Solver's integer-valued variables can also be used in contexts where each integer stands for an object (rather than for a numerical value that participates in arithmetic expressions). The IlcAllDiff operator required for our formulation is applicable to both object and integer arrays.


Set-valued variables

Many kinds of combinatorial problems are more naturally described in terms of choosing an optimal subset, than in terms of any numerical decision variables. Thus a second extension to the var declaration's domain specification would give rise to variables that take subsets as values.

As an example, consider first a simple knapsack problem that concerns a set of objects having given values and weights, and an overall capacity:

   set OBJECTS;

   param value {OBJECTS} >  0;
   param weight {OBJECTS} >  0;

   param capacity >  0;
The problem can be stated concisely and naturally as follows: find a subset of some given set of objects, such that the total value of the subset is maximized, subject to the total weight of the subset being less than the capacity. This statement can be converted to a conventional algebraic formulation, by defining binary variable corresponding to each object:
   var In {OBJECTS} binary;

   maximize Total_Value: 
      sum {i in OBJECTS} value[i] * In[i];

   subject to Weight_Limit:
      sum {i in OBJECTS} weight[i] * In[i] < = capacity;
The concise and natural original description could be expressed directly in AMPL, however, by use of a set-valued variable:
   var Knapsack within OBJECTS;

   maximize Total_Value: 
      sum {i in Knapsack} value[i];

   subject to Weight_Limit:
      sum {i in Knapsack} weight[i] < = capacity;
The alternative objective and constraint superficially resemble the standard algebraic constraints, but they sum over the members of the set-valued variable Knapsack rather than over the given set OBJECTS.

The budgeted traveling salesman problem offers a somewhat different example. The problem is stated in terms of a set of cities, one designated the home city; a set of city pairs on which travel is possible, and travel costs between these pairs; and an overall budget:

   set CITIES; 
   param Home symbolic in CITIES; 

   set LINKS within {i in CITIES, j in CITIES: i < >  j}; 
   param cost {LINKS}; 

   param budget >  0;
In one version of the problem, the goal is to plan a tour from the home city visiting as many cities as possible, using only available city-pair links, and subject to the total travel cost being within the budget. For this case we need a variable whose values are (circularly) ordered subsets of cities:
   var Tour circular within CITIES; 

   maximize Cities_Visited: card {Tour}; 

   subject to Budget_Limit: 
      sum {c in Tour} cost[c,next(c)] < = budget; 

   subject to Leave_Home: first(Tour) = Home; 

   subject to Link_Exists {c in Tour}: (c,next(c)) in LINKS;
Unlike the knapsack problem, this one has no especially obvious equivalent as an integer program. The equivalent IPs that do exist, moreover, are generally too hard for general-purpose integer programming codes except in very small instances. Most combinatorial optimization problems of practical complexity share these characteristics.

The general forms for declarations of set-valued variables would be as follows:

var indexingopt var-name within const-set-expr ;

Defines set-valued variables whose domain consists of all unordered subsets of the set specified by the const-set-expr.

var indexingopt var-name ordered within const-set-expr ;

The same as above, except the const-set-expr must describe an ordered set, and induces an ordering on all of the subsets in the variable's domain. Ordered set functions such as first and next may thus be applied to the variable and its members.

var indexingopt var-name circular within const-set-expr ;

The same as above, except the orderings are circular.

The use of within in the domain specification is sufficient to distinguish declarations of set-valued variables. The distinction could be emphasized, however, by starting the declaration with a new keyword (such as var_set) or an extra keyword (as in var set).

ILOG Solver includes data types for sets of objects (IlcAnySet) and sets of integers (IlcIntSet). Set-valued variables are provided by corresponding constrained set variables of objects (types IlcAnySetVar and IlcAnySetVarArray) and integers (types IlcIntSetVar and IlcIntSetVarArray). Functions are provided for the standard set operations, including membership, containment, union, intersection, and cardinality.



Comments or questions?
Write to info@ampl.com or use our comment form.

Return to the AMPL proposed new features page.

Return to the AMPL update page.

Return to the AMPL home page.


LAST MODIFIED 19 AUGUST 1996 BY 4er.