Skip to content

Commit

Permalink
Some updates
Browse files Browse the repository at this point in the history
  • Loading branch information
wujianjack committed Oct 10, 2023
1 parent 3452f90 commit 4495603
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 68 deletions.
97 changes: 61 additions & 36 deletions pyomo/contrib/appsi/solvers/copt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import math
from typing import List, Dict, Optional
from pyomo.common.collections import ComponentMap, OrderedSet
from pyomo.common.log import LogStream
from pyomo.common.dependencies import attempt_import
from pyomo.common.errors import PyomoException
from pyomo.common.tee import capture_output, TeeStream
from pyomo.common.timing import HierarchicalTimer
from pyomo.common.shutdown import python_is_shutting_down
from pyomo.common.config import ConfigValue
from pyomo.common.config import ConfigValue, NonNegativeInt
from pyomo.core.kernel.objective import minimize, maximize
from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler
from pyomo.core.base.var import _GeneralVarData
Expand All @@ -26,22 +28,24 @@
)
from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available
from pyomo.core.staleflag import StaleFlagManager
import sys

logger = logging.getLogger(__name__)


coptpy_available = False
try:
import coptpy
def _import_coptpy():
try:
import coptpy
except ImportError:
Copt._available = Copt.Availability.NotFound
raise
if coptpy.COPT.VERSION_MAJOR < 6:
Copt._available = Copt.Availability.BadVersion
raise ImportError('The APPSI COPT interface requires coptpy>=6.0.0')
return coptpy

if not (
coptpy.COPT.VERSION_MAJOR > 6
or (coptpy.COPT.VERSION_MAJOR == 6 and coptpy.COPT.VERSION_MINOR >= 5)
):
raise ImportError('The APPSI Copt interface requires coptpy>=6.5.0')
coptpy_available = True
except:
pass

coptpy, coptpy_available = attempt_import('coptpy', importer=_import_coptpy)


class DegreeError(PyomoException):
Expand All @@ -66,7 +70,12 @@ def __init__(
)

self.declare('logfile', ConfigValue(domain=str))
self.declare('solver_output_logger', ConfigValue())
self.declare('log_level', ConfigValue(domain=NonNegativeInt))

self.logfile = ''
self.solver_output_logger = logger
self.log_level = logging.INFO


class CoptSolutionLoader(PersistentSolutionLoader):
Expand Down Expand Up @@ -226,6 +235,7 @@ class Copt(PersistentBase, PersistentSolver):

_available = None
_num_instances = 0
_coptenv = None

def __init__(self, only_child_vars=True):
super(Copt, self).__init__(only_child_vars=only_child_vars)
Expand All @@ -235,10 +245,8 @@ def __init__(self, only_child_vars=True):
self._solver_options = dict()
self._solver_model = None

if coptpy_available:
if coptpy_available and self._coptenv is None:
self._coptenv = coptpy.Envr()
else:
self._coptenv = None

self._symbol_map = SymbolMap()
self._labeler = None
Expand All @@ -256,7 +264,12 @@ def __init__(self, only_child_vars=True):
self._last_results_object: Optional[CoptResults] = None

def available(self):
if self._available is None:
if not coptpy_available:
return self.Availability.NotFound
elif self._available == self.Availability.BadVersion:
return self.Availability.BadVersion
else:
self._available = Copt.Availability.BadLicense
if self._coptenv is not None:
m = self._coptenv.createModel('checklic')
m.setParam("Logging", 0)
Expand All @@ -267,7 +280,7 @@ def available(self):
self._available = Copt.Availability.FullLicense
except coptpy.CoptError:
self._available = Copt.Availability.LimitedLicense
return self._available
return self._available

def release_license(self):
self._reinit()
Expand Down Expand Up @@ -316,25 +329,37 @@ def symbol_map(self):
return self._symbol_map

def _solve(self, timer: HierarchicalTimer):
config = self.config
options = self.copt_options
if config.stream_solver:
self._solver_model.setParam('LogToConsole', 1)
else:
self._solver_model.setParam('LogToConsole', 0)
self._solver_model.setLogFile(config.logfile)

if config.time_limit is not None:
self._solver_model.setParam('TimeLimit', config.time_limit)
if config.mip_gap is not None:
self._solver_model.setParam('RelGap', config.mip_gap)

for key, option in options.items():
self._solver_model.setParam(key, option)
timer.start('solve')
self._solver_model.solve(self._callback)
timer.stop('solve')
self._needs_updated = False
ostreams = [
LogStream(
level=self.config.log_level, logger=self.config.solver_output_logger
)
]
if self.config.stream_solver:
ostreams.append(sys.stdout)

with TeeStream(*ostreams) as t:
with capture_output(output=t.STDOUT, capture_fd=False):
config = self.config
options = self.copt_options

if config.stream_solver:
self._solver_model.setParam('LogToConsole', 1)
else:
self._solver_model.setParam('LogToConsole', 0)
self._solver_model.setLogFile(config.logfile)

if config.time_limit is not None:
self._solver_model.setParam('TimeLimit', config.time_limit)
if config.mip_gap is not None:
self._solver_model.setParam('RelGap', config.mip_gap)

for key, option in options.items():
self._solver_model.setParam(key, option)

timer.start('solve')
self._solver_model.solve()
timer.stop('solve')

return self._postsolve(timer)

