Skip to content

Commit 9caeed2

Browse files
authored
Merge branch 'main' into sens
2 parents 0f38064 + d38d9cb commit 9caeed2

File tree

8 files changed

+389
-58
lines changed

8 files changed

+389
-58
lines changed

pyomo/contrib/parmest/examples/semibatch/semibatch.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,20 @@ def label_model(self):
284284

285285
m = self.model
286286

287+
m.experiment_outputs = Suffix(direction=Suffix.LOCAL)
288+
m.experiment_outputs.update(
289+
(m.Ca[t], self.data["Ca_meas"][f"{t}"]) for t in m.measT
290+
)
291+
m.experiment_outputs.update(
292+
(m.Cb[t], self.data["Cb_meas"][f"{t}"]) for t in m.measT
293+
)
294+
m.experiment_outputs.update(
295+
(m.Cc[t], self.data["Cc_meas"][f"{t}"]) for t in m.measT
296+
)
297+
m.experiment_outputs.update(
298+
(m.Tr[t], self.data["Tr_meas"][f"{t}"]) for t in m.measT
299+
)
300+
287301
m.unknown_parameters = Suffix(direction=Suffix.LOCAL)
288302
m.unknown_parameters.update(
289303
(k, ComponentUID(k)) for k in [m.k1, m.k2, m.E1, m.E2]

pyomo/contrib/parmest/parmest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,13 @@ def __init__(
284284
try:
285285
outputs = [k.name for k, v in model.experiment_outputs.items()]
286286
except:
287-
RuntimeError(
287+
raise RuntimeError(
288288
'Experiment list model does not have suffix ' + '"experiment_outputs".'
289289
)
290290
try:
291291
params = [k.name for k, v in model.unknown_parameters.items()]
292292
except:
293-
RuntimeError(
293+
raise RuntimeError(
294294
'Experiment list model does not have suffix ' + '"unknown_parameters".'
295295
)
296296

pyomo/contrib/parmest/tests/test_parmest.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,39 @@ def SSE(model):
8080
exp_list, obj_function=SSE, solver_options=solver_options, tee=True
8181
)
8282

83+
def test_parmest_exception(self):
84+
"""
85+
Test the exception raised by parmest when the "experiment_outputs"
86+
attribute is not defined in the model
87+
"""
88+
from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import (
89+
RooneyBieglerExperiment,
90+
)
91+
92+
# create an instance of the RooneyBieglerExperiment class
93+
# without the "experiment_outputs" attribute
94+
class RooneyBieglerExperimentException(RooneyBieglerExperiment):
95+
def label_model(self):
96+
m = self.model
97+
98+
# add the unknown parameters
99+
m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL)
100+
m.unknown_parameters.update(
101+
(k, pyo.ComponentUID(k)) for k in [m.asymptote, m.rate_constant]
102+
)
103+
104+
# create an experiment list
105+
exp_list = []
106+
for i in range(self.data.shape[0]):
107+
exp_list.append(RooneyBieglerExperimentException(self.data.loc[i, :]))
108+
109+
# check the exception raised by parmest due to not defining
110+
# the "experiment_outputs"
111+
with self.assertRaises(RuntimeError) as context:
112+
parmest.Estimator(exp_list, obj_function="SSE", tee=True)
113+
114+
self.assertIn("experiment_outputs", str(context.exception))
115+
83116
def test_theta_est(self):
84117
objval, thetavals = self.pest.theta_est()
85118

@@ -816,6 +849,22 @@ def label_model(self):
816849

817850
m = self.model
818851

852+
if isinstance(self.data, pd.DataFrame):
853+
meas_time_points = self.data.index
854+
else:
855+
meas_time_points = list(self.data["ca"].keys())
856+
857+
m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
858+
m.experiment_outputs.update(
859+
(m.ca[t], self.data["ca"][t]) for t in meas_time_points
860+
)
861+
m.experiment_outputs.update(
862+
(m.cb[t], self.data["cb"][t]) for t in meas_time_points
863+
)
864+
m.experiment_outputs.update(
865+
(m.cc[t], self.data["cc"][t]) for t in meas_time_points
866+
)
867+
819868
m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL)
820869
m.unknown_parameters.update(
821870
(k, pyo.ComponentUID(k)) for k in [m.k1, m.k2]
@@ -885,6 +934,51 @@ def get_labeled_model(self):
885934
self.m_df = ABC_model(data_df)
886935
self.m_dict = ABC_model(data_dict)
887936

937+
# create an instance of the ReactorDesignExperimentDAE class
938+
# without the "unknown_parameters" attribute
939+
class ReactorDesignExperimentException(ReactorDesignExperimentDAE):
940+
def label_model(self):
941+
942+
m = self.model
943+
944+
if isinstance(self.data, pd.DataFrame):
945+
meas_time_points = self.data.index
946+
else:
947+
meas_time_points = list(self.data["ca"].keys())
948+
949+
m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL)
950+
m.experiment_outputs.update(
951+
(m.ca[t], self.data["ca"][t]) for t in meas_time_points
952+
)
953+
m.experiment_outputs.update(
954+
(m.cb[t], self.data["cb"][t]) for t in meas_time_points
955+
)
956+
m.experiment_outputs.update(
957+
(m.cc[t], self.data["cc"][t]) for t in meas_time_points
958+
)
959+
960+
# create an experiment list without the "unknown_parameters" attribute
961+
exp_list_df_no_params = [ReactorDesignExperimentException(data_df)]
962+
exp_list_dict_no_params = [ReactorDesignExperimentException(data_dict)]
963+
964+
self.exp_list_df_no_params = exp_list_df_no_params
965+
self.exp_list_dict_no_params = exp_list_dict_no_params
966+
967+
def test_parmest_exception(self):
968+
"""
969+
Test the exception raised by parmest when the "unknown_parameters"
970+
attribute is not defined in the model
971+
"""
972+
with self.assertRaises(RuntimeError) as context:
973+
parmest.Estimator(self.exp_list_df_no_params, obj_function="SSE")
974+
975+
self.assertIn("unknown_parameters", str(context.exception))
976+
977+
with self.assertRaises(RuntimeError) as context:
978+
parmest.Estimator(self.exp_list_dict_no_params, obj_function="SSE")
979+
980+
self.assertIn("unknown_parameters", str(context.exception))
981+
888982
def test_dataformats(self):
889983
obj1, theta1 = self.pest_df.theta_est()
890984
obj2, theta2 = self.pest_dict.theta_est()

pyomo/neos/kestrel.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,13 @@ def __del__(self):
9494
# Note that this is only to suppress warnings, as __del__ is not
9595
# guaranteed to be called (especially for any objects that still
9696
# exist when the Python process terminates)
97-
if self.neos is not None:
98-
self.transport.close()
97+
if getattr(self, 'neos', None) is not None:
98+
try:
99+
if hasattr(self, 'transport') and hasattr(self.transport, 'close'):
100+
self.transport.close()
101+
except Exception:
102+
# never raise from a destructor
103+
pass
99104

100105
def setup_connection(self):
101106
import http.client
@@ -127,14 +132,16 @@ def setup_connection(self):
127132
xmlrpclib.ProtocolError,
128133
http.client.BadStatusLine,
129134
NotImplementedError,
130-
):
131-
e = sys.exc_info()[1]
135+
Exception,
136+
) as e:
137+
self.connect_error = e
132138
self.neos = None
133139
logger.info("Fail: %s" % (e,))
134140
logger.warning("NEOS is temporarily unavailable:\n\t(%s)" % (e,))
135141

