Skip to content

Commit 4a92411

Browse files
Merge branch 'main' into feature/3381_quadratic_obj_highs
2 parents 6d6c2d2 + 6e71dec commit 4a92411

File tree

7 files changed

+565
-127
lines changed

7 files changed

+565
-127
lines changed

Diff for: 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

Diff for: 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

Diff for: 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

Diff for: pyomo/common/log.py

+112-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,76 @@ 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 logger:
419+
for handler in logger.handlers:
420+
found += 1
421+
if not isinstance(handler, logging.StreamHandler):
422+
continue
423+
try:
424+
fd = handler.stream.fileno()
425+
except (AttributeError, OSError):
426+
fd = None
427+
if fd not in redirects:
428+
continue
429+
yield _StreamRedirector(handler, redirects[fd].original_fd)
430+
if not logger.propagate:
431+
break
432+
else:
433+
logger = logger.parent
434+
if not found:
435+
fd = logging.lastResort.stream.fileno()
436+
if not redirects:
437+
yield _LastResortRedirector(fd)
438+
elif fd in redirects:
439+
yield _LastResortRedirector(redirects[fd].original_fd)
440+
441+
442+
class _StreamRedirector(object):
443+
def __init__(self, handler, fd):
444+
self.handler = handler
445+
self.fd = fd
446+
self.orig_stream = None
447+
448+
def __enter__(self):
449+
self.orig_stream = self.handler.stream
450+
self.handler.stream = os.fdopen(
451+
os.dup(self.fd), mode="w", closefd=True
452+
).__enter__()
453+
454+
def __exit__(self, et, ev, tb):
455+
try:
456+
self.handler.stream.__exit__(et, ev, tb)
457+
finally:
458+
self.handler.stream = self.orig_stream
459+
460+
461+
class _LastResortRedirector(object):
462+
def __init__(self, fd):
463+
self.fd = fd
464+
self.orig_stream = None
465+
466+
def __enter__(self):
467+
self.orig = logging.lastResort
468+
logging.lastResort = logging.StreamHandler(
469+
os.fdopen(os.dup(self.fd), mode="w", closefd=True).__enter__()
470+
)
471+
472+
def __exit__(self, et, ev, tb):
473+
try:
474+
logging.lastResort.stream.close()
475+
finally:
476+
logging.lastResort = self.orig

0 commit comments

Comments
 (0)