Skip to content

Commit 3e02be5

Browse files
committed
cvt:cmp:eps default 0 #102
Normalize conditional comparisons for better unification, makes positive tolerance unnecessary in current tests
1 parent a9bd1eb commit 3e02be5

File tree

14 files changed

+130
-20
lines changed

14 files changed

+130
-20
lines changed

CHANGES.mp.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
Summary of recent updates to the AMPL MP Library
22
================================================
33

4+
## unreleased
5+
- Changed default tolerance for strict comparisons
6+
to 0 (option cvt:cmp:eps, #102.)
7+
- Fixed a bug where equivalent conditional
8+
comparisons were not unified.
9+
10+
411
## 20230728
5-
- Option 'tech:writesolution' #218
6-
- Option 'writeprob' ('tech:writemodel') ASL-compatible
7-
- Hint when 'writeprob' fails: use 'writesol'
12+
- Option 'tech:writesolution' #218.
13+
- Option 'writeprob' ('tech:writemodel') ASL-compatible.
14+
- Hint when 'writeprob' fails: use 'writesol'.
815

916

1017
## 20230726

include/mp/flat/constr_algebraic.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ class AlgebraicConstraint :
7070
/// Sorting and merging terms, some solvers require
7171
void sort_terms() { Body::sort_terms(); }
7272

73-
/// Negate
73+
/// Is Normalized?
74+
bool is_normalized() {
75+
sort_terms();
76+
return GetBody().is_normalized();
77+
}
78+
79+
/// Negate all terms
7480
void negate() { Body::negate(); RhsOrRange::negate(); }
7581

7682
/// Testing API

include/mp/flat/constr_base.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ class BasicConstraint {
3535
/// For functional constraints, result variable index
3636
int GetResultVar() const { return -1; }
3737

38-
3938
private:
4039
std::string name_;
4140
};
@@ -210,7 +209,8 @@ class ConditionalConstraint :
210209

211210
/// Base class
212211
using Base = CustomFunctionalConstraint<
213-
Con, ParamArray0, LogicalFunctionalConstraintTraits, CondConId<Con> >;
212+
Con, ParamArray0,
213+
LogicalFunctionalConstraintTraits, CondConId<Con> >;
214214

215215
/// Default constructor
216216
ConditionalConstraint() = default;

include/mp/flat/constr_prepro.h

+32-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
#define CONSTR_PREPRO_H
33

44
/**
5-
* Preprocess flat constraints before adding
5+
* Preprocess flat constraints before adding.
6+
*
7+
* Possible tasks:
8+
* 1. Simplify constraints
9+
* 2. Replace a functional constraint by a different one,
10+
* via returning its result variable from another
11+
* (see conditional inequalities).
612
*/
713

814
#include <cmath>
@@ -126,6 +132,8 @@ class ConstraintPreprocessors {
126132
CondLinConEQ& c, PreprocessInfo& prepro) {
127133
prepro.narrow_result_bounds(0.0, 1.0);
128134
prepro.set_result_type( var::INTEGER );
135+
if (!IsNormalized(c))
136+
c.GetConstraint().negate(); // for equality
129137
if (0!=MPD( IfPreproEqResBounds() ))
130138
if (FixEqualityResult(c, prepro))
131139
return;
@@ -135,6 +143,16 @@ class ConstraintPreprocessors {
135143
return;
136144
}
137145

146+
/// See if the argument of a conditional
147+
/// algebraic constraint is normalized
148+
template <class Body, int kind>
149+
bool IsNormalized(
150+
ConditionalConstraint<
151+
AlgebraicConstraint< Body, AlgConRhs<kind> > >& cc) {
152+
auto& arg = cc.GetConstraint();
153+
return arg.is_normalized();
154+
}
155+
138156
/// Preprocess CondQuadConEQ
139157
template <class PreprocessInfo>
140158
void PreprocessConstraint(
@@ -218,9 +236,21 @@ class ConstraintPreprocessors {
218236
PreprocessInfo& prepro) {
219237
prepro.narrow_result_bounds(0.0, 1.0);
220238
prepro.set_result_type( var::INTEGER );
221-
// See if we need to round the constant term
222239
assert(kind);
223240
auto& algc = cc.GetArguments();
241+
if (!IsNormalized(cc)) {
242+
auto arg1 = algc;
243+
arg1.negate(); // Negate the terms and sense
244+
prepro.set_result_var(
245+
MPD( AssignResultVar2Args(
246+
ConditionalConstraint<
247+
AlgebraicConstraint< Body, AlgConRhs<
248+
-kind> > > { {
249+
std::move(arg1.GetBody()), arg1.rhs()
250+
} } ) ));
251+
return;
252+
}
253+
// See if we need to round the constant term
224254
auto rhs = algc.rhs();
225255
auto bnt_body = MPD(
226256
ComputeBoundsAndType(algc.GetBody()) );

include/mp/flat/convert_functional.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class BasicFCC {
3333
SetResultVar(GetConverter().
3434
template GetConstraint<Constraint>(i).
3535
GetResultVar());
36-
GetConverter().IncrementVarUsage(GetResultVar());
36+
GetConverter().IncrementVarUsage(GetResultVar()); // already here
3737
if (GetConverter().DoingAutoLinking()) { // Autolink known targets
3838
auto& varvn = GetConverter().GetVarValueNode();
3939
GetConverter().AutoLink( varvn.Select(GetResultVar()) );

include/mp/flat/expr_affine.h

+6
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ class LinTerms {
110110
add_term(term.first, term.second);
111111
}
112112

113+
/// Is normalized? Assume terms are sorted.
114+
bool is_normalized() const {
115+
assert(size());
116+
return coef(0) > 0.0;
117+
}
118+
113119
/// Negate
114120
void negate() {
115121
for (auto& c: coefs_)

include/mp/flat/expr_quadratic.h

+15
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ class QuadTerms {
7373
vars2_.reserve(num_terms);
7474
}
7575

76+
/// Is normalized? Assume sorted.
77+
bool is_normalized() const {
78+
assert(size());
79+
return coef(0) > 0.0;
80+
}
81+
7682
/// Arithmetic
7783
void negate() {
7884
for (auto& cf: coefs_)
@@ -172,6 +178,15 @@ class QuadAndLinTerms :
172178
/// add_term(c, v1, v2)
173179
using QuadTerms::add_term;
174180

181+
/// Is normalized? Assume sorted.
182+
bool is_normalized() const {
183+
assert(QuadTerms::size());
184+
return
185+
LinTerms::size()
186+
? LinTerms::is_normalized()
187+
: QuadTerms::is_normalized();
188+
}
189+
175190
/// Negate
176191
void negate() {
177192
LinTerms::negate();

include/mp/flat/redef/MIP/converter_mip.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class MIPFlatConverter
303303

304304
private:
305305
struct Options {
306-
double cmpEps_ { 1e-4 };
306+
double cmpEps_ { 0.0 };
307307
double bigM_default_ { -1 };
308308
double PLApproxRelTol_ { 1e-2 };
309309
double PLApproxDomain_ { 1e6 };
@@ -313,7 +313,9 @@ class MIPFlatConverter
313313
void InitOwnOptions() {
314314
this->GetEnv().AddOption("cvt:mip:eps cvt:cmp:eps",
315315
"Tolerance for strict comparison of continuous variables for MIP. "
316-
"Ensure larger than the solver's feasibility tolerance.",
316+
"Also applies to negation of conditional comparisons: "
317+
"b==1 <==> x<=5 means that with b==0, x>=5+eps. "
318+
"Default: 0.",
317319
options_.cmpEps_, 0.0, 1e100);
318320
this->GetEnv().AddOption("cvt:bigM cvt:bigm cvt:mip:bigM cvt:mip:bigm",
319321
"Default value of big-M for linearization of logical constraints. "

test/end2end/cases/categorized/fast/complementarity/modellist.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"files" : ["econ2.mod", "econ2.dat"],
55
"tags" : ["linear", "continuous", "complementarity"],
66
"options": {
7-
"gcg_options": "cvt:bigm=1e5"
7+
"ANYSOLVER_options": "cvt:bigm=1e5"
88
},
99
"values": {
1010
"Price['AA1']": 0.0,

test/end2end/cases/categorized/fast/cp_global_constraints/modellist.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"tags": [ "logical" ],
1212
"objective": 22,
1313
"options": {
14-
"cbc_options": "cvt:mip:bigM=100000",
14+
"ANYSOLVER_options": "cvt:mip:bigM=100000",
1515
"gcg_options": "cvt:mip:bigM=100000 mode=2"
1616
}
1717
},
@@ -21,7 +21,7 @@
2121
"tags": [ "logical" ],
2222
"objective": 22,
2323
"options": {
24-
"cbc_options": "cvt:mip:bigM=100000",
24+
"ANYSOLVER_options": "cvt:mip:bigM=100000",
2525
"gcg_options": "cvt:mip:bigM=100000 mode=2"
2626
}
2727
},
@@ -80,7 +80,7 @@
8080
"tags" : ["logical"],
8181
"options": {
8282
"solution_round": "6",
83-
"gcg_options": "cvt:mip:bigM=100000"
83+
"ANYSOLVER_options": "cvt:mip:bigM=100000"
8484
},
8585
"comment_options": "For solution_round, see #200",
8686
"objective" : 218125

test/end2end/cases/categorized/fast/logical/ifthen_var.mod

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
/** Test expression map as well as if-then */
1+
/**
2+
* Test expression map as well as if-then.
3+
* Requires cvt:cmp:eps > feastol
4+
* for AMPL to correctly compute obj value, see #102.
5+
*/
26

37
var x >=-100, <= 200;
48
var y >=-300, <= 460;

test/end2end/cases/categorized/fast/logical/modellist.json

+18-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@
1515
{
1616
"name" : "ifthen_var",
1717
"objective" : -5,
18-
"tags" : ["logical"]
18+
"tags" : ["logical"],
19+
"options": {
20+
"ANYSOLVER_options": "cvt:cmp:eps=1e-4"
21+
},
22+
"comment": [
23+
"For AMPL to correctly compute obj value, ",
24+
"need strict inequality for the opposite case"
25+
]
1926
},
2027
{
2128
"name" : "test_int_non_int",
@@ -113,17 +120,25 @@
113120
{
114121
"name" : "booleq_01",
115122
"options": {
116-
"gcg_options": "cvt:bigm=1e5"
123+
"ANYSOLVER_options": "cvt:bigm=1e5"
117124
},
118125
"objective" : 1,
119126
"tags" : ["logical"]
120127
},
121128
{
122129
"name" : "booleq_02",
123130
"options": {
124-
"gcg_options": "cvt:bigm=1e5"
131+
"ANYSOLVER_options": "cvt:bigm=1e5"
125132
},
126133
"objective" : 1,
127134
"tags" : ["logical"]
135+
},
136+
{
137+
"name" : "x-multmip3_small",
138+
"options": {
139+
"ANYSOLVER_options": "cvt:bigm=1e5"
140+
},
141+
"objective" : 150,
142+
"tags" : ["logical"]
128143
}
129144
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## Trying to replicate the cvt:cmp:eps problem from x-multmip3.mod.
2+
## Checks that all cases of x[i] >= minl are assigned
3+
## the same auxiliary variable, otherwise with cvt:cmp:eps=0
4+
## the optimal objective becomes 0. See also #102.
5+
6+
param n default 4;
7+
param minl default 375;
8+
param limU default 500;
9+
param fcost default 50;
10+
param overall default 1200;
11+
param maxserve default n-1;
12+
13+
var x{1..n} >= 0;
14+
15+
minimize Total:
16+
sum {i in 1..n} if x[i]>=minl then fcost;
17+
18+
s.t. Lin01:
19+
sum {i in 1..n} x[i] >= overall;
20+
21+
s.t. Disj{i in 1..n}:
22+
x[i]==0 or minl <= x[i] <= limU;
23+
24+
s.t. Count:
25+
count {i in 1..n} (x[i] >= minl) <= maxserve;

test/end2end/scripts/python/AMPLRunner.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from shutil import which
55

66
from Solver import Solver
7-
from amplpy import AMPL, Kind, OutputHandler, ErrorHandler, Runnable, ampl
7+
from amplpy import AMPL, Kind, OutputHandler, ErrorHandler, ampl
88
from Model import Model
99
import time
1010
from TimeMe import TimeMe

0 commit comments

Comments
 (0)