Skip to content

Commit 2c610bb

Browse files
authored
Merge branch 'main' into pyros-discrete-coefficient-matching
2 parents 5317228 + 8f07dc9 commit 2c610bb

File tree

81 files changed

+2494
-1780
lines changed

Some content is hidden

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

81 files changed

+2494
-1780
lines changed

doc/OnlineDocs/explanation/solvers/gdpopt.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Three algorithms are available in GDPopt:
2020

2121
Usage and implementation details for GDPopt can be found in the PSE 2018 paper
2222
(`Chen et al., 2018`_), or via its
23-
`preprint <http://egon.cheme.cmu.edu/Papers/Chen_Pyomo_GDP_PSE2018.pdf>`_.
23+
`preprint <https://egon.cheme.cmu.edu/Papers/Chen_Pyomo_GDP_PSE2018.pdf>`_.
2424

2525
Credit for prototyping and development can be found in the ``GDPopt`` class documentation, below.
2626

doc/OnlineDocs/explanation/solvers/mindtpy.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ The following algorithms are currently available in MindtPy:
1717

1818
Usage and early implementation details for MindtPy can be found in the PSE 2018 paper Bernal et al.,
1919
(`ref <https://doi.org/10.1016/B978-0-444-64241-7.50144-0>`_,
20-
`preprint <http://egon.cheme.cmu.edu/Papers/Bernal_Chen_MindtPy_PSE2018Paper.pdf>`_).
20+
`preprint <https://egon.cheme.cmu.edu/Papers/Bernal_Chen_MindtPy_PSE2018Paper.pdf>`_).
2121
This solver implementation has been developed by `David Bernal <https://github.com/bernalde>`_
2222
and `Zedong Peng <https://github.com/ZedongPeng>`_ as part of research efforts at the `Bernal Research Group
23-
<https://bernalde.github.io/>`_ and the `Grossmann Research Group <http://egon.cheme.cmu.edu/>`_
23+
<https://bernalde.github.io/>`_ and the `Grossmann Research Group <https://egon.cheme.cmu.edu/>`_
2424
at Purdue University and Carnegie Mellon University.
2525

2626
.. _Duran & Grossmann, 1986: https://dx.doi.org/10.1007/BF02592064

doc/OnlineDocs/explanation/solvers/persistent.rst

+40-40
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ Using Persistent Solvers
2424
The first step in using a persistent solver is to create a Pyomo model
2525
as usual.
2626

27-
>>> import pyomo.environ as pe
28-
>>> m = pe.ConcreteModel()
29-
>>> m.x = pe.Var()
30-
>>> m.y = pe.Var()
31-
>>> m.obj = pe.Objective(expr=m.x**2 + m.y**2)
32-
>>> m.c = pe.Constraint(expr=m.y >= -2*m.x + 5)
27+
>>> import pyomo.environ as pyo
28+
>>> m = pyo.ConcreteModel()
29+
>>> m.x = pyo.Var()
30+
>>> m.y = pyo.Var()
31+
>>> m.obj = pyo.Objective(expr=m.x**2 + m.y**2)
32+
>>> m.c = pyo.Constraint(expr=m.y >= -2*m.x + 5)
3333

3434
You can create an instance of a persistent solver through the SolverFactory.
3535

36-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
36+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
3737

3838
This returns an instance of :py:class:`GurobiPersistent`. Now we need
3939
to tell the solver about our model.
@@ -48,7 +48,7 @@ variables and constraints. We can now solve the model.
4848
We can also add or remove variables, constraints, blocks, and
4949
objectives. For example,
5050

51-
>>> m.c2 = pe.Constraint(expr=m.y >= m.x) # doctest: +SKIP
51+
>>> m.c2 = pyo.Constraint(expr=m.y >= m.x) # doctest: +SKIP
5252
>>> opt.add_constraint(m.c2) # doctest: +SKIP
5353

5454
This tells the solver to add one new constraint but otherwise leave
@@ -69,29 +69,29 @@ code will run without error, but the solver will have an extra
6969
constraint. The solver will have both y >= -2*x + 5 and y <= x, which
7070
is not what was intended!
7171

72-
>>> m = pe.ConcreteModel() # doctest: +SKIP
73-
>>> m.x = pe.Var() # doctest: +SKIP
74-
>>> m.y = pe.Var() # doctest: +SKIP
75-
>>> m.c = pe.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
76-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
72+
>>> m = pyo.ConcreteModel() # doctest: +SKIP
73+
>>> m.x = pyo.Var() # doctest: +SKIP
74+
>>> m.y = pyo.Var() # doctest: +SKIP
75+
>>> m.c = pyo.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
76+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
7777
>>> opt.set_instance(m) # doctest: +SKIP
7878
>>> # WRONG:
7979
>>> del m.c # doctest: +SKIP
80-
>>> m.c = pe.Constraint(expr=m.y <= m.x) # doctest: +SKIP
80+
>>> m.c = pyo.Constraint(expr=m.y <= m.x) # doctest: +SKIP
8181
>>> opt.add_constraint(m.c) # doctest: +SKIP
8282

