Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docplex 2.25.236 #4

Merged
merged 1 commit into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 13 additions & 4 deletions docplex/cp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@
Detailed description
--------------------
"""

from docplex.cp.utils import *
from docplex.cp.parameters import CpoParameters, ALL_PARAMETER_NAMES

Expand All @@ -211,8 +210,14 @@

# Check if running in a worker environment
try:
import docplex.util.environment as runenv
IS_IN_WORKER = isinstance(runenv.get_environment(), runenv.WorkerEnvironment)
from docplex_wml.version import *
#from docplex_wml.worker.environment import WorkerEnvironment
#import docplex.util.environment as runenv
from docplex_wml import _in_ws_nb
if _in_ws_nb is False:
IS_IN_WORKER = True
else:
IS_IN_WORKER = False
except:
IS_IN_WORKER = False

Expand Down Expand Up @@ -471,7 +476,11 @@
# Apply special changes if running in a worker

if IS_IN_WORKER:
context.solver.max_threads = runenv.get_environment().get_available_core_count()
try:
context.solver.max_threads = int(os.environ["CORE_NUMBERS"])
except:
context.solver.max_threads = 1
#context.solver.max_threads = runenv.get_environment().get_available_core_count()


#-----------------------------------------------------------------------------
Expand Down
33 changes: 31 additions & 2 deletions docplex/cp/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,35 @@ def add_parameters(self, **kwargs):
self.parameters.__setattr__(k, v)


def read_ops_file(self, infile):
""" Load and set parameters of this model from an OPL-style .ops file.

This method loads parameters from an OPL-style .ops file. Any existing
parameter settings which are not overridden by the settings from the
file will take their default values.

Args:
infile: Either a file name or an open file object.
Returns:
The loaded parameters, now associated with this model.

"""
p = CpoParameters.read_ops_file(infile)
return self.set_parameters(p)

def export_parameters_as_ops_file(self, outfile):
""" Write parameters of this model to an OPL-style .ops file.

This method writes the non-default-valued parameters to an OPL-style .ops file.

Args:
outfile: Either a file name or an open file object.
Returns:
Nothing

"""
self.parameters.export_as_ops_file(outfile)

def get_parameters(self):
""" Get the solving parameters associated to this model.

Expand Down Expand Up @@ -1333,7 +1362,7 @@ def refine_conflict(self, **kwargs):
require to explicitly create a CpoSolver instead of calling function at model level.
Please refer to this class for more details.

This function is available on DOcplexcloud and with local CPO solver with release number greater or equal to 12.7.0.
This function is available on Watson Machine Learning and with local CPO solver with release number greater or equal to 12.7.0.

Args:
context (Optional): Complete solving context.
Expand Down Expand Up @@ -1379,7 +1408,7 @@ def propagate(self, cnstr=None, **kwargs):
require to explicitly create a CpoSolver instead of calling function at model level.
Please refer to this class for more details.

This function is available on DOcplexcloud and with local CPO solver with release number greater or equal to 12.7.0.
This function is available on Watson Machine Learning and with local CPO solver with release number greater or equal to 12.7.0.

Args:
cnstr (Optional): Optional constraint to be added to the model before invoking propagation.
Expand Down
2,657 changes: 1,384 additions & 1,273 deletions docplex/cp/parameters.py

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion docplex/cp/solver/environment_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def solver_created(self, solver):
return

# Check if calling environment is DODS (Decision Optimization for Data Science)
if self.env.is_dods():
if self.env.is_wmlworker:
# Force solve() method to proceed with search_next sequence
solver.context.solver.solve_with_search_next = True
# Check if debug mode is required
Expand Down Expand Up @@ -214,6 +214,10 @@ def result_found(self, solver, sres):
sdetails["KPI." + k] = v

