Skip to content

Commit fd88466

Browse files
Merge pull request #29 from IBMDecisionOptimization/prepare_release_2.30
New Release
2 parents 75b6b29 + 4ac42ce commit fd88466

File tree

524 files changed

+134404
-41843
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

524 files changed

+134404
-41843
lines changed

CHANGELOG.rst

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
Changelog
22
---------
33

4-
Changed in 2.29.241:
4+
Changed in 2.30.251:
55
````````````````````
6+
* Fix for SyntaxWarning: invalid escape sequence when running with Python version > 3.12.
7+
* Replace the deprecated usage of 'strtobool' with Python >= 3.12
8+
* modeller.constant(x) function creates an unhandled constant(x) in .cpo file
9+
* docplex config --upgrade requires setuptools with Python >= 3.12
10+
* docplex config --upgrade retrieves python executable
11+
* Conda SafetyError on reinstalling with conda install cplex after a docplex config --upgrade
12+
* C-style comments not allowed inside """ ... """ in some Python 3.12 versions
13+
* Replace the deprecated usage of strtobool
14+
* Fix export crash when an incorrect format is passed
15+
* Report best bound in solve details even if no integer solution has been found
16+
17+
Changed in 2.29.245 (2025.04):
18+
``````````````````````````````
619

720
* each record of a solve history use a unique timestamp for its fields
821

docplex/cp/catalog.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def __init__(self, cpname, pyname, kwrd, prio, signs):
268268
self.cpo_name = cpname
269269
self.python_name = pyname
270270
self.priority = prio
271-
if kwrd:
271+
if kwrd is not None:
272272
self.keyword = kwrd
273273
else:
274274
self.keyword = cpname
@@ -571,8 +571,8 @@ def __ne__(self, other):
571571
CpoSignature(Type_Constraint, (Type_PositiveInt, Type_CumulExpr)),
572572
CpoSignature(Type_Constraint, (Type_CumulExpr, Type_IntExpr)),
573573
CpoSignature(Type_Constraint, (Type_IntExpr, Type_CumulExpr))) )
574-
Oper_lexicographic = CpoOperation("lexicographic", "lexicographic", None, -1, ( CpoSignature(Type_Constraint, (Type_IntExprArray, Type_IntExprArray)),) )
575-
Oper_strict_lexicographic = CpoOperation("strictLexicographic", "strict_lexicographic", None, -1, ( CpoSignature(Type_Constraint, (Type_IntExprArray, Type_IntExprArray)),) )
574+
Oper_lexicographic = CpoOperation("lexicographic", "lexicographic", None, -1, ( CpoSignature(Type_BoolExpr, (Type_IntExprArray, Type_IntExprArray)),) )
575+
Oper_strict_lexicographic = CpoOperation("strictLexicographic", "strict_lexicographic", None, -1, ( CpoSignature(Type_BoolExpr, (Type_IntExprArray, Type_IntExprArray)),) )
576576
Oper_log = CpoOperation("log", "log", None, -1, ( CpoSignature(Type_FloatExpr, (Type_FloatExpr,)),) )
577577
Oper_logical_and = CpoOperation("and", "logical_and", "&&", 7, ( CpoSignature(Type_BoolExpr, (Type_BoolExpr, Type_BoolExpr)),
578578
CpoSignature(Type_BoolExpr, (Type_BoolExprArray,)),) )

docplex/cp/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# (c) Copyright IBM Corp. 2015 - 2022
55
# --------------------------------------------------------------------------
66

7-
"""
7+
r"""
88
Configuration of the CP Optimizer Python API
99
1010
This module is the top-level handler of the configuration parameters for