136142
def tempfile(self):
137-
return os.path.join(tempfile.gettempdir(), 'at%s.jobs' % os.getenv('ampl_id'))
143+
ampl_id = os.getenv('ampl_id', 'unknown')
144+
return os.path.join(tempfile.gettempdir(), f'at{ampl_id}.jobs')
138145

139146
def kill(self, jobNumber, password):
140147
response = self.neos.killJob(jobNumber, password)
@@ -148,7 +155,7 @@ def solvers(self):
148155
while attempt < 3:
149156
try:
150157
return self.neos.listSolversInCategory("kestrel")
151-
except socket.timeout:
158+
except (socket.timeout, Exception):
152159
attempt += 1
153160
return []
154161

@@ -157,13 +164,14 @@ def retrieve(self, stub, jobNumber, password):
157164
results = self.neos.getFinalResults(jobNumber, password)
158165
if isinstance(results, xmlrpclib.Binary):
159166
results = results.data
167+
if isinstance(results, str):
168+
results = results.encode()
160169
# decode results to kestrel.sol; well try to anyway, any errors
161170
# will result in error strings in .sol file instead of solution.
162171
if stub[-4:] == '.sol':
163172
stub = stub[:-4]
164-
solfile = open(stub + ".sol", "wb")
165-
solfile.write(results)
166-
solfile.close()
173+
with open(stub + ".sol", "wb") as solfile:
174+
solfile.write(results)
167175

