Skip to content

Commit 404fd6d

Browse files
authored
Merge pull request #3335 from jsiirola/freebsd-fixes
Omnibus testing / platform portability fixes
2 parents 7f779ab + 658dab3 commit 404fd6d

File tree

22 files changed

+360
-121
lines changed

22 files changed

+360
-121
lines changed

pyomo/common/fileutils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,17 @@ def find_dir(
286286
)
287287

288288

289-
_exeExt = {'linux': None, 'windows': '.exe', 'cygwin': '.exe', 'darwin': None}
289+
_exeExt = {
290+
'linux': None,
291+
'freebsd': None,
292+
'windows': '.exe',
293+
'cygwin': '.exe',
294+
'darwin': None,
295+
}
290296

291297
_libExt = {
292298
'linux': ('.so', '.so.*'),
299+
'freebsd': ('.so', '.so.*'),
293300
'windows': ('.dll', '.pyd'),
294301
'cygwin': ('.dll', '.so', '.so.*'),
295302
'darwin': ('.dylib', '.so', '.so.*'),

pyomo/common/tests/test_download.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import pyomo.common.envvar as envvar
2323

2424
from pyomo.common import DeveloperError
25-
from pyomo.common.fileutils import this_file
25+
from pyomo.common.fileutils import this_file, Executable
2626
from pyomo.common.download import FileDownloader, distro_available
2727
from pyomo.common.log import LoggingIntercept
2828
from pyomo.common.tee import capture_output
@@ -173,7 +173,8 @@ def test_get_os_version(self):
173173
self.assertTrue(v.replace('.', '').startswith(dist_ver))
174174

175175
if (
176-
subprocess.run(
176+
Executable('lsb_release').available()
177+
and subprocess.run(
177178
['lsb_release'],
178179
stdout=subprocess.DEVNULL,
179180
stderr=subprocess.DEVNULL,
@@ -206,7 +207,7 @@ def test_get_os_version(self):
206207
self.assertEqual(_os, 'win')
207208
self.assertEqual(_norm, _os + ''.join(_ver.split('.')[:2]))
208209
else:
209-
self.assertEqual(ans, '')
210+
self.assertEqual(_os, '')
210211

211212
self.assertEqual((_os, _ver), FileDownloader._os_version)
212213
# Exercise the fetch from CACHE

pyomo/common/unittest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ def filter_file_contents(self, lines, abstol=None):
858858

859859
return filtered
860860

861-
def compare_baseline(self, test_output, baseline, abstol=1e-6, reltol=None):
861+
def compare_baseline(self, test_output, baseline, abstol=1e-6, reltol=1e-8):
862862
# Filter files independently and then compare filtered contents
863863
out_filtered = self.filter_file_contents(
864864
test_output.strip().split('\n'), abstol

pyomo/contrib/appsi/solvers/ipopt.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,24 @@ def get_reduced_costs(
567567
return ComponentMap((k, v) for k, v in self._reduced_costs.items())
568568
else:
569569
return ComponentMap((v, self._reduced_costs[v]) for v in vars_to_load)
570+
571+
def has_linear_solver(self, linear_solver):
572+
import pyomo.core as AML
573+
from pyomo.common.tee import capture_output
574+
575+
m = AML.ConcreteModel()
576+
m.x = AML.Var()
577+
m.o = AML.Objective(expr=(m.x - 2) ** 2)
578+
with capture_output() as OUT:
579+
solver = self.__class__()
580+
solver.config.stream_solver = True
581+
solver.config.load_solution = False
582+
solver.ipopt_options['linear_solver'] = linear_solver
583+
try:
584+
solver.solve(m)
585+
except FileNotFoundError:
586+
# The APPSI interface always tries to open the SOL file,
587+
# and will generate a FileNotFoundError if ipopt didn't
588+
# generate one
589+
return False
590+
return 'running with linear solver' in OUT.getvalue()
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# ___________________________________________________________________________
2+
#
3+
# Pyomo: Python Optimization Modeling Objects
4+
# Copyright (c) 2008-2024
5+
# National Technology and Engineering Solutions of Sandia, LLC
6+
# Under the terms of Contract DE-NA0003525 with National Technology and
7+
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
8+
# rights in this software.
9+
# This software is distributed under the 3-clause BSD License.
10+
# ___________________________________________________________________________
11+
12+
from pyomo.common import unittest
13+
from pyomo.contrib.appsi.solvers import ipopt
14+
15+
16+
ipopt_available = ipopt.Ipopt().available()
17+
18+
19+
@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
20+
class TestIpoptInterface(unittest.TestCase):
21+
def test_has_linear_solver(self):
22+
opt = ipopt.Ipopt()
23+
self.assertTrue(
24+
any(
25+
map(
26+
opt.has_linear_solver,
27+
[
28+
'mumps',
29+
'ma27',
30+
'ma57',
31+
'ma77',
32+
'ma86',
33+
'ma97',
34+
'pardiso',
35+
'pardisomkl',
36+
'spral',
37+
'wsmp',
38+
],
39+
)
40+
)
41+
)
42+
self.assertFalse(opt.has_linear_solver('bogus_linear_solver'))

pyomo/contrib/doe/doe.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def __init__(
9797
A Python ``function`` that returns a Concrete Pyomo model, similar to the interface for ``parmest``
9898
solver:
9999
A ``solver`` object that User specified, default=None.
100-
If not specified, default solver is IPOPT MA57.
100+
If not specified, default solver is IPOPT (with MA57, if available).
101101
prior_FIM:
102102
A 2D numpy array containing Fisher information matrix (FIM) for prior experiments.
103103
The default None means there is no prior information.
@@ -995,7 +995,7 @@ def run_grid_search(
995995
)
996996
count += 1
997997
failed_count += 1
998-
self.logger.warning("failed count:", failed_count)
998+
self.logger.warning("failed count: %s", failed_count)
999999
result_combine[tuple(design_set_iter)] = None
10001000

10011001
# For user's access
@@ -1387,7 +1387,10 @@ def _fix_design(self, m, design_val, fix_opt=True, optimize_option=None):
13871387
def _get_default_ipopt_solver(self):
13881388
"""Default solver"""
13891389
solver = SolverFactory("ipopt")
1390-
solver.options["linear_solver"] = "ma57"
1390+
for linear_solver in ('ma57', 'ma27', 'ma97'):
1391+
if solver.has_linear_solver(linear_solver):
1392+
solver.options["linear_solver"] = linear_solver
1393+
break
13911394
solver.options["halt_on_ampl_error"] = "yes"
13921395
solver.options["max_iter"] = 3000
13931396
return solver

pyomo/contrib/doe/tests/test_example.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from pyomo.opt import SolverFactory
4040

4141
ipopt_available = SolverFactory("ipopt").available()
42+
k_aug_available = SolverFactory("k_aug").available(exception_flag=False)
4243

4344

4445
class TestReactorExamples(unittest.TestCase):
@@ -57,6 +58,7 @@ def test_reactor_optimize_doe(self):
5758

5859
reactor_optimize_doe.main()
5960

61+
@unittest.skipIf(not k_aug_available, "The 'k_aug' command is not available")
6062
@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
6163
@unittest.skipIf(not pandas_available, "pandas is not available")
6264
@unittest.skipIf(not numpy_available, "Numpy is not available")

pyomo/contrib/doe/tests/test_fim_doe.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
VariablesWithIndices,
3636
)
3737
from pyomo.contrib.doe.examples.reactor_kinetics import create_model, disc_for_measure
38+
from pyomo.environ import SolverFactory
39+
40+
ipopt_available = SolverFactory("ipopt").available()
3841

3942

4043
class TestMeasurementError(unittest.TestCase):
@@ -196,6 +199,7 @@ def test(self):
196199

197200

198201
@unittest.skipIf(not numpy_available, "Numpy is not available")
202+
@unittest.skipIf(not ipopt_available, "ipopt is not available")
199203
class TestPriorFIMError(unittest.TestCase):
200204
def test(self):
201205
# Control time set [h]

pyomo/contrib/doe/tests/test_reactor_example.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from pyomo.opt import SolverFactory
3636

3737
ipopt_available = SolverFactory("ipopt").available()
38+
k_aug_available = SolverFactory("k_aug").available(exception_flag=False)
3839

3940

4041
class Test_Reaction_Kinetics_Example(unittest.TestCase):
@@ -133,6 +134,7 @@ def test_kinetics_example_sequential_finite_then_optimize(self):
133134
# self.assertAlmostEqual(value(optimize_result.model.T[0.5]), 300, places=2)
134135
self.assertAlmostEqual(np.log10(optimize_result.trace), 3.340, places=2)
135136

137+
@unittest.skipIf(not k_aug_available, "The 'k_aug' solver is not available")
136138
@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available")
137139
@unittest.skipIf(not numpy_available, "Numpy is not available")
138140
@unittest.skipIf(not pandas_available, "Pandas is not available")

pyomo/contrib/parmest/tests/test_examples.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
import pyomo.common.unittest as unittest
1313
import pyomo.contrib.parmest.parmest as parmest
1414
from pyomo.contrib.parmest.graphics import matplotlib_available, seaborn_available
15+
from pyomo.contrib.pynumero.asl import AmplInterface
1516
from pyomo.opt import SolverFactory
1617

1718
ipopt_available = SolverFactory("ipopt").available()
19+
pynumero_ASL_available = AmplInterface.available()
1820

1921

2022
@unittest.skipIf(
@@ -43,6 +45,7 @@ def test_model_with_constraint(self):
4345

4446
rooney_biegler_with_constraint.main()
4547

48+
@unittest.skipUnless(pynumero_ASL_available, "test requires libpynumero_ASL")
4649
@unittest.skipUnless(seaborn_available, "test requires seaborn")
4750
def test_parameter_estimation_example(self):
4851
from pyomo.contrib.parmest.examples.rooney_biegler import (
@@ -66,11 +69,11 @@ def test_likelihood_ratio_example(self):
6669
likelihood_ratio_example.main()
6770

6871

69-
@unittest.skipIf(
70-
not parmest.parmest_available,
71-
"Cannot test parmest: required dependencies are missing",
72+
@unittest.skipUnless(pynumero_ASL_available, "test requires libpynumero_ASL")
73+
@unittest.skipUnless(ipopt_available, "The 'ipopt' solver is not available")
74+
@unittest.skipUnless(
75+
parmest.parmest_available, "Cannot test parmest: required dependencies are missing"
7276
)
73-
@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available")
7477
class TestReactionKineticsExamples(unittest.TestCase):
7578
@classmethod
7679
def setUpClass(self):
@@ -140,6 +143,7 @@ def test_model(self):
140143

141144
reactor_design.main()
142145

146+
@unittest.skipUnless(pynumero_ASL_available, "test requires libpynumero_ASL")
143147
def test_parameter_estimation_example(self):
144148
from pyomo.contrib.parmest.examples.reactor_design import (
145149
parameter_estimation_example,

0 commit comments

Comments
 (0)