Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pysagas/optimisation/cart3d/cart3d.py: 0%
332 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-30 04:27 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-10-30 04:27 +0000
1import os
2import time
3import glob
4import pickle
5import shutil
6import subprocess
7import numpy as np
8import pandas as pd
9import multiprocess as mp
10from pysagas.flow import FlowState
11from pysagas.cfd.cart3d import Cart3D
12from hypervehicle.generator import Generator
13from pyoptsparse import Optimizer, Optimization
14from pysagas.optimisation.optimiser import ShapeOpt
15from hypervehicle.utilities import SensitivityStudy
16from pysagas.sensitivity.cart3d import Cart3DSensitivityCalculator
17from pysagas.optimisation.cart3d.utilities import C3DPrep, combine_sense_data
20np.seterr(all="ignore")
23class Cart3DShapeOpt(ShapeOpt):
24 """A wrapper to perform shape optimisation with Cart3D."""
26 C3D_errors = [
27 "==> ADAPT failed",
28 "Check cart3d.out in AD_A_J for more clues",
29 "==> adjointErrorEst_quad failed again, status = 1",
30 "ERROR: CUBES failed",
31 "ERROR: ADAPT failed with status = 1",
32 "ERROR",
33 ]
35 def __init__(
36 self,
37 freestream: FlowState,
38 optimiser: Optimizer,
39 vehicle_generator: Generator,
40 objective_callback: callable,
41 jacobian_callback: callable,
42 A_ref: float = 1.0,
43 l_ref: float = 1.0,
44 optimiser_options: dict = None,
45 sensitivity_filename: str = "all_components_sensitivity.csv",
46 working_dir_name: str = "working_dir",
47 sim_directory_name: str = "simulation",
48 basefiles_dir_name: str = "basefiles",
49 c3d_log_name: str = "C3D_log",
50 c3d_info_file: str = None,
51 matching_tolerance: float = 1e-5,
52 save_evolution: bool = True,
53 write_config_xml: bool = True,
54 sensitivity_kwargs: dict = None,
55 ) -> None:
56 """Initialise Cart3D Shape Optimiser.
58 Parameters
59 ----------
60 a_inf : float
61 The freestream speed of sound (m/s).
63 rho_inf : float
64 The freestream density (kg/m^3).
66 V_inf : float
67 The freestream velocity (m/s).
69 A_ref : float
70 The aerodynamic reference area (m^2).
72 optimiser : Optimizer
73 The pyoptsparse Optimizer object of choice.
75 optimiser_options : dict, optional
76 The options to pass to the optimiser. See the PyOptSparse
77 documentation for solver-specific options. The default is None.
79 vehicle_generator : Generator
80 The vehicle generator object.
82 objective_callback : callable
83 The callback function to compute and return the objective function
84 value and constraint violation values.
86 jacobian_callback : callable
87 The callback function to compute and return the Jacobian of the
88 objective function value and constraint violation values.
90 sensitivity_filename : str, optional
91 The filename of the combined components sensitivities. The default
92 is 'all_components_sensitivity.csv'.
94 working_dir_name : str, optional
95 The name of the working directory. The default is 'working_dir'.
97 sim_dir_name : str, optional
98 The name of the simulation directory. The default is 'simulation'.
100 basefiles_dir_name : str, optional
101 The name of the base files directory. The default is 'basefiles'.
103 c3d_log_name : str, optional
104 The name to use for the Cart3D logfile. The default is C3D_log.
106 save_evolution : bool, optional
107 A boolean flag to save geometry files of the evolving geometry.
108 If True, the files will be saved to the evolution directory. The
109 default is True.
111 write_config_xml : bool, optional
112 A boolean flag to write the Cart3D Config.xml file when running
113 comp2tri. If the geometry may be perturbed to a state where one
114 component is no longer part of the wetted surface, writing the
115 comp2tri file can cause set-up issues, and so it can be beneficial
116 to turn it off. The default is True.
117 """
118 # Define global variable so that functions can access them
119 global c3d_logname, _matching_tolerance, _max_matching_tol, _matching_target
120 global sens_filename, basefiles_dir, _c3dprepper, sim_dir_name
121 global _A_ref, _l_ref
122 global _freestream
123 global home_dir, working_dir, f_sense_filename
124 global obj_cb, jac_cb
125 global generator
126 global save_comptri, evo_dir
127 global moo
128 moo = False
130 _freestream = freestream
132 save_comptri = save_evolution
134 generator = vehicle_generator
136 # Construct paths
137 home_dir = os.getcwd()
138 basefiles_dir = os.path.join(home_dir, basefiles_dir_name)
139 working_dir = os.path.join(home_dir, working_dir_name)
140 sim_dir_name = sim_directory_name
141 sens_filename = sensitivity_filename
142 f_sense_filename = "F_sensitivities.csv"
143 c3d_logname = c3d_log_name
145 if save_comptri:
146 # Create evolution history directory
147 evo_dir = os.path.join(home_dir, "evolution")
148 if not os.path.exists(evo_dir):
149 os.mkdir(evo_dir)
151 # Save callback functions
152 obj_cb = objective_callback
153 jac_cb = jacobian_callback
155 # Save reference area
156 _A_ref = A_ref
157 _l_ref = l_ref
159 # Create instance of Cart3D prepper
160 _c3dprepper = C3DPrep(
161 logfile=c3d_logname, info_file=c3d_info_file, write_config=write_config_xml
162 )
164 # Save sensitivity kwargs
165 global _sens_kwargs
166 _sens_kwargs = sensitivity_kwargs if sensitivity_kwargs else {}
168 # Other settings
169 _matching_tolerance = matching_tolerance
170 _max_matching_tol = 0.1
171 _matching_target = 0.9
173 # Construct optimisation problem
174 self.opt_problem = Optimization(
175 name="Cart3D-PySAGAS Shape Optimisation",
176 objFun=evaluate_objective,
177 sens=evaluate_gradient,
178 )
180 # Complete super initialisation
181 super().__init__(
182 optimiser=optimiser,
183 working_dir=working_dir,
184 optimiser_options=optimiser_options,
185 )
188class Cart3DMooShapeOpt(Cart3DShapeOpt):
189 def __init__(
190 self,
191 a_inf: float,
192 rho_inf: float,
193 V_inf: float,
194 A_ref: float,
195 optimiser: Optimizer,
196 vehicle_generator: Generator,
197 objective_callback: callable,
198 jacobian_callback: callable,
199 mach_aoa_points: list[tuple[float, float]],
200 optimiser_options: dict = None,
201 sensitivity_filename: str = "all_components_sensitivity.csv",
202 working_dir_name: str = "working_dir",
203 sim_directory_name: str = "simulation",
204 basefiles_dir_name: str = "basefiles",
205 c3d_log_name: str = "C3D_log",
206 c3d_info_file: str = None,
207 matching_tolerance: float = 0.00001,
208 save_evolution: bool = True,
209 write_config_xml: bool = True,
210 ) -> None:
211 super().__init__(
212 a_inf,
213 rho_inf,
214 V_inf,
215 A_ref,
216 optimiser,
217 vehicle_generator,
218 objective_callback,
219 jacobian_callback,
220 optimiser_options,
221 sensitivity_filename,
222 working_dir_name,
223 sim_directory_name,
224 basefiles_dir_name,
225 c3d_log_name,
226 c3d_info_file,
227 matching_tolerance,
228 save_evolution,
229 write_config_xml,
230 )
231 global sim_points
232 sim_points = mach_aoa_points
234 global moo
235 moo = True
237 # Overload
238 self.opt_problem = Optimization(
239 name="Cart3D-PySAGAS Shape Optimisation",
240 objFun=evaluate_moo_objective,
241 sens=evaluate_moo_gradient,
242 )
244 self.opt_problem: Optimization
245 self.optimiser = optimiser(options=optimiser_options)
248def evaluate_moo_objective(x: dict) -> dict:
249 print("Evaluating objective function.")
251 # Pre-process parameters
252 _process_parameters(x)
254 print("Deploying simulation tasks to pool.")
255 pool = mp.Pool()
256 results = []
257 for result in pool.starmap(aero_wrapper, sim_points):
258 results.append(result)
259 print(" Done.")
261 # Load properties
262 properties_dir = glob.glob("*_properties")
263 if properties_dir:
264 volmass = pd.read_csv(
265 glob.glob(os.path.join(properties_dir[0], "*volmass.csv"))[0],
266 index_col=0,
267 )
269 # Fetch user-defined properties
270 properties_file = glob.glob(os.path.join(properties_dir[0], "*properties.csv"))
271 if len(properties_file) > 0:
272 # File exists, load it
273 properties = pd.read_csv(
274 properties_file[0],
275 index_col=0,
276 )["0"]
277 else:
278 properties = None
280 # Objective function callback
281 funcs = obj_cb(
282 parameters=_unwrap_x(x), results=results, volmass=volmass, properties=properties
283 )
285 return funcs, False
288def aero_wrapper(mach, aoa):
289 freestream = FlowState(mach=mach, pressure=101e3, temperature=288, aoa=aoa)
290 c3d = Cart3D(stl_files=stl_files, freestream=freestream, verbosity=0)
291 flow_result = c3d.solve()
292 return flow_result
295def sens_wrapper(mach, aoa):
296 freestream = FlowState(mach=mach, pressure=101e3, temperature=288, aoa=aoa)
297 c3d = Cart3D(stl_files=stl_files, freestream=freestream, verbosity=0)
299 # Append sensitivity data to tri file
300 sim_dir = os.path.join(working_dir, f"M{mach}A{float(aoa)}")
301 components_filepath = os.path.join(sim_dir, "Components.i.tri")
302 combine_sense_data(
303 components_filepath=components_filepath,
304 sensitivity_files=sensitivity_files,
305 match_target=_matching_target,
306 tol_0=_matching_tolerance,
307 max_tol=_max_matching_tol,
308 outdir=sim_dir,
309 verbosity=0,
310 )
312 # Run the sensitivity solver
313 sens_file = os.path.join(sim_dir, "all_components_sensitivity.csv")
314 sens_result = c3d.solve_sens(sensitivity_filepath=sens_file)
315 flow_result = c3d.flow_result
317 return flow_result, sens_result
320def evaluate_moo_gradient(x: dict, objective: dict) -> dict:
321 """Evaluates the gradient function at the parameter set `x`."""
322 print("Evaluating gradient function.")
324 # Pre-process parameters
325 _process_parameters(x)
327 print("Deploying sensitivity tasks to pool.")
328 pool = mp.Pool()
329 results = []
330 for result in pool.starmap(sens_wrapper, sim_points):
331 results.append(result)
332 print(" Done.")
334 # Calculate Jacobian
335 properties_dir = glob.glob("*_properties")
336 if properties_dir:
337 scalar_sens_dir = os.path.join("scalar_sensitivities")
338 vm = pd.read_csv(
339 glob.glob(os.path.join(properties_dir[0], "*volmass.csv"))[0],
340 index_col=0,
341 )
342 vm_sens = pd.read_csv(
343 os.path.join(scalar_sens_dir, "volmass_sensitivity.csv"),
344 index_col=0,
345 )[x.keys()]
347 # Fetch user-defined properties and sensitivities
348 properties_file = glob.glob(os.path.join(properties_dir[0], "*properties.csv"))
349 if len(properties_file) > 0:
350 # File exists, load it
351 properties = pd.read_csv(
352 properties_file[0],
353 index_col=0,
354 )["0"]
356 # Also load sensitivity file
357 property_sens = pd.read_csv(
358 os.path.join(scalar_sens_dir, "property_sensitivity.csv"),
359 index_col=0,
360 )[x.keys()]
361 else:
362 properties = None
363 property_sens = None
365 else:
366 # No properties data found
367 vm = None
368 vm_sens = None
369 properties = None
370 property_sens = None
372 # Call function
373 jac = jac_cb(
374 parameters=_unwrap_x(x),
375 results=results,
376 volmass=vm,
377 volmass_sens=vm_sens,
378 properties=properties,
379 property_sens=property_sens,
380 )
382 return jac
385def evaluate_objective(x: dict) -> dict:
386 """Evaluates the objective function at the parameter set `x`."""
387 # Pre-process parameters
388 _process_parameters(x)
390 # Run Cart3D simulation
391 sim_success, loads_dict, _ = _run_simulation()
393 # Load properties
394 properties_dir = glob.glob("*_properties")
395 if properties_dir:
396 volmass = pd.read_csv(
397 glob.glob(os.path.join(properties_dir[0], "*volmass.csv"))[0],
398 index_col=0,
399 )
401 # Load COG data
402 cog = np.loadtxt(
403 glob.glob(os.path.join(properties_dir[0], "*cog.txt"))[0], delimiter=","
404 )
406 # Fetch user-defined properties
407 properties_file = glob.glob(os.path.join(properties_dir[0], "*properties.csv"))
408 if len(properties_file) > 0:
409 # File exists, load it
410 properties = pd.read_csv(
411 properties_file[0],
412 index_col=0,
413 )["0"]
414 else:
415 properties = None
417 else:
418 # No properties data found
419 volmass = None
420 properties = None
421 cog = None
423 # Call objective function
424 failed = False
425 if sim_success:
426 # Evaluate objective function
427 funcs: dict[str, dict[str, list]] = obj_cb(
428 loads_dict=loads_dict,
429 volmass=volmass,
430 properties=properties,
431 cog=cog,
432 )
434 # Check for nans
435 for v1 in funcs.values():
436 if any(np.isnan(v1)):
437 failed = True
439 else:
440 # Simulation failed
441 # funcs = {}
442 loads_dict = {"C_L-entire": 0, "C_D-entire": 1}
443 # funcs = obj_cb(
444 # loads_dict=loads_dict,
445 # volmass=volmass,
446 # properties=properties,
447 # cog=cog,
448 # )
449 failed = True
451 if failed:
452 funcs = {}
454 return funcs, failed
457def evaluate_gradient(x: dict, objective: dict) -> dict:
458 """Evaluates the gradient function at the parameter set `x`."""
459 # Pre-process parameters
460 _process_parameters(x)
462 # Run Cart3D simulation
463 _, loads_dict, components_plt_filepath = _run_simulation()
465 # Initialise filepaths
466 components_filepath = components_plt_filepath
467 sensitivity_filepath = os.path.join(working_dir, sens_filename)
469 # Create PySAGAS wrapper and run
470 try:
471 wrapper = Cart3DSensitivityCalculator(
472 freestream=_freestream,
473 sensitivity_filepath=sensitivity_filepath,
474 components_filepath=components_filepath,
475 verbosity=0,
476 )
478 except ValueError:
479 # The sensitivity data does not match the point data, regenerate it
480 tri_components_filepath = os.path.join(
481 working_dir, sim_dir_name, "Components.i.tri"
482 )
483 sensitivity_files = glob.glob(os.path.join("*sensitivity*"))
484 combine_sense_data(
485 tri_components_filepath,
486 sensitivity_files=sensitivity_files,
487 match_target=_matching_target,
488 tol_0=_matching_tolerance,
489 max_tol=_max_matching_tol,
490 outdir=working_dir,
491 verbosity=0,
492 )
494 # Re-instantiate the wrapper
495 wrapper = Cart3DSensitivityCalculator(
496 freestream=_freestream,
497 sensitivity_filepath=sensitivity_filepath,
498 components_filepath=components_filepath,
499 verbosity=0,
500 )
502 result = wrapper.calculate(**_sens_kwargs)
503 F_sense = result.f_sens
504 M_sens = result.m_sens
506 # Non-dimensionalise
507 coef_sens = F_sense / (_freestream.q * _A_ref)
508 moment_coef_sens = M_sens / (_freestream.q * _A_ref * _l_ref)
510 # Calculate Jacobian
511 properties_dir = glob.glob("*_properties")
512 if properties_dir:
513 scalar_sens_dir = os.path.join("scalar_sensitivities")
514 vm = pd.read_csv(
515 glob.glob(os.path.join(properties_dir[0], "*volmass.csv"))[0],
516 index_col=0,
517 )
518 vm_sens = pd.read_csv(
519 os.path.join(scalar_sens_dir, "volmass_sensitivity.csv"),
520 index_col=0,
521 )[x.keys()]
523 # Load COG data
524 cog = np.loadtxt(
525 glob.glob(os.path.join(properties_dir[0], "*cog.txt"))[0], delimiter=","
526 )
527 cog_sens_files = glob.glob(
528 os.path.join(scalar_sens_dir, "*cog_sensitivity.txt")
529 )
530 cog_sens = {}
531 for file in cog_sens_files:
532 param = file.split("_cog_sensitivity.txt")[0].split("/")[-1]
533 cog_sens[param] = np.loadtxt(file, delimiter=",")
535 # Fetch user-defined properties and sensitivities
536 properties_file = glob.glob(os.path.join(properties_dir[0], "*properties.csv"))
537 if len(properties_file) > 0:
538 # File exists, load it
539 properties = pd.read_csv(
540 properties_file[0],
541 index_col=0,
542 )["0"]
544 # Also load sensitivity file
545 property_sens = pd.read_csv(
546 os.path.join(scalar_sens_dir, "property_sensitivity.csv"),
547 index_col=0,
548 )[x.keys()]
549 else:
550 properties = None
551 property_sens = None
553 else:
554 # No properties data found
555 vm = None
556 vm_sens = None
557 properties = None
558 property_sens = None
559 cog = None
560 cog_sens = None
562 # Call function
563 failed = False
564 try:
565 jac: dict[str, dict[str, list]] = jac_cb(
566 parameters=x,
567 coef_sens=coef_sens,
568 moment_coef_sens=moment_coef_sens,
569 loads_dict=loads_dict,
570 volmass=vm,
571 volmass_sens=vm_sens,
572 properties=properties,
573 property_sens=property_sens,
574 cog=cog,
575 cog_sens=cog_sens,
576 )
578 # Check for nans
579 for v1 in jac.values():
580 for v2 in v1.values():
581 if any(np.isnan(v2)):
582 failed = True
584 except Exception as e:
585 print(f"Exception calling jacobian callback: {e}")
586 failed = True
588 if failed:
589 jac = {}
591 return jac, failed
594def _process_parameters(x):
595 # Move into working directory
596 os.chdir(working_dir)
598 # Load existing parameters to compare
599 already_started = _compare_parameters(x)
601 if not already_started:
602 # These parameters haven't run yet, prepare the working directory
603 if save_comptri:
604 # Move components file into evolution directory
605 comp_filepath = os.path.join(working_dir, sim_dir_name, "Components.i.tri")
607 # Check if file exists
608 if os.path.exists(comp_filepath):
609 # Determine new name
610 new_filename = f"{len(os.listdir(evo_dir)):04d}.tri"
612 # Copy file over
613 shutil.copyfile(
614 comp_filepath,
615 os.path.join(evo_dir, new_filename),
616 )
618 # Delete any files from previous run
619 _clean_dir(working_dir)
621 # Dump design parameters to file
622 with open("parameters.pkl", "wb") as f:
623 pickle.dump(x, f)
625 # Generate vehicle and geometry sensitivities
626 if len(glob.glob("*sensitivity*")) == 0 or not already_started:
627 # No sensitivity files generated yet, or this is new geometry
628 print("Running sensitivity study.")
629 parameters = _unwrap_x(x)
630 ss = SensitivityStudy(vehicle_constructor=generator, verbosity=0)
631 ss.dvdp(parameter_dict=parameters, perturbation=2, write_nominal_stl=True)
632 ss.to_csv()
633 print(" Done.")
635 if moo:
636 # Multi-objective optimisation
637 global stl_files, sensitivity_files
638 stl_files = glob.glob("*.stl")
639 sensitivity_files = glob.glob("*sensitivity*")
641 # Also copy aero.csh and input.cntl into working directory
642 for filename in ["input.cntl", "aero.csh"]:
643 shutil.copyfile(
644 os.path.join(home_dir, filename),
645 os.path.join(working_dir, filename),
646 )
647 shutil.copymode(
648 os.path.join(home_dir, filename),
649 os.path.join(working_dir, filename),
650 )
653def _run_simulation(no_attempts: int = 3):
654 """Prepare and run the CFD simulation with Cart3D. The simulation will be
655 run in the 'simulation' subdirectory of the iteration directory.
656 """
657 # Make simulation directory
658 sim_dir = os.path.join(working_dir, sim_dir_name)
659 run_intersect = False
660 components_filepath = os.path.join(sim_dir, "Components.i.tri")
661 if not os.path.exists(sim_dir):
662 os.mkdir(sim_dir)
663 run_intersect = True
664 else:
665 # Check for intersected file
666 run_intersect = not os.path.exists(components_filepath)
667 intersected = True
669 # Attempt component intersection
670 sim_success = False
671 for attempt in range(no_attempts):
672 # Run intersect
673 if run_intersect:
674 _c3dprepper._log(f"SHAPEOPT INTERSECT ATTEMPT {attempt+1}")
675 intersected = _c3dprepper.intersect_stls()
677 # Check for intersection
678 if intersected:
679 # Prepare rest of simulation directory
680 if not os.path.exists(os.path.join(sim_dir, "input.cntl")):
681 # Move files to simulation directory (including Components.i.tri)
682 _c3dprepper.run_autoinputs()
683 os.system(
684 f"mv *.tri Config.xml input.c3d preSpec.c3d.cntl {sim_dir} >> {c3d_logname} 2>&1"
685 )
687 # Copy sim files and permissions
688 for filename in ["input.cntl", "aero.csh"]:
689 shutil.copyfile(
690 os.path.join(basefiles_dir, filename),
691 os.path.join(sim_dir, filename),
692 )
693 shutil.copymode(
694 os.path.join(basefiles_dir, filename),
695 os.path.join(sim_dir, filename),
696 )
698 # Create all_components_sensitivity.csv
699 if not os.path.exists(sens_filename):
700 combine_sense_data(
701 components_filepath,
702 sensitivity_files=glob.glob("*sensitivity*"),
703 match_target=_matching_target,
704 tol_0=_matching_tolerance,
705 max_tol=_max_matching_tol,
706 verbosity=0,
707 )
709 # Run Cart3D and await result
710 os.chdir(sim_dir)
711 target_adapt = _infer_adapt(sim_dir)
712 c3d_donefile = os.path.join(sim_dir, target_adapt, "FLOW", "DONE")
713 _c3dprepper._log(f"Waiting for DONE file: {c3d_donefile}")
714 run_cmd = "./aero.csh restart"
715 _restarts = 0
716 if not os.path.exists(c3d_donefile):
717 with open(c3d_logname, "a") as f:
718 subprocess.run(
719 run_cmd, shell=True, stdout=f, stderr=subprocess.STDOUT
720 )
721 while not os.path.exists(c3d_donefile):
722 # Wait...
723 time.sleep(5)
725 # Check for C3D failure
726 running, e = _c3d_running()
728 if not running:
729 # C3D failed, try restart it
730 if _restarts > 3:
731 return False
733 f = open(c3d_logname, "a")
734 subprocess.run(
735 run_cmd, shell=True, stdout=f, stderr=subprocess.STDOUT
736 )
737 f.close()
738 _restarts += 1
740 sim_success = True
741 break
743 if sim_success:
744 # Sim finished successfully, read loads file
745 loads_dict = _read_c3d_loads(
746 os.path.join(working_dir, sim_dir_name, "BEST/FLOW/loadsCC.dat")
747 )
748 components_plt_filepath = os.path.join(
749 working_dir, sim_dir_name, "BEST/FLOW/Components.i.plt"
750 )
752 else:
753 loads_dict = None
754 components_plt_filepath = None
756 # Change back to working directory
757 os.chdir(working_dir)
759 return sim_success, loads_dict, components_plt_filepath
762def _read_c3d_loads(
763 loadsCC_filepath: str,
764 b_frame: bool = True,
765 v_frame: bool = True,
766 moments: bool = True,
767) -> dict:
768 load_dict = {}
769 with open(loadsCC_filepath, "r") as file:
770 for line in file:
771 if line[0] == "#":
772 # Commented line, skip
773 continue
775 # Remove linebreaks and multiple spaces
776 line = " ".join(line.split())
777 words = line.split(":")
779 if len(words) == 0 or len(words) == 1: # skip if empty line
780 continue
782 text = words[0]
783 number = float(words[1])
784 word = text.split(" ")
785 tag = word[0]
786 coeff = word[-1]
787 coeff = coeff[1:4]
788 if b_frame is True:
789 if coeff in ["C_A", "C_Y", "C_N"]: # get force in b_frame
790 load_dict["{0}-{1}".format(coeff, tag)] = number
791 if v_frame is True:
792 if coeff in ["C_D", "C_S", "C_L"]: # get force in v_frame
793 load_dict["{0}-{1}".format(coeff, tag)] = number
794 if moments is True:
795 if coeff in ["C_l", "C_m", "C_n", "C_M"]: # get moment coeff
796 load_dict["{0}-{1}".format(coeff, tag)] = number
798 return load_dict
801def _c3d_running() -> bool:
802 """Watches the Cart3D log file to check for errors and return False
803 if Cart3D has stopped running."""
804 with open(c3d_logname) as f:
805 # Get last line in log file
806 for line in f:
807 pass
809 # Check if if it is in the known errors
810 for e in Cart3DShapeOpt.C3D_errors:
811 if e in line:
812 return False, e
814 # No errors
815 return True, None
818def _infer_adapt(sim_dir) -> str:
819 with open(f"{sim_dir}/aero.csh", "r") as f:
820 lines = f.readlines()
822 for line in lines:
823 if line.find("set n_adapt_cycles") != -1:
824 return f"adapt{int(line.split('=')[-1]):02d}"
827def _compare_parameters(x):
828 """Compares the current parameters x to the last run simulation
829 parameters."""
830 try:
831 with open("parameters.pkl", "rb") as f:
832 xp = pickle.load(f)
834 # Compare to current parameters
835 already_run = x == xp
837 except FileNotFoundError:
838 # Simulation not run yet
839 already_run = False
841 return already_run
844def _clean_dir(directory: str, keep: list = None):
845 """Deletes everything in a directory except for what is specified
846 in keep."""
847 all_files = os.listdir(directory)
848 if keep is None:
849 # Convert to empty list
850 keep = []
851 rm_files = set(all_files) - set(keep)
852 for f in rm_files:
853 if os.path.isdir(f):
854 shutil.rmtree(f)
855 else:
856 os.remove(f)
859def _unwrap_x(x: dict) -> dict:
860 """Unwraps an ordered dictionary."""
861 unwrapped = {}
862 for key, val in x.items():
863 if len(val) == 1:
864 unwrapped[key] = val[0]
865 else:
866 unwrapped[key] = val
867 return unwrapped