168176
def submit(self, xml):
169177
# LOGNAME and USER should map to the effective user (i.e., the
@@ -214,7 +222,13 @@ def getJobAndPassword(self):
214222

215223
def getAvailableSolvers(self):
216224
"""Return a list of all NEOS solvers that this interface supports"""
217-
allKestrelSolvers = self.neos.listSolversInCategory("kestrel")
225+
if self.neos is None:
226+
return []
227+
try:
228+
allKestrelSolvers = self.neos.listSolversInCategory("kestrel")
229+
except Exception as e:
230+
logger.warning("Failed to retrieve solver list from NEOS: %s", e)
231+
return []
218232
_ampl = ':AMPL'
219233
return sorted(s[: -len(_ampl)] for s in allKestrelSolvers if s.endswith(_ampl))
220234

@@ -226,34 +240,36 @@ def getSolverName(self):
226240
227241
- we don't want to be case sensitive, but NEOS is.
228242
- we need to read in options variable
229-
230243
"""
231244
# Get a list of available kestrel solvers from NEOS
232245
kestrelAmplSolvers = self.getAvailableSolvers()
233-
self.options = None
246+
247+
NEOS_solver_name = None
248+
m = None
249+
234250
# Read kestrel_options to get solver name
235251
if "kestrel_options" in os.environ:
236252
self.options = os.getenv("kestrel_options")
237253
elif "KESTREL_OPTIONS" in os.environ:
238254
self.options = os.getenv("KESTREL_OPTIONS")
239-
#
255+
else:
256+
self.options = None
257+
240258
if self.options is not None:
241259
m = re.search(r'solver\s*=*\s*(\S+)', self.options, re.IGNORECASE)
242-
NEOS_solver_name = None
243260
if m:
244261
solver_name = m.groups()[0]
245262
for s in kestrelAmplSolvers:
246263
if s.upper() == solver_name.upper():
247264
NEOS_solver_name = s
248265
break
249-
#
250266
if not NEOS_solver_name:
251267
raise RuntimeError(
252268
"%s is not available on NEOS. Choose from:\n\t%s"
253269
% (solver_name, "\n\t".join(kestrelAmplSolvers))
254270
)
255-
#
256-
if self.options is None or m is None:
271+
272+
if NEOS_solver_name is None:
257273
raise RuntimeError(
258274
"%s is not available on NEOS. Choose from:\n\t%s"
259275
% (solver_name, "\n\t".join(kestrelAmplSolvers))
@@ -274,19 +290,16 @@ def formXML(self, stub):
274290
ampl_files = {}
275291
for key in ['adj', 'col', 'env', 'fix', 'spc', 'row', 'slc', 'unv']:
276292
if os.access(stub + "." + key, os.R_OK):
277-
f = open(stub + "." + key, "r")
278-
val = ""
279-
buf = f.read()
280-
while buf:
281-
val += buf
282-
buf = f.read()
283-
f.close()
284-
ampl_files[key] = val
293+
with open(stub + "." + key, "r") as f:
294+
ampl_files[key] = f.read()
295+
285296
# Get priority
286297
priority = ""
287-
m = re.search(r'priority[\s=]+(\S+)', self.options)
288-
if m:
289-
priority = "<priority>%s</priority>\n" % (m.groups()[0])
298+
if self.options:
299+
m = re.search(r'priority[\s=]+(\S+)', self.options)
300+
if m:
301+
priority = "<priority>%s</priority>\n" % (m.groups()[0])
302+
290303
# Add any AMPL-created environment variables to dictionary
291304
solver_options = "kestrel_options:solver=%s\n" % solver.lower()
292305
solver_options_key = "%s_options" % solver
@@ -298,7 +311,7 @@ def formXML(self, stub):
298311
solver_options_value = os.getenv(solver_options_key.lower())
299312
elif solver_options_key.upper() in os.environ:
300313
solver_options_value = os.getenv(solver_options_key.upper())
301-
if not solver_options_value == "":
314+
if solver_options_value:
302315
solver_options += "%s_options:%s\n" % (solver.lower(), solver_options_value)
303316
#
304317
nl_string = (base64.encodebytes(zipped_nl_file.getvalue())).decode('utf-8')

0 commit comments

Comments
 (0)