57
57
58
58
logger = logging .getLogger (__name__ )
59
59
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
+
60
64
61
65
class IpoptConfig (SolverConfig ):
62
66
def __init__ (
@@ -551,7 +555,7 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
551
555
return parsed_data
552
556
553
557
# 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 )
555
559
if iter_match :
556
560
parsed_data ['iters' ] = int (iter_match .group (1 ))
557
561
# Gather all the iteration data
@@ -571,73 +575,71 @@ def _parse_ipopt_output(self, output: Union[str, io.StringIO]) -> Dict[str, Any]
571
575
]
572
576
all_iterations = []
573
577
578
+ iterations = 0
574
579
for line in iter_table :
575
580
tokens = line .strip ().split ()
576
581
if len (tokens ) == len (columns ):
577
582
iter_data = dict (zip (columns , tokens ))
578
583
579
584
# 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"
589
592
590
593
# 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 ]
598
597
else :
599
- iter_data [" step_acceptance" ] = None
598
+ iter_data [' step_acceptance' ] = None
600
599
601
600
# 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
+ )
612
614
613
615
all_iterations .append (iter_data )
616
+ iterations += 1
614
617
615
618
parsed_data ['iteration_log' ] = all_iterations
616
619
617
620
# Extract scaled and unscaled table
618
621
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.]+)' ,
624
627
output ,
625
628
re .DOTALL ,
626
629
)
627
630
if scaled_unscaled_match :
631
+ fields = [
632
+ "incumbent_objective" ,
633
+ "dual_infeasibility" ,
634
+ "constraint_violation" ,
635
+ "complementarity_error" ,
636
+ "overall_nlp_error" ,
637
+ ]
628
638
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 ])
634
640
}
635
641
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 ])
641
643
}
642
644
643
645
parsed_data .update (unscaled )
0 commit comments