8383
The correct way to do this is:
8484

85-
>>> m = pe.ConcreteModel() # doctest: +SKIP
86-
>>> m.x = pe.Var() # doctest: +SKIP
87-
>>> m.y = pe.Var() # doctest: +SKIP
88-
>>> m.c = pe.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
89-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
85+
>>> m = pyo.ConcreteModel() # doctest: +SKIP
86+
>>> m.x = pyo.Var() # doctest: +SKIP
87+
>>> m.y = pyo.Var() # doctest: +SKIP
88+
>>> m.c = pyo.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
89+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
9090
>>> opt.set_instance(m) # doctest: +SKIP
9191
>>> # Correct:
9292
>>> opt.remove_constraint(m.c) # doctest: +SKIP
9393
>>> del m.c # doctest: +SKIP
94-
>>> m.c = pe.Constraint(expr=m.y <= m.x) # doctest: +SKIP
94+
>>> m.c = pyo.Constraint(expr=m.y <= m.x) # doctest: +SKIP
9595
>>> opt.add_constraint(m.c) # doctest: +SKIP
9696

9797
.. warning:: Components removed from a pyomo model must be removed
@@ -100,14 +100,14 @@ The correct way to do this is:
100100
Additionally, unexpected behavior may result if a component is
101101
modified before being removed.
102102

103-
>>> m = pe.ConcreteModel() # doctest: +SKIP
104-
>>> m.b = pe.Block() # doctest: +SKIP
105-
>>> m.b.x = pe.Var() # doctest: +SKIP
106-
>>> m.b.y = pe.Var() # doctest: +SKIP
107-
>>> m.b.c = pe.Constraint(expr=m.b.y >= -2*m.b.x + 5) # doctest: +SKIP
108-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
103+
>>> m = pyo.ConcreteModel() # doctest: +SKIP
104+
>>> m.b = pyo.Block() # doctest: +SKIP
105+
>>> m.b.x = pyo.Var() # doctest: +SKIP
106+
>>> m.b.y = pyo.Var() # doctest: +SKIP
107+
>>> m.b.c = pyo.Constraint(expr=m.b.y >= -2*m.b.x + 5) # doctest: +SKIP
108+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
109109
>>> opt.set_instance(m) # doctest: +SKIP
110-
>>> m.b.c2 = pe.Constraint(expr=m.b.y <= m.b.x) # doctest: +SKIP
110+
>>> m.b.c2 = pyo.Constraint(expr=m.b.y <= m.b.x) # doctest: +SKIP
111111
>>> # ERROR: The constraint referenced by m.b.c2 does not
112112
>>> # exist in the solver model.
113113
>>> opt.remove_block(m.b) # doctest: +SKIP
@@ -117,12 +117,12 @@ the solver instance, modify it with Pyomo, and then add it back to the
117117
solver instance. The only exception is with variables. Variables may
118118
be modified and then updated with with solver:
119119

120-
>>> m = pe.ConcreteModel() # doctest: +SKIP
121-
>>> m.x = pe.Var() # doctest: +SKIP
122-
>>> m.y = pe.Var() # doctest: +SKIP
123-
>>> m.obj = pe.Objective(expr=m.x**2 + m.y**2) # doctest: +SKIP
124-
>>> m.c = pe.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
125-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
120+
>>> m = pyo.ConcreteModel() # doctest: +SKIP
121+
>>> m.x = pyo.Var() # doctest: +SKIP
122+
>>> m.y = pyo.Var() # doctest: +SKIP
123+
>>> m.obj = pyo.Objective(expr=m.x**2 + m.y**2) # doctest: +SKIP
124+
>>> m.c = pyo.Constraint(expr=m.y >= -2*m.x + 5) # doctest: +SKIP
125+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
126126
>>> opt.set_instance(m) # doctest: +SKIP
127127
>>> m.x.setlb(1.0) # doctest: +SKIP
128128
>>> opt.update_var(m.x) # doctest: +SKIP
@@ -160,13 +160,13 @@ Persistent Solver Performance
160160
In order to get the best performance out of the persistent solvers, use the
161161
"save_results" flag:
162162