# Submit details to environment
if self.env.is_wmlworker:
from docplex_wml.worker.worker_utils import make_cpo_new_kpis_dict
new_kpis = make_cpo_new_kpis_dict(mdl, solver)
sdetails.update(new_kpis)
self.publish_context.log(3, "Solve details: ", sdetails)
self.env.update_solve_details(sdetails)

Expand Down
2 changes: 1 addition & 1 deletion docplex/mp/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from docplex.mp.error_handler import docplex_fatal

try:
from docplex.worker.solvehook import get_solve_hook
from docplex_wml.worker.solvehook import get_solve_hook
except ImportError:
get_solve_hook = None

Expand Down
10 changes: 7 additions & 3 deletions docplex/mp/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ def cplex_platform():
if sys_platform == 'Windows':
return 'x64_win64'
elif sys_platform == 'Darwin':
return 'x86-64_osx'
if machine == 'x86_64':
return 'x86-64_osx'
else:
return 'arm64_osx'
elif sys_platform == 'Linux':
if machine == 'x86_64':
return 'x86-64_linux'
Expand Down Expand Up @@ -224,7 +227,7 @@ def python_version(self):

def auto_configure(self, logger=None):
self.check_cplex(logger=logger)
# check for pandas (watson studio)
# check for pandas (docplex_wml studio)
self.check_pandas()