def solve(self, model, timer: HierarchicalTimer = None) -> Results:
Expand Down
2 changes: 0 additions & 2 deletions pyomo/contrib/mindtpy/config_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,6 @@ def _add_subsolver_configs(CONFIG):
[
'gurobi',
'cplex',
'copt',
'cbc',
'glpk',
'gams',
Expand Down Expand Up @@ -616,7 +615,6 @@ def _add_subsolver_configs(CONFIG):
[
'gurobi',
'cplex',
'copt',
'cbc',
'glpk',
'gams',
Expand Down
54 changes: 31 additions & 23 deletions pyomo/solvers/plugins/solvers/copt_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import sys

from pyomo.common.collections import ComponentSet, ComponentMap, Bunch
from pyomo.common.dependencies import attempt_import
from pyomo.common.errors import ApplicationError
from pyomo.common.tempfiles import TempfileManager
from pyomo.core.expr.numvalue import value, is_fixed
Expand All @@ -32,21 +33,35 @@

logger = logging.getLogger('pyomo.solvers')

coptpy_available = False
try:
import coptpy

coptpy_available = True
except:
class DegreeError(ValueError):
pass


class DegreeError(ValueError):
pass
def _parse_coptpy_version(coptpy, avail):
if not avail:
return
coptpy_major = coptpy.COPT.VERSION_MAJOR
coptpy_minor = coptpy.COPT.VERSION_MINOR
coptpy_tech = coptpy.COPT.VERSION_TECHNICAL
CoptDirect._version = (coptpy_major, coptpy_minor, coptpy_tech)
CoptDirect._name = "COPT %s.%s.%s" % CoptDirect._version
while len(CoptDirect._version) < 4:
CoptDirect._version += (0,)
CoptDirect._version = CoptDirect._version[:4]


coptpy, coptpy_available = attempt_import(
'coptpy', catch_exceptions=(Exception,), callback=_parse_coptpy_version
)


@SolverFactory.register('copt_direct', doc='Direct python interface to COPT')
class CoptDirect(DirectSolver):
_name = None
_version = 0
_coptenv = None

def __init__(self, **kwds):
if 'type' not in kwds:
kwds['type'] = 'copt_direct'
Expand All @@ -55,21 +70,6 @@ def __init__(self, **kwds):

self._python_api_exists = True

self._version_major = coptpy.COPT.VERSION_MAJOR
self._version_minor = coptpy.COPT.VERSION_MINOR
self._version_technical = coptpy.COPT.VERSION_TECHNICAL
self._version_name = "COPT %s.%s.%s" % (
self._version_major,
self._version_minor,
self._version_technical,
)

if coptpy_available:
self._coptenv = coptpy.Envr()
else:
self._coptenv = None
self._coptmodel_name = "coptprob"

self._pyomo_var_to_solver_var_map = ComponentMap()
self._solver_var_to_pyomo_var_map = ComponentMap()
self._pyomo_con_to_solver_con_map = dict()
Expand All @@ -85,6 +85,11 @@ def __init__(self, **kwds):
self._capabilities.sos1 = True
self._capabilities.sos2 = True

if coptpy_available and self._coptenv is None:
self._coptenv = coptpy.Envr()
self._coptmodel_name = "coptprob"
self._solver_model = None

def available(self, exception_flag=True):
if not coptpy_available:
if exception_flag:
Expand Down Expand Up @@ -338,6 +343,9 @@ def _set_instance(self, model, kwds={}):
self._pyomo_var_to_solver_var_map = ComponentMap()
self._solver_var_to_pyomo_var_map = ComponentMap()

if self._solver_model is not None:
self._solver_model.clear()
self._solver_model = None
try:
if model.name is not None:
self._solver_model = self._coptenv.createModel(model.name)
Expand Down Expand Up @@ -403,7 +411,7 @@ def _postsolve(self):
self.results = SolverResults()
soln = Solution()

self.results.solver.name = self._version_name
self.results.solver.name = self._name
self.results.solver.wallclock_time = self._solver_model.SolvingTime

status = self._solver_model.status
Expand Down
5 changes: 1 addition & 4 deletions pyomo/solvers/plugins/solvers/copt_persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

from pyomo.solvers.plugins.solvers.copt_direct import CoptDirect, coptpy_available
from pyomo.solvers.plugins.solvers.copt_direct import CoptDirect, coptpy
from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver
from pyomo.opt.base import SolverFactory

if coptpy_available:
import coptpy


@SolverFactory.register('copt_persistent', doc='Persistent python interface to COPT')
class CoptPersistent(PersistentSolver, CoptDirect):
Expand Down
3 changes: 2 additions & 1 deletion pyomo/solvers/tests/checks/test_copt_persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
import pyomo.common.unittest as unittest
import pyomo.environ as pyo

coptpy_available = False
try:
import coptpy

coptpy_available = True
except:
coptpy_available = False
pass


class TestCoptPersistent(unittest.TestCase):
Expand Down
4 changes: 2 additions & 2 deletions pyomo/solvers/tests/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,8 @@ def test_solver_cases(*args):
]
)

_test_solver_cases['copt', 'python'] = initialize(
name='copt',
_test_solver_cases['copt_direct', 'python'] = initialize(
name='copt_direct',
io='python',
capabilities=_copt_capabilities,
import_suffixes=['slack', 'dual', 'rc'],
Expand Down

0 comments on commit 4495603

Please sign in to comment.