163-
>>> import pyomo.environ as pe
164-
>>> m = pe.ConcreteModel()
165-
>>> m.x = pe.Var()
166-
>>> m.y = pe.Var()
167-
>>> m.obj = pe.Objective(expr=m.x**2 + m.y**2)
168-
>>> m.c = pe.Constraint(expr=m.y >= -2*m.x + 5)
169-
>>> opt = pe.SolverFactory('gurobi_persistent') # doctest: +SKIP
163+
>>> import pyomo.environ as pyo
164+
>>> m = pyo.ConcreteModel()
165+
>>> m.x = pyo.Var()
166+
>>> m.y = pyo.Var()
167+
>>> m.obj = pyo.Objective(expr=m.x**2 + m.y**2)
168+
>>> m.c = pyo.Constraint(expr=m.y >= -2*m.x + 5)
169+
>>> opt = pyo.SolverFactory('gurobi_persistent') # doctest: +SKIP
170170
>>> opt.set_instance(m) # doctest: +SKIP
171171
>>> results = opt.solve(save_results=False) # doctest: +SKIP
172172

doc/OnlineDocs/explanation/solvers/pynumero/tutorial.nlp_interfaces.rst

+7-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Relevant imports
1010
.. doctest::
1111
:skipif: not numpy_available or not scipy_available or not asl_available
1212

13-
>>> import pyomo.environ as pe
13+
>>> import pyomo.environ as pyo
1414
>>> from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP
1515
>>> import numpy as np
1616

@@ -19,12 +19,12 @@ Create a Pyomo model
1919
.. doctest::
2020
:skipif: not numpy_available or not scipy_available or not asl_available
2121

22-
>>> m = pe.ConcreteModel()
23-
>>> m.x = pe.Var(bounds=(-5, None))
24-
>>> m.y = pe.Var(initialize=2.5)
25-
>>> m.obj = pe.Objective(expr=m.x**2 + m.y**2)
26-
>>> m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2)
27-
>>> m.c2 = pe.Constraint(expr=m.y >= pe.exp(m.x))
22+
>>> m = pyo.ConcreteModel()
23+
>>> m.x = pyo.Var(bounds=(-5, None))
24+
>>> m.y = pyo.Var(initialize=2.5)
25+
>>> m.obj = pyo.Objective(expr=m.x**2 + m.y**2)
26+
>>> m.c1 = pyo.Constraint(expr=m.y == (m.x - 1)**2)
27+
>>> m.c2 = pyo.Constraint(expr=m.y >= pyo.exp(m.x))
2828

2929
Create a :py:class:`pyomo.contrib.pynumero.interfaces.pyomo_nlp.PyomoNLP` instance
3030

examples/gdp/small_lit/ex1_Lee.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Taken from Example 1 of the paper "New Algorithms for Nonlinear Generalized Disjunctive Programming" by Lee and Grossmann
1414
1515
This GDP problem has one disjunction containing three terms, and exactly one of them must be true. The literature can be found here:
16-
http://egon.cheme.cmu.edu/Papers/LeeNewAlgo.pdf
16+
https://egon.cheme.cmu.edu/Papers/LeeNewAlgo.pdf
1717
1818
"""
1919

pyomo/common/log.py

+114-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import inspect
2222
import io
2323
import logging
24+
import os
2425
import re
2526
import sys
2627
import textwrap
@@ -286,14 +287,22 @@ class LoggingIntercept(object):
286287
----------
287288
output: io.TextIOBase
288289
the file stream to send log messages to
290+
289291
module: str
290-
the target logger name to intercept
292+
the target logger name to intercept. `logger` and `module` are
293+
mutually exclusive.
294+
291295
level: int
292296
the logging level to intercept
297+
293298
formatter: logging.Formatter
294299
the formatter to use when rendering the log messages. If not
295300
specified, uses `'%(message)s'`
296301
302+
logger: logging.Logger
303+
the target logger to intercept. `logger` and `module` are
304+
mutually exclusive.
305+
297306
Examples
298307
--------
299308
>>> import io, logging
@@ -306,17 +315,36 @@ class LoggingIntercept(object):
306315
307316
"""
308317

309-
def __init__(self, output=None, module=None, level=logging.WARNING, formatter=None):
318+
def __init__(
319+
self,
320+
output=None,
321+
module=None,
322+
level=logging.WARNING,
323+
formatter=None,
324+
logger=None,
325+
):
310326
self.handler = None
311327
self.output = output
312-
self.module = module
328+
if logger is not None:
329+
if module is not None:
330+
raise ValueError(
331+
"LoggingIntercept: only one of 'module' and 'logger' is allowed"
332+
)
333+
self._logger = logger
334+
else:
335+
self._logger = logging.getLogger(module)
313336
self._level = level
314337
if formatter is None:
315338
formatter = logging.Formatter('%(message)s')
316339
self._formatter = formatter
317340
self._save = None
318341