docplex/cp/cpo/cpo_compiler.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def _write_expression(self, out, xinfo):
404404
if isinstance(expr, CpoAlias):
405405
# Simple alias
406406
out.write(name + u" = " + self._compile_expression(expr.expr, False) + u";\n")
407-
elif isroot and expr.type in (Type_Constraint, Type_SearchPhase, Type_BoolExpr):
407+
elif isroot and expr.type in (Type_Constraint, Type_SearchPhase, Type_BoolExpr, Type_Objective):
408408
# Named constraint
409409
if self.is_format_at_least_12_8:
410410
out.write(name + u": " + self._compile_expression(expr, True) + u";\n")
@@ -557,10 +557,12 @@ def _compile_expression(self, expr, root=True):
557557
estack[-1] = [res, 0, False]
558558
continue
559559

560-
cout.append(self._get_id_string(oper.keyword))
561-
cout.append("(")
560+
if len(oper.keyword) > 0:
561+
cout.append(self._get_id_string(oper.keyword))
562+
cout.append("(")
562563
if cnx >= oplen:
563-
cout.append(")")
564+
if len(oper.keyword) > 0:
565+
cout.append(")")
564566
estack.pop()
565567
else:
566568
edscr[1] += 1

docplex/cp/cpo/cpo_parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
MIN_CPO_VERSION_NUMBER = "12.6.0.0"
3131

3232
# Maximum CPO format version number
33-
MAX_CPO_VERSION_NUMBER = "21.10.0.0"
33+
MAX_CPO_VERSION_NUMBER = "22.1.2.0"
3434

3535
# Map of all operators. Key is operator, value is list of corresponding operation descriptors
3636
_ALL_OPERATORS = {}
@@ -806,9 +806,9 @@ def _read_section_internals(self):
806806
if compare_natural(ver, MIN_CPO_VERSION_NUMBER) < 0:
807807
raise CpoUnsupportedFormatVersionException("Can not parse a CPO file with version {}, lower than {}"
808808
.format(ver, MIN_CPO_VERSION_NUMBER))
809-
if compare_natural(ver, MAX_CPO_VERSION_NUMBER) > 0:
810-
raise CpoUnsupportedFormatVersionException("Can not parse a CPO file with version {}, greater than {}"
811-
.format(ver, MAX_CPO_VERSION_NUMBER))
809+
# if compare_natural(ver, MAX_CPO_VERSION_NUMBER) > 0:
810+
# raise CpoUnsupportedFormatVersionException("Can not parse a CPO file with version {}, greater than {}"
811+
# .format(ver, MAX_CPO_VERSION_NUMBER))
812812
self._check_token(self._next_token(), TOKEN_PARENT_CLOSE)
813813
tok = self._next_token()
814814

docplex/cp/expression.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2186,7 +2186,13 @@ def _domain_iterator(d):
21862186
if isinstance(d, (tuple, list)):
21872187
for x in d:
21882188
if isinstance(x, (list, tuple)):
2189-
min, max = x
2189+
# test changes
2190+
assert len(x) == 2 or (len(x) == 3 and x[2] == "holes")
2191+
min, max = x[:2]
2192+
# if len(x)>2:
2193+
# min, max = x[0],x[1]
2194+
# else:
2195+
# min, max = x
21902196
if min == max:
21912197
yield min
21922198
else:

docplex/cp/lp/lp_tokenizer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class LpTokenizer(Tokenizer):
6969
__slots__ = ()
7070

