Skip to content

Commit aa89364

Browse files
committed
Address parsing changes from jsiirola
1 parent 38d324e commit aa89364

File tree

1 file changed

+45
-43
lines changed

1 file changed

+45
-43
lines changed

pyomo/contrib/solver/solvers/ipopt.py

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757

5858
logger = logging.getLogger(__name__)
5959

60+
# Acceptable chars for the end of the alpha_pr column
61+
# in ipopt's output, per https://coin-or.github.io/Ipopt/OUTPUT.html
62+
_ALPHA_PR_CHARS = set("fFhHkKnNRwstTr")
63+
6064

6165
class IpoptConfig(SolverConfig):
6266
def __init__(
@@ -551,7 +555,7 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
551555
return parsed_data
552556

553557
# Extract number of iterations
554-
iter_match = re.search(r'Number of Iterations\.\.\.\.:\s+(\d+)', output)
558+
iter_match = re.search(r'Number of Iterations.*:\s+(\d+)', output)
555559
if iter_match:
556560
parsed_data['iters'] = int(iter_match.group(1))
557561
# Gather all the iteration data
@@ -571,73 +575,71 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
571575
]
572576
all_iterations = []
573577

578+
iterations = 0
574579
for line in iter_table:
575580
tokens = line.strip().split()
576581
if len(tokens) == len(columns):
577582
iter_data = dict(zip(columns, tokens))
578583

579584
# Extract restoration flag from 'iter'
580-
iter_val = iter_data["iter"]
581-
iter_match = re.match(r"(\d+)(r?)", iter_val)
582-
if iter_match:
583-
iter_data["restoration"] = bool(
584-
iter_match.group(2)
585-
) # True if 'r' is present
586-
else:
587-
iter_data["restoration"] = False
588-
iter_data.pop('iter')
585+
iter_data['restoration'] = iter_data['iter'].endswith('r')
586+
if iter_data['restoration']:
587+
iter_data['iter'] = iter_data['iter'][:-1]
588+
assert str(iterations) == iter_data.pop(
589+
'iter'
590+
), f"Number of iterations ({iterations}) does not match the "
591+
"parsed row in the iterations table"
589592

590593
# Separate alpha_pr into numeric part and optional tag
591-
alpha_pr_val = iter_data["alpha_pr"]
592-
match = re.match(r"([0-9.eE+-]+)([a-zA-Z]?)", alpha_pr_val)
593-
if match:
594-
iter_data["alpha_pr"] = match.group(1)
595-
iter_data["step_acceptance"] = (
596-
match.group(2) if match.group(2) else None
597-
)
594+
iter_data['step_acceptance'] = iter_data['alpha_pr'][-1]
595+
if iter_data['step_acceptance'] in _ALPHA_PR_CHARS:
596+
iter_data['alpha_pr'] = iter_data['alpha_pr'][:-1]
598597
else:
599-
iter_data["step_acceptance"] = None
598+
iter_data['step_acceptance'] = None
600599

601600
# Attempt to cast all values to float where possible
602-
for key in iter_data:
603-
try:
604-
if iter_data[key] == '-':
605-
iter_data[key] = None
606-
continue
607-
if isinstance(iter_data[key], bool):
608-
continue
609-
iter_data[key] = float(iter_data[key])
610-
except (ValueError, TypeError):
611-
pass
601+
for key in columns:
602+
if key == 'iter':
603+
continue
604+
if iter_data[key] == '-':
605+
iter_data[key] = None
606+
else:
607+
try:
608+
iter_data[key] = float(iter_data[key])
609+
except (ValueError, TypeError):
610+
logger.warning(
611+
"Error converting Ipopt log entry to "
612+
f"float:\n\t{sys.exc_info()[1]}\n\t{line}"
613+
)
612614

613615
all_iterations.append(iter_data)
616+
iterations += 1
614617

615618
parsed_data['iteration_log'] = all_iterations
616619

617620
# Extract scaled and unscaled table
618621
scaled_unscaled_match = re.findall(
619-
r'Objective\.\.+:\s+([-+eE0-9.]+)\s+([-+eE0-9.]+).*?'
620-
r'Dual infeasibility\.\.+:\s+([-+eE0-9.]+)\s+([-+eE0-9.]+).*?'
621-
r'Constraint violation\.\.+:\s+([-+eE0-9.]+)\s+([-+eE0-9.]+).*?'
622-
r'Complementarity\.\.+:\s+([-+eE0-9.]+)\s+([-+eE0-9.]+).*?'
623-
r'Overall NLP error\.\.+:\s+([-+eE0-9.]+)\s+([-+eE0-9.]+)',
622+
r'Objective\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
623+
r'Dual infeasibility\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
624+
r'Constraint violation\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
625+
r'Complementarity\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)\s*'
626+
r'Overall NLP error\.*:\s*([-+eE0-9.]+)\s+([-+eE0-9.]+)',
624627
output,
625628
re.DOTALL,
626629
)
627630
if scaled_unscaled_match:
631+
fields = [
632+
"incumbent_objective",
633+
"dual_infeasibility",
634+
"constraint_violation",
635+
"complementarity_error",
636+
"overall_nlp_error",
637+
]
628638
scaled = {
629-
"incumbent_objective": float(scaled_unscaled_match[0][0]),
630-
"dual_infeasibility": float(scaled_unscaled_match[0][2]),
631-
"constraint_violation": float(scaled_unscaled_match[0][4]),
632-
"complementarity_error": float(scaled_unscaled_match[0][6]),
633-
"overall_nlp_error": float(scaled_unscaled_match[0][8]),
639+
k: float(v) for k, v in zip(fields, scaled_unscaled_match[0][0:10:2])
634640
}
635641
unscaled = {
636-
"incumbent_objective": float(scaled_unscaled_match[0][1]),
637-
"dual_infeasibility": float(scaled_unscaled_match[0][3]),
638-
"constraint_violation": float(scaled_unscaled_match[0][5]),
639-
"complementarity": float(scaled_unscaled_match[0][7]),
640-
"overall_nlp_error": float(scaled_unscaled_match[0][9]),
642+
k: float(v) for k, v in zip(fields, scaled_unscaled_match[0][1:10:2])
641643
}
642644

643645
parsed_data.update(unscaled)

0 commit comments

Comments
 (0)