319342
def __enter__(self):
343+
# Get the logger for the scope we will be overriding
344+
logger = self._logger
345+
self._save = logger.level, logger.propagate, logger.handlers
346+
if self._level is None:
347+
self._level = logger.getEffectiveLevel()
320348
# Set up the handler
321349
output = self.output
322350
if output is None:
@@ -326,23 +354,25 @@ def __enter__(self):
326354
self.handler.setFormatter(self._formatter)
327355
self.handler.setLevel(self._level)
328356
# Register the handler with the appropriate module scope
329-
logger = logging.getLogger(self.module)
330-
self._save = logger.level, logger.propagate, logger.handlers
331357
logger.handlers = []
332-
logger.propagate = 0
358+
logger.propagate = False
333359
logger.setLevel(self.handler.level)
334360
logger.addHandler(self.handler)
335361
return output
336362

337363
def __exit__(self, et, ev, tb):
338-
logger = logging.getLogger(self.module)
364+
logger = self._logger
339365
logger.removeHandler(self.handler)
340366
self.handler = None
341367
logger.setLevel(self._save[0])
342368
logger.propagate = self._save[1]
343369
assert not logger.handlers
344370
logger.handlers.extend(self._save[2])
345371

372+
@property
373+
def module(self):
374+
return self._logger.name
375+
346376

347377
class LogStream(io.TextIOBase):
348378
"""
@@ -357,6 +387,8 @@ def __init__(self, level, logger):
357387
self._buffer = ''
358388

359389
def write(self, s: str) -> int:
390+
if not s:
391+
return 0
360392
res = len(s)
361393
if self._buffer:
362394
s = self._buffer + s
@@ -369,3 +401,78 @@ def write(self, s: str) -> int:
369401
def flush(self):
370402
if self._buffer:
371403
self.write('\n')
404+
405+
def redirect_streams(self, redirects):
406+
"""Redirect StreamHandler objects to the original file descriptors
407+
408+
This utility method for py:class:`~pyomo.common.tee.capture_output`
409+
locates any StreamHandlers that would process messages from the
410+
logger assigned to this :py:class:`LogStream` that would write
411+
to the file descriptors redirected by `capture_output` and
412+
yields context managers that will redirect those StreamHandlers
413+
back to duplicates of the original file descriptors.
414+
415+
"""
416+
found = 0
417+
logger = self._logger
418+
while isinstance(logger, logging.LoggerAdapter):
419+
logger = logger.logger
420+
while logger:
421+
for handler in logger.handlers:
422+
found += 1
423+
if not isinstance(handler, logging.StreamHandler):
424+
continue
425+
try:
426+
fd = handler.stream.fileno()
427+
except (AttributeError, OSError):
428+
fd = None
429+
if fd not in redirects:
430+
continue
431+
yield _StreamRedirector(handler, redirects[fd].original_fd)
432+
if not logger.propagate:
433+
break
434+
else:
435+
logger = logger.parent
436+
if not found:
437+
fd = logging.lastResort.stream.fileno()
438+
if not redirects:
439+
yield _LastResortRedirector(fd)
440+
elif fd in redirects:
441+
yield _LastResortRedirector(redirects[fd].original_fd)
442+
443+
444+
class _StreamRedirector(object):
445+
def __init__(self, handler, fd):
446+
self.handler = handler
447+
self.fd = fd
448+
self.orig_stream = None
449+
450+
def __enter__(self):
451+
self.orig_stream = self.handler.stream
452+
self.handler.stream = os.fdopen(
453+
os.dup(self.fd), mode="w", closefd=True
454+
).__enter__()
455+
456+
def __exit__(self, et, ev, tb):
457+
try:
458+
self.handler.stream.__exit__(et, ev, tb)
459+
finally:
460+
self.handler.stream = self.orig_stream
461+
462+
463+
class _LastResortRedirector(object):
464+
def __init__(self, fd):
465+
self.fd = fd
466+
self.orig_stream = None
467+
468+
def __enter__(self):
469+
self.orig = logging.lastResort
470+
logging.lastResort = logging.StreamHandler(
471+
os.fdopen(os.dup(self.fd), mode="w", closefd=True).__enter__()
472+
)
473+
474+
def __exit__(self, et, ev, tb):
475+
try:
476+
logging.lastResort.stream.close()
477+
finally:
478+
logging.lastResort = self.orig

0 commit comments

Comments
 (0)