7171
def __init__(self, **args):
72-
""" Create a new tokenizer
72+
r""" Create a new tokenizer
7373
Args:
7474
See arguments list of :class:`~docplex.cp.utils.Tokenizer`
7575
"""
@@ -201,7 +201,7 @@ def _read_symbol(self):
201201

202202

203203
def _read_antislash(self):
204-
""" Read token starting by \ """
204+
r""" Read token starting by \ """
205205
self._skip_to_end_of_line()
206206
return Token(TOKEN_TYPE_COMMENT, None if self.skip_comments else self._get_token()[:-1])
207207

docplex/cp/modeler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,7 +1735,7 @@ def lexicographic(x, y):
17351735
x = _convert_arg(x, "x", Type_IntExprArray)
17361736
y = _convert_arg(y, "y", Type_IntExprArray)
17371737
_check_same_size_arrays(x, y)
1738-
return CpoFunctionCall(Oper_lexicographic, Type_Constraint, (x, y))
1738+
return CpoFunctionCall(Oper_lexicographic, Type_BoolExpr, (x, y))
17391739

17401740

17411741
def strict_lexicographic(x, y):
@@ -1762,7 +1762,7 @@ def strict_lexicographic(x, y):
17621762
x = _convert_arg(x, "x", Type_IntExprArray)
17631763
y = _convert_arg(y, "y", Type_IntExprArray)
17641764
_check_same_size_arrays(x, y)
1765-
return CpoFunctionCall(Oper_strict_lexicographic, Type_Constraint, (x, y))
1765+
return CpoFunctionCall(Oper_strict_lexicographic, Type_BoolExpr, (x, y))
17661766

17671767

17681768
def standard_deviation(x, meanLB=NEGATIVE_INFINITY, meanUB=POSITIVE_INFINITY):

docplex/cp/solution.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ def get_objective_bound(self):
17761776

17771777

17781778
def get_objective_gaps(self):
1779-
""" Gets the numeric values of the gap between objective value and objective bound.
1779+
r""" Gets the numeric values of the gap between objective value and objective bound.
17801780
17811781
For a single objective, gap is calculated as gap = \|value - bound\| / max(1e-10, \|value\|)
17821782
@@ -1792,7 +1792,7 @@ def get_objective_gaps(self):
17921792

17931793

17941794
def get_objective_gap(self):
1795-
""" Gets the numeric values of the gap between the first objective value and objective bound.
1795+
r""" Gets the numeric values of the gap between the first objective value and objective bound.
17961796
17971797
For a single objective, gap is calculated as gap = \|value - bound\| / max(1e-10, \|value\|)
17981798

docplex/mp/cplex_engine.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from docplex.mp.constants import ConflictStatus
2222
from docplex.mp.constr import IndicatorConstraint, RangeConstraint, BinaryConstraint, \
2323
EquivalenceConstraint
24+
from docplex.mp.format import ExchangeFormat
2425
from docplex.mp.progress import ProgressData
2526
from docplex.mp.qprogress import QProgressData
2627
from docplex.mp.solution import SolveSolution, SolutionPool
@@ -2048,12 +2049,13 @@ def solve(self, mdl, parameters=None, **kwargs):
20482049
is_mip = cpx._is_MIP()
20492050

20502051
solve_ok = self._is_solve_status_ok(cpx_status)
2051-
if solve_ok:
2052-
nb_iterations, nb_nodes_processed = get_progress_details(cpx)
2053-
if is_mip:
2052+
if is_mip:
2053+
if not self._is_multiobj():
2054+
cpx_bestbound = cpx.solution.MIP.get_best_objective()
2055+
if solve_ok:
2056+
nb_iterations, nb_nodes_processed = get_progress_details(cpx)
20542057
if not self._is_multiobj():
20552058
cpx_miprelgap = cpx.solution.MIP.get_mip_relative_gap()
2056-
cpx_bestbound = cpx.solution.MIP.get_best_objective()
20572059
else:
20582060
cpx_miprelgap = cpx_bestbound = self.get_infinity()
20592061

@@ -2547,6 +2549,9 @@ def var_by_index(idx):
25472549
return ConflictRefinerResult(conflicts, refined_by=self.name)
25482550

25492551
def export(self, out, exchange_format):
2552+
if exchange_format is not None and not isinstance(exchange_format, ExchangeFormat):
2553+
self._model.fatal("Not a valid exchange format: {0}", exchange_format)
2554+
25502555
self.sync_cplex()
25512556
if is_string(out):
25522557
path = out
@@ -2563,7 +2568,7 @@ def export(self, out, exchange_format):
25632568
else:
25642569
raise DOcplexException("CPLEX error in SAV export: {0!s}", cpx_se)
25652570
else:
2566-
# assume a file-like object
2571+
# delegate to cplex, assume write and close methods.
25672572
filetype = exchange_format.filetype
25682573
return self._cplex.write_to_stream(out, filetype)
25692574

0 commit comments

Comments
 (0)