def check_all(self):
Expand Down Expand Up @@ -306,7 +309,8 @@ def load_cplex_from_cos_root(cos_root, version=""):
# user provided a cos location that was not right, raise warning
warnings.warn("Could not load CPLEX from Location provided by DOCPLEX_COS_LOCATION=%s. Using default locations." % user_cos_location)
if cplex is None:
try_environs = ['CPLEX_STUDIO_DIR221',
try_environs = ['CPLEX_STUDIO_DIR2211',
'CPLEX_STUDIO_DIR221',
'CPLEX_STUDIO_DIR20101',
'CPLEX_STUDIO_DIR201',
'CPLEX_STUDIO_DIR1210',
Expand Down
12 changes: 11 additions & 1 deletion docplex/mp/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import os
import sys
import warnings
from itertools import chain
from itertools import chain, product
from io import StringIO

from docplex.mp.aggregator import ModelAggregator
Expand Down Expand Up @@ -5740,6 +5740,16 @@ def export_parameters_as_prm(self, path=None, basename=None):
self.parameters.export_prm_to_path(path=prm_path)
return prm_path

def export_parameters_as_ops(self, path=None, basename=None):
# path is either a nonempty path string or None
self._checker.typecheck_string(path, accept_none=True, accept_empty=False)
self._checker.typecheck_string(basename, accept_none=True, accept_empty=False)

# combination of path/directory and basename resolution are done in resolve_path
ops_path = self._resolve_path(path, basename, extension='.ops')
self.parameters.export_as_ops_file(filename=ops_path)
return ops_path

def export_annotations(self, path=None, basename=None):
from docplex.mp.anno import ModelAnnotationPrinter

Expand Down
84 changes: 81 additions & 3 deletions docplex/mp/model_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@

from docplex.mp.quad import VarPair

import os
import xml.etree.ElementTree as ET
import tempfile



class ModelReaderError(DOcplexException):
pass
Expand Down Expand Up @@ -118,6 +123,82 @@ def parse_sense(cls, cpx_sense, sense_dict=_sense2comp_dict):
return sense_dict.get(cpx_sense)

@classmethod
def read_ops_file(cls, filename, version=None):
""" Reads an OPL .ops setting file and builds a CPLEX parameter group.

Reads a .ops file and returns a DOcplex parameter group instance.
All CPO and OPL settings are ignored. This parameter object can be used in a solve().
.ops file have dedicated User Interface in OPL IDE and Cloud Pak for Data Experiments, which ease
the selection of engine settings.

Args:
filename: a path string
version: optional CPLEX version.

Returns:
A `RootParameterGroup object`, if the read operation succeeds, else None.
"""
# We parse the XML file to get the list of cplex params names in OPL naming convention and their value.
# We rely on the old 12.x CPLEX naming convention CPX_PARAM_ instead of standard CPXPARAM_ as in OPL runtime to rebuild the names.
# We build a temporary .prm and import it in docplex with existing method.
# We can't do a cleaner way as not all CPLEX C methods related to settings are available in python.
# Adding them would only work in a next future version and not previous ones...

tree = ET.parse(filename)
root = tree.getroot()
# Find the CPLEX section
# < settings version = "2" >
# < category name = "cplex" >
# < setting name = "advind" value = "2" / >

cplex_node = None
if root.tag == "settings":
for child in root:
if child.tag == "category" and child.attrib.get("name", "") == "cplex":
cplex_node = child
break
values = {}
if cplex_node is not None:
for child in cplex_node:
if child.tag == "setting":
if "name" in child.attrib and "value" in child.attrib:
# Some settings have different names from OPL to CPLEX
def get_name(name):
if name == "rootalg":
name = "STARTALG"
elif name == "nodealg":
name = "SUBALG"
return "CPX_PARAM_" + name.upper()

values[get_name(child.attrib["name"].lower())] = child.attrib["value"]
else:
raise Exception("Bad XML .ops file: bad attributes {0}".format(child.attrib))
else:
raise Exception("Bad XML .ops file: unknown tag {0}".format(child.tag))
else:
return None

# clean up param input
def boolean_to_int(b):
if b == "true":
return 1
elif b == "false":
return 0
else:
return b

values = {k: boolean_to_int(v) for k, v in values.items()}

with tempfile.NamedTemporaryFile(suffix=".prm", delete=False) as prm_file:
if version is not None:
prm_file.write("CPLEX Parameter File Version {}\n".format(version).encode('utf-8'))
for k, v in values.items():
prm_file.write("{0} {1}\n".format(k, v).encode('utf-8'))

prms = ModelReader.read_prm(prm_file.name)
os.remove(prm_file.name)
return prms
@classmethod
def read_prm(cls, filename):
""" Reads a CPLEX PRM file.

Expand All @@ -130,9 +211,6 @@ def read_prm(cls, filename):
Returns:
A `RootParameterGroup object`, if the read operation succeeds, else None.
"""
# TODO: Clean up - now creating an adapter raise importError if CPLEX not found
# if not Cplex: # pragma: no cover
# raise RuntimeError("ModelReader.read_prm() requires CPLEX runtime.")
with _CplexReaderFileContext(filename, read_method=["parameters", "read_file"]) as adapter:
cpx = adapter.cpx
if cpx:
Expand Down
41 changes: 41 additions & 0 deletions docplex/mp/params/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,47 @@ def export_prm_to_string(self, overload_params=None):
self.export_prm(oss, overload_params)
return oss.getvalue()

def export_as_ops_file(self, filename):
with open(filename, mode='w') as out:
out.write(self.export_as_ops_file_to_string())

def export_as_ops_file_to_string(self):
from xml.etree.ElementTree import Element
from xml.etree import ElementTree
from xml.dom import minidom

def prettify(elem):
"""Return a pretty-printed XML string for the Element.
"""
rough_string = ElementTree.tostring(element=elem, encoding='utf-8')
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent=" ", )

settings = Element("settings")
settings.attrib = {"version": "2"}
category = Element("category")
category.attrib = {"name": "cplex"}
settings.append(category)

for p in self.generate_nondefault_params():
name = p.cpx_name.replace("CPX_PARAM_", "").lower()
if name == "startalg":
name = "rootalg"
elif name == "subalg":
name = "nodealg"
value = str(p.get())
if isinstance(p, BoolParameter):
if p.get() == 0:
value = "false"
else:
value = "true"

child = Element("setting")
child.attrib = {"name": name, "value": value}
category.append(child)

return prettify(settings)

def print_info_to_string(self, overload_params=None, print_defaults=False):
""" Writes parameters in readable format to a string.

Expand Down
Loading