44import subprocess # nosec: B404
55import sys
66import time
7- from typing import List , Optional , Tuple
7+ from typing import Dict , List , Optional , Tuple
88
99from unified_planning .engines .results import PlanGenerationResultStatus as Status
1010from unified_planning .io .pddl_reader import PDDLReader
3939 "blink" : 5 ,
4040 "invert" : 7 ,
4141}
42+ SLOW_THRESHOLD = 5
4243VALID_STATUS = {Status .SOLVED_SATISFICING , Status .SOLVED_OPTIMALLY }
4344
4445IS_GITHUB_ACTIONS = os .getenv ("GITHUB_ACTIONS" ) == "true"
@@ -57,7 +58,14 @@ def write(text: str = "", **markup: bool) -> None:
5758 esc = [ESC_TABLE [name ] for name , value in markup .items () if value ]
5859 if esc :
5960 text = "" .join (f"\x1b [{ cod } m" for cod in esc ) + text + "\x1b [0m"
60- print (text )
61+ print (text , end = "" )
62+
63+
64+ def write_line (text : str = "" , ** markup : bool ) -> None :
65+ """Write a line of text with ANSI escape sequences for formatting."""
66+ write (text , ** markup )
67+ if not text .endswith ("\n " ):
68+ print ()
6169
6270
6371# pylint: disable=too-many-arguments
@@ -80,8 +88,8 @@ def separator(
8088 if len (line ) + len (sep .rstrip ()) <= width :
8189 line += sep .rstrip ()
8290 if github_group and IS_GITHUB_ACTIONS :
83- write (f"::group::{ title } " )
84- write (f"{ before } { line } { after } " , ** markup )
91+ write_line (f"::group::{ title } " )
92+ write_line (f"{ before } { line } { after } " , ** markup )
8593
8694
8795def big_separator (
@@ -180,11 +188,22 @@ def validate_plan_with_val(pb: Path, dom: Path, plan: Path) -> bool:
180188if len (sys .argv ) > 1 :
181189 problems = [pb for pb in problems if pb .stem in sys .argv [1 :]]
182190
191+ last_results : Dict [str , Tuple [str , float ]] = {}
192+ last_results_file = Path ("/tmp/aries_ci_ipc_last_best_results.csv" ) # nosec: B108
193+ if last_results_file .exists ():
194+ lines = last_results_file .read_text (encoding = "utf-8" ).splitlines ()
195+ for _line in lines :
196+ pb , s , t = _line .split ("," )
197+ last_results [pb ] = (s , float (t ))
198+
183199valid : List [str ] = []
184200invalid : List [str ] = []
185201unsolved : List [Tuple [str , str ]] = []
186202update_depth : List [str ] = []
187203
204+ timing : Dict [str , float ] = {}
205+ status : Dict [str , str ] = {}
206+
188207try :
189208 for i , folder in enumerate (problems ):
190209 separator (
@@ -194,7 +213,7 @@ def validate_plan_with_val(pb: Path, dom: Path, plan: Path) -> bool:
194213 blue = True ,
195214 )
196215
197- out_file = f"/tmp/aries-{ folder .stem } .log"
216+ out_file = f"/tmp/aries-{ folder .stem } .log" # nosec: B108
198217
199218 try :
200219 domain = folder / "domain.pddl"
@@ -207,32 +226,33 @@ def validate_plan_with_val(pb: Path, dom: Path, plan: Path) -> bool:
207226 max_depth = 100 # pylint: disable=invalid-name
208227
209228 with open (out_file , mode = "w+" , encoding = "utf-8" ) as output :
210- write (f"Output log: { output .name } \n " )
229+ write_line (f"Output log: { output .name } \n " )
211230
212231 with OneshotPlanner (
213232 name = "aries" ,
214233 params = {"max-depth" : max_depth },
215234 ) as planner :
216235 # Problem Kind
217- write ("Problem Kind" , cyan = True )
218- write (str (upf_pb .kind ), light = True )
219- write ()
236+ write_line ("Problem Kind" , cyan = True )
237+ write_line (str (upf_pb .kind ), light = True )
238+ write_line ()
220239
221240 # Unsupported features
222241 unsupported = upf_pb .kind .features .difference (
223242 planner .supported_kind ().features
224243 )
225244 if unsupported :
226- write ("Unsupported Features" , cyan = True )
227- write ("\n " .join (unsupported ), light = True )
228- write ()
245+ write_line ("Unsupported Features" , cyan = True )
246+ write_line ("\n " .join (unsupported ), light = True )
247+ write_line ()
229248
230249 # Resolution
231250 start = time .time ()
232251 result = planner .solve (upf_pb , output_stream = output )
233- write ("Resolution status" , cyan = True )
234- write (f"Elapsed time: { time .time () - start :.3f} s" , light = True )
235- write (f"Status: { result .status } " , light = True )
252+ write_line ("Resolution status" , cyan = True )
253+ timing [folder .stem ] = time .time () - start
254+ write_line (f"Elapsed time: { timing [folder .stem ]:.3f} s" , light = True )
255+ write_line (f"Status: { result .status } " , light = True )
236256
237257 # Update max depth
238258 if not has_max_depth :
@@ -243,57 +263,96 @@ def validate_plan_with_val(pb: Path, dom: Path, plan: Path) -> bool:
243263
244264 # Solved problem
245265 if result .status in VALID_STATUS :
246- write ("\n Validating plan by Val" , cyan = True )
266+ write_line ("\n Validating plan by Val" , cyan = True )
247267 out_path = Path (output .name )
248268 extract_plan_for_val (result .plan , domain , problem , out_path )
249269 if validate_plan_with_val (problem , domain , out_path ):
250- write ("Plan is valid" , bold = True , green = True )
270+ write_line ("Plan is valid" , bold = True , green = True )
251271 valid .append (folder .stem )
272+ status [folder .stem ] = "valid"
252273 else :
253- write ("Plan is invalid" , bold = True , red = True )
274+ write_line ("Plan is invalid" , bold = True , red = True )
254275 invalid .append (folder .stem )
276+ status [folder .stem ] = "invalid"
255277
256278 # Unsolved problem
257279 else :
280+ write_line (
281+ Path (out_file ).read_text (encoding = "utf-8" ), yellow = True
282+ )
258283 unsolved .append ((folder .stem , result .status .name ))
259- write ("Unsolved problem" , bold = True , red = True )
284+ status [folder .stem ] = result .status .name .lower ()
285+ write_line ("Unsolved problem" , bold = True , red = True )
260286
261287 except Exception as e : # pylint: disable=broad-except
262288 unsolved .append ((folder .stem , "Error" ))
263- write (f"Error: { e } " , bold = True , red = True )
264- write ()
265- write (Path (out_file ).read_text (encoding = "utf-8" ), yellow = True )
289+ status [folder .stem ] = "error"
290+ write_line (f"Error: { e } " , bold = True , red = True )
291+ write_line ()
292+ write_line (Path (out_file ).read_text (encoding = "utf-8" ), yellow = True )
266293
267294 finally :
268295 if IS_GITHUB_ACTIONS :
269- write ("::endgroup::" )
296+ write_line ("::endgroup::" )
270297
271298except KeyboardInterrupt :
272299 pass
273300finally :
274301 # Summary
275302 separator (title = "Summary" , bold = True , blue = True )
276303
304+ csv_data = "" # pylint: disable=invalid-name
305+ for folder in problems :
306+ t = min ( # type: ignore
307+ timing [folder .stem ],
308+ (
309+ last_results [folder .stem ][1 ]
310+ if folder .stem in last_results
311+ else float ("inf" )
312+ ),
313+ )
314+ csv_data += f"{ folder .stem } ,{ status [folder .stem ]} ,{ t } \n "
315+ last_results_file .write_text (csv_data , encoding = "utf-8" )
316+
277317 if valid :
278318 big_separator (title = f"Valid: { len (valid )} " , bold = True , green = True )
319+ offset = max (map (len , valid )) + 3
279320 for res in valid :
280- print (res )
321+ time_str = f" { last_results [res ][1 ]:.3f} -> " if res in last_results else ""
322+ time_str += f"{ timing [res ]:.3f} "
323+ write (f"{ res :<{offset }} " )
324+ if res in last_results and timing [res ] < last_results [res ][1 ]:
325+ write_line (time_str , bold = True , green = True )
326+ elif (
327+ res in last_results
328+ and timing [res ] - last_results [res ][1 ] > SLOW_THRESHOLD
329+ ):
330+ write_line (time_str , red = True )
331+ else :
332+ write_line (time_str )
333+
334+ slow = list (filter (lambda t : t [1 ] > SLOW_THRESHOLD , timing .items ()))
335+ if slow :
336+ big_separator (title = f"Slow: { len (slow )} " , bold = True , yellow = True )
337+ offset = max (map (len , map (lambda t : t [0 ], slow ))) + 3
338+ for res , t in sorted (slow , key = lambda t : t [1 ], reverse = True ): # type: ignore
339+ write_line (f"{ res :<{offset }} { t :.3f} " )
281340
282341 if invalid :
283342 big_separator (title = f"Invalid: { len (invalid )} " , bold = True , red = True )
284343 for res in invalid :
285- print (res )
344+ write_line (res )
286345
287346 if unsolved :
288347 big_separator (title = f"Unsolved: { len (unsolved )} " , bold = True , red = True )
289348 offset = max (map (len , map (lambda t : t [0 ], unsolved ))) + 3
290349 for res , reason in unsolved :
291- print (f"{ res :<{offset }} { reason } " )
350+ write_line (f"{ res :<{offset }} { reason } " )
292351
293352 if update_depth :
294353 big_separator (title = f"Updated depth: { len (update_depth )} " , bold = True )
295354 for res in update_depth :
296- print (res )
355+ write_line (res )
297356
298357 EXIT_CODE = 0 if not invalid and not unsolved else 1
299358 big_separator (title = f"End with code { EXIT_CODE } " , bold = True )
0 commit comments