From ca97b1be5059737c22082e2c14933808c6d3400e Mon Sep 17 00:00:00 2001 From: Sean Engelstad Date: Wed, 24 Jan 2024 10:15:17 -0500 Subject: [PATCH 1/4] Update esp/caps download (#271) * Update esp/caps download * Update tar command in unit-test workflow * Fix ESP124 folder name --- .github/workflows/unit_tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d12c9c29..64946d43 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -113,10 +113,10 @@ jobs: cd $F2F_DIR; mkdir extern cd $F2F_DIR/extern/ - wget https://acdl.mit.edu/ESP/PreBuilts/ESP123-linux-x86_64.tgz - tar -xvf ESP123-linux-x86_64.tgz - export ESP_ROOT=${F2F_DIR}/extern/ESP123/EngSketchPad - export CASROOT=${F2F_DIR}/extern/ESP123/OpenCASCADE-7.7.0 + wget https://acdl.mit.edu/ESP/PreBuilts/ESP124-linux-x86_64.tgz + tar -xvf ESP124-linux-x86_64.tgz + export ESP_ROOT=${F2F_DIR}/extern/ESP124/EngSketchPad + export CASROOT=${F2F_DIR}/extern/ESP124/OpenCASCADE-7.7.0 export CASARCH= export PATH=$PATH:$CASROOT/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CASROOT/lib @@ -125,7 +125,7 @@ jobs: source $ESP_ROOT/../ESPenv.sh cd ./src/CAPS/aim make - cd $F2F_DIR/extern/ESP123/ + cd $F2F_DIR/extern/ESP124/ # remove all ESP/CAPS unit test files with recursive delete find . -name "test*" -type f -delete cd $F2F_DIR @@ -136,7 +136,7 @@ jobs: echo "Running Tests"; echo "============================================================="; if [[ ${{ matrix.NAME }} == 'Real' ]]; then - source ${F2F_DIR}/extern/ESP123/ESPenv.sh + source ${F2F_DIR}/extern/ESP124/ESPenv.sh fi testflo ${GITHUB_WORKSPACE}/tests/unit_tests/; From a23f2c233e25438e61d396bb74da6e78eb93b0c5 Mon Sep 17 00:00:00 2001 From: Brian Burke <97243450+bburke38@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:32:55 -0500 Subject: [PATCH 2/4] FUN3D clean up: fix logic in shape driver and remove build_morph method in fun3d_model (#272) * Update funtofem_shape_driver.py * Update fun3d_model.py Delete redundant build_morph method and add documentation to build method. --- funtofem/driver/funtofem_shape_driver.py | 18 +++++------ funtofem/interface/caps2fun/fun3d_model.py | 36 +++++++++------------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/funtofem/driver/funtofem_shape_driver.py b/funtofem/driver/funtofem_shape_driver.py index 58129010..83725c4c 100644 --- a/funtofem/driver/funtofem_shape_driver.py +++ b/funtofem/driver/funtofem_shape_driver.py @@ -364,16 +364,16 @@ def solve_forward(self): root=0, ) - if not (self.is_remote) and self.change_shape: - # case where analysis script does the meshing and the remote does not. - # need to read new shape variable values before doing the meshing - self.model.read_design_variables_file( - self.comm, - filename=Remote.paths(self.comm, self.flow_dir).design_file, - root=0, - ) - if not (self.is_remote) and self.is_paired: + if self.change_shape: + # case where analysis script does the meshing and the remote does not. + # need to read new shape variable values before doing the meshing + self.model.read_design_variables_file( + self.comm, + filename=Remote.paths(self.comm, self.flow_dir).design_file, + root=0, + ) + # remove the _functions_file so remote will fail if self.comm.rank == 0: analysis_functions_file = Remote.paths( diff --git a/funtofem/interface/caps2fun/fun3d_model.py b/funtofem/interface/caps2fun/fun3d_model.py index 01aca993..04133d17 100644 --- a/funtofem/interface/caps2fun/fun3d_model.py +++ b/funtofem/interface/caps2fun/fun3d_model.py @@ -37,13 +37,25 @@ def build( verbosity=0, ): """ - make a pyCAPS problem with the tacsAIM and egadsAIM on serial / root proc + Make a pyCAPS problem with the tacsAIM and egadsAIM on serial / root proc Parameters --------------------------------- csm_file : filepath - filename / full path of ESP/CAPS Constructive Solid Model or .CSM file + Filename / full path of ESP/CAPS Constructive Solid Model or .CSM file. comm : MPI.COMM - MPI communicator + MPI communicator. + project_name : str + Name of the case that is passed to the flow side, e.g., what is used to name the FUN3D input files. + problem_name : str + CAPS problem name, internal name used to define the CAPS problem and determines the name of the directory + that is created by CAPS to build the fluid mesh, geometry, sensitivity files, etc. + mesh_morph : bool + Turn mesh morphing on or off for use with shape variables that alter the fluid geometry + (e.g., when using mesh deformation rather than remeshing). + root : int + The rank of the processor that will control this process. + verbosity : int + Parameter passed directly to pyCAPS to determine output level. """ caps_problem = None if comm.rank == root: @@ -55,24 +67,6 @@ def build( comm.Barrier() return cls(fun3d_aim, aflr_aim, comm, project_name, root=root) - @classmethod - def build_morph( - cls, - csm_file, - comm, - project_name="fun3d_CAPS", - root: int = 0, - problem_name: str = "capsFluid", - ): - return cls.build( - csm_file=csm_file, - comm=comm, - project_name=project_name, - problem_name=problem_name, - root=root, - mesh_morph=True, - ) - @property def root_proc(self) -> bool: return self.fun3d_aim.root_proc From b70866bc40a58ee8e81b7311f571610e1c8c9672 Mon Sep 17 00:00:00 2001 From: Brian Burke <97243450+bburke38@users.noreply.github.com> Date: Mon, 29 Jan 2024 15:07:36 -0500 Subject: [PATCH 3/4] Print out a summary of the driver instance for inspection (#273) * Clean up the model summary Added methods to print out a summary of the driver set up, including details on the solvers, shape change, mesh change, transfer settings, and comm manager. Cleaned up the model summary to print more neatly using standard spacings rather than arbitrary tabs. Small bug fix in `funtofem_shape_driver.py` associated with trying to delete the structural model at the end of the adjoint. Originally implemented to free up memory, but the implementation was attempting to delete an attribute which leads to an Attribute Error. * Create driver summary methods * Move print_transfer to _funtofem_driver Move _print_transfer method to _funtofem_driver so that all drivers can inherit the method. Also changed the generic driver summary in _funtofem_driver. * Formatting * Make transfer_settings an attribute of base driver Add attribute to the base driver class for the transfer_settings, including a check for None. Remove attribution in the shape driver. * Typo in base driver * Formatting --- .../matrix_pencil/fake_aerodynamics.py | 6 +- .../matrix_pencil/structural_model_nbg.py | 34 +++-- funtofem/driver/_funtofem_driver.py | 53 ++++++- funtofem/driver/funtofem_shape_driver.py | 67 ++++++++- funtofem/driver/oneway_struct_driver.py | 8 +- funtofem/interface/solver_manager.py | 12 ++ funtofem/interface/utils/fun3d_client.py | 38 ++--- funtofem/model/_base.py | 140 ++++++++---------- funtofem/model/body.py | 11 +- funtofem/model/funtofem_model.py | 115 +++++++------- funtofem/mphys/mphys_meld.py | 6 +- 11 files changed, 294 insertions(+), 196 deletions(-) diff --git a/examples/framework/functionality/stand_alone/matrix_pencil/fake_aerodynamics.py b/examples/framework/functionality/stand_alone/matrix_pencil/fake_aerodynamics.py index 695dcc14..1588f337 100644 --- a/examples/framework/functionality/stand_alone/matrix_pencil/fake_aerodynamics.py +++ b/examples/framework/functionality/stand_alone/matrix_pencil/fake_aerodynamics.py @@ -103,8 +103,8 @@ def get_function_gradients(self, scenario, bodies, offset): if vartype == "aerodynamic": for i, var in enumerate(scenario.variables[vartype]): if var.active and "force" in var.name: - scenario.derivatives[vartype][offset + func][ - i - ] = -np.sum(self.fixed_step_psi_A) + scenario.derivatives[vartype][offset + func][i] = ( + -np.sum(self.fixed_step_psi_A) + ) return 0 diff --git a/examples/framework/functionality/stand_alone/matrix_pencil/structural_model_nbg.py b/examples/framework/functionality/stand_alone/matrix_pencil/structural_model_nbg.py index 85a4becc..52e40660 100644 --- a/examples/framework/functionality/stand_alone/matrix_pencil/structural_model_nbg.py +++ b/examples/framework/functionality/stand_alone/matrix_pencil/structural_model_nbg.py @@ -533,26 +533,28 @@ def get_function_gradients(self, scenario, bodies, offset): for i, var in enumerate(scenario.variables[vartype]): if var.name == "alpha0" and var.active: if self.comm.rank == 0: - scenario.derivatives[vartype][offset + ifunc][ - i - ] = self.dfdx[1] - - scenario.derivatives[vartype][offset + ifunc][ - i - ] = self.comm.bcast( - scenario.derivatives[vartype][offset + ifunc][i], root=0 + scenario.derivatives[vartype][offset + ifunc][i] = ( + self.dfdx[1] + ) + + scenario.derivatives[vartype][offset + ifunc][i] = ( + self.comm.bcast( + scenario.derivatives[vartype][offset + ifunc][i], + root=0, + ) ) if var.name == "struct_dt" and var.active: if self.comm.rank == 0: - scenario.derivatives[vartype][offset + ifunc][ - i - ] = self.dfdx[0] - - scenario.derivatives[vartype][offset + ifunc][ - i - ] = self.comm.bcast( - scenario.derivatives[vartype][offset + ifunc][i], root=0 + scenario.derivatives[vartype][offset + ifunc][i] = ( + self.dfdx[0] + ) + + scenario.derivatives[vartype][offset + ifunc][i] = ( + self.comm.bcast( + scenario.derivatives[vartype][offset + ifunc][i], + root=0, + ) ) def eval_func(self, x, name="dt"): diff --git a/funtofem/driver/_funtofem_driver.py b/funtofem/driver/_funtofem_driver.py index 69c627fe..29bc9712 100644 --- a/funtofem/driver/_funtofem_driver.py +++ b/funtofem/driver/_funtofem_driver.py @@ -25,6 +25,7 @@ import numpy as np from mpi4py import MPI from funtofem import TransferScheme +from .transfer_settings import TransferSettings try: from .hermes_transfer import HermesTransfer @@ -68,6 +69,10 @@ def __init__( comm_manager = solvers.comm_manager self.comm_manager = comm_manager + if transfer_settings is None: + transfer_settings = TransferSettings() + self.transfer_settings = transfer_settings + # communicator self.comm = comm_manager.master_comm self.aero_comm = comm_manager.aero_comm @@ -113,7 +118,7 @@ def __init__( self.struct_root, self.aero_comm, self.aero_root, - transfer_settings=transfer_settings, + transfer_settings=self.transfer_settings, ) # Initialize the shape parameterization @@ -327,3 +332,49 @@ def _solve_steady_adjoint(self, scenario): def _solve_unsteady_adjoint(self, scenario): return 1 + + def print_summary(self, print_model=False, print_comm=False): + """ + Print out a summary of the FUNtoFEM driver for inspection. + """ + + print("==========================================================") + print("|| FUNtoFEM Driver Summary ||") + print("==========================================================") + print(self) + + self._print_transfer(print_comm=print_comm) + + if print_model: + print( + "\nPrinting abbreviated model summary. For details print model summary directly." + ) + self.model.print_summary(print_level=-1, ignore_rigid=True) + + return + + def _print_transfer(self, print_comm=False): + print("\n---------------------") + print("| Transfer Settings |") + print("---------------------") + + print(f" Elastic scheme: {self.transfer_settings.elastic_scheme}") + print(f" No. points: {self.transfer_settings.npts}") + print(f" Beta: {self.transfer_settings.beta}") + print(f" Thermal scheme: {self.transfer_settings.thermal_scheme}") + print(f" No. points: {self.transfer_settings.thermal_npts}") + print(f" Beta: {self.transfer_settings.thermal_beta}\n") + + if print_comm: + print(self.comm_manager) + + return + + def __str__(self): + line1 = f"Driver (): {self.__class__.__qualname__}" + line2 = f" Model: {self.model.name}" + line3 = f" Number of scenarios: {len(self.model.scenarios)}" + + output = (line1, line2, line3) + + return "\n".join(output) diff --git a/funtofem/driver/funtofem_shape_driver.py b/funtofem/driver/funtofem_shape_driver.py index 83725c4c..eb4c211b 100644 --- a/funtofem/driver/funtofem_shape_driver.py +++ b/funtofem/driver/funtofem_shape_driver.py @@ -170,7 +170,6 @@ def __init__( solvers, comm_manager, transfer_settings, model ) - self.transfer_settings = transfer_settings self.remote = remote self.is_paired = is_paired self.struct_nprocs = struct_nprocs @@ -709,11 +708,11 @@ def solve_adjoint(self): # not sure if this barrier is necessary here but just in case self.comm.Barrier() - if not self.is_remote: - # delete struct interface to free up memory in shape change - # self.solvers.structural._deallocate() - del self.solvers.structural - self.comm.Barrier() + # if not self.is_remote: + # # delete struct interface to free up memory in shape change + # # self.solvers.structural._deallocate() + # del self.solvers.structural + # self.comm.Barrier() self.comm.Barrier() start_time = time.time() @@ -1067,3 +1066,59 @@ def remote_meshing(self) -> bool: @property def tacs_model(self): return self.model.structural + + def print_summary(self, print_model=False, print_comm=False): + """ + Print out a summary of the FUNtoFEM driver for inspection. + """ + + print("\n\n==========================================================") + print("|| FUNtoFEM Driver Summary ||") + print("==========================================================") + print(self) + + self._print_shape_change() + self._print_transfer(print_comm=print_comm) + + if print_model: + print( + "\nPrinting abbreviated model summary. For details print model summary directly." + ) + self.model.print_summary(print_level=-1, ignore_rigid=True) + + return + + def _print_shape_change(self): + _num_shape_vars = len(self.shape_variables) + print("\n--------------------") + print("| Shape Change |") + print("--------------------") + + print(f" No. shape variables: {_num_shape_vars}") + print(f" Aerodynamic shape change: {self.aero_shape}") + print(f" Structural shape change: {self.struct_shape}") + + print(f" Meshing:", end=" ") + if self.is_paired: + # Remeshing + print(f" RE-MESH") + if self.change_shape: + print(f" Remote is meshing.") + else: + print(f" Analysis script is meshing.") + else: + # Morphing + print(f" MORPH") + + return + + def __str__(self): + line1 = f"Driver (): {self.__class__.__qualname__}" + line2 = f" Using remote: {self.is_remote}" + line3 = f" Flow solver type: {self._flow_solver_type}" + line4 = f" Structural solver type: {self._struct_solver_type}" + line5 = f" No. structural procs: {self.struct_nprocs}" + + output = (line1, line2, line3, line4, line5) + + return "\n".join(output) diff --git a/funtofem/driver/oneway_struct_driver.py b/funtofem/driver/oneway_struct_driver.py index bf9d0828..12daf863 100644 --- a/funtofem/driver/oneway_struct_driver.py +++ b/funtofem/driver/oneway_struct_driver.py @@ -545,9 +545,11 @@ def solve_adjoint(self): # write the sensitivity file for the tacs AIM self.model.write_sensitivity_file( comm=self.comm, - filename=self.struct_aim.root_sens_file - if not self.fun3d_dir - else self.analysis_sens_file, + filename=( + self.struct_aim.root_sens_file + if not self.fun3d_dir + else self.analysis_sens_file + ), discipline="structural", ) diff --git a/funtofem/interface/solver_manager.py b/funtofem/interface/solver_manager.py index 15a079ce..0d7a308c 100644 --- a/funtofem/interface/solver_manager.py +++ b/funtofem/interface/solver_manager.py @@ -41,6 +41,18 @@ def __init__( self.aero_comm = master_comm self.aero_root = aero_root + def __str__(self): + line0 = f"CommManager" + line1 = f" Master comm: {self.master_comm}" + line2 = f" Aero comm: {self.aero_comm}" + line3 = f" Aero root: {self.aero_root}" + line4 = f" Struct comm: {self.struct_comm}" + line5 = f" Struct root: {self.struct_root}" + + output = (line0, line1, line2, line3, line4, line5) + + return "\n".join(output) + class SolverManager: def __init__(self, comm, use_flow: bool = True, use_struct: bool = True): diff --git a/funtofem/interface/utils/fun3d_client.py b/funtofem/interface/utils/fun3d_client.py index 0c455f33..d6dcbbd8 100644 --- a/funtofem/interface/utils/fun3d_client.py +++ b/funtofem/interface/utils/fun3d_client.py @@ -435,20 +435,20 @@ def get_function_gradients(self, scenario, bodies, offset): for i, var in enumerate(scenario.variables[vartype]): if var.active: if function.adjoint: - scenario.derivatives[vartype][offset + func][ - i - ] = self.fun3d_client.get_design_global_derivative( - function.id, var.id + scenario.derivatives[vartype][offset + func][i] = ( + self.fun3d_client.get_design_global_derivative( + function.id, var.id + ) ) else: scenario.derivatives[vartype][offset + func][ i ] = 0.0 - scenario.derivatives[vartype][offset + func][ - i - ] = self.comm.bcast( - scenario.derivatives[vartype][offset + func][i], - root=0, + scenario.derivatives[vartype][offset + func][i] = ( + self.comm.bcast( + scenario.derivatives[vartype][offset + func][i], + root=0, + ) ) for ibody, body in enumerate(bodies, 1): @@ -457,20 +457,22 @@ def get_function_gradients(self, scenario, bodies, offset): for i, var in enumerate(body.variables[vartype]): if var.active: if function.adjoint: - body.derivatives[vartype][offset + func][ - i - ] = self.fun3d_client.get_design_rigid_derivative( - ibody, function.id, var.id + body.derivatives[vartype][offset + func][i] = ( + self.fun3d_client.get_design_rigid_derivative( + ibody, function.id, var.id + ) ) else: body.derivatives[vartype][offset + func][ i ] = 0.0 - scenario.derivatives[vartype][offset + func][ - i - ] = self.comm.bcast( - scenario.derivatives[vartype][offset + func][i], - root=0, + scenario.derivatives[vartype][offset + func][i] = ( + self.comm.bcast( + scenario.derivatives[vartype][ + offset + func + ][i], + root=0, + ) ) except FUN3DAeroException as e: diff --git a/funtofem/model/_base.py b/funtofem/model/_base.py index b33df90f..88f00cf6 100644 --- a/funtofem/model/_base.py +++ b/funtofem/model/_base.py @@ -320,103 +320,83 @@ def set_id(self, id): def _print_functions(self): print( - " ------------------------------------------------------------------------------------" - ) - print( - " | Function \t| Analysis Type\t| Comp. Adjoint\t| Time Range\t| Averaging\t|" + " --------------------------------------------------------------------------------" ) + self._print_long("Function", width=12, indent_line=5) + self._print_long("Analysis Type", width=15) + self._print_long("Comp. Adjoint", width=15) + self._print_long("Time Range", width=20) + self._print_long("Averaging", end_line=True) + print( - " ------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------" ) for func in self.functions: - if len(func.name) >= 8: - print( - " | ", - func.name, - "\t| ", - func.analysis_type, - "\t| ", - func.adjoint, - "\t| [", - func.start, - ",", - func.stop, - "] \t| ", - func.averaging, - "\t|", - ) - else: - print( - " | ", - func.name, - "\t\t| ", - func.analysis_type, - "\t| ", - func.adjoint, - "\t| [", - func.start, - ",", - func.stop, - "] \t| ", - func.averaging, - "\t|", - ) + analysis_type = func.analysis_type + adjoint = func.adjoint + start = func.start + stop = func.stop + averaging = func.averaging + _time_range = " ".join(("[", str(start), ",", str(stop), "]")) + adjoint = str(adjoint) + self._print_long(func.name, width=12, indent_line=5) + self._print_long(analysis_type, width=15) + self._print_long(adjoint, width=15) + self._print_long(_time_range, width=20) + self._print_long(averaging, end_line=True) + print( - " ------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------" ) return def _print_variables(self, vartype): print( - " ------------------------------------------------------------------------------------------------------------" - ) - print( - " | Variable\t\t| Var. ID\t| Value\t\t| Bounds\t\t| Active\t| Coupled\t|" + " --------------------------------------------------------------------------------------" ) + self._print_long("Variable", width=12, indent_line=5) + self._print_long("Var. ID", width=10) + self._print_long("Value", width=16) + self._print_long("Bounds", width=24) + self._print_long("Active", width=8) + self._print_long("Coupled", width=9, end_line=True) + print( - " ------------------------------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------------" ) for var in self.variables[vartype]: - if len(var.name) >= 8: - print( - " | ", - var.name, - "\t|", - var.id, - "\t\t|", - var.value, - " \t| [", - var.lower, - ",", - var.upper, - "] \t|", - var.active, - " \t|", - var.coupled, - "\t|", - ) - else: - print( - " | ", - var.name, - "\t\t|", - var.id, - "\t\t|", - var.value, - " \t| [", - var.lower, - ",", - var.upper, - "] \t|", - var.active, - " \t|", - var.coupled, - "\t|", - ) + _name = "{:s}".format(var.name) + _id = "{: d}".format(var.id) + _value = "{:#.8g}".format(var.value) + _lower = "{:#.3g}".format(var.lower) + _upper = "{:#.3g}".format(var.upper) + _active = str(var.active) + _coupled = str(var.coupled) + _bounds = " ".join(("[", _lower, ",", _upper, "]")) + + self._print_long(_name, width=12, indent_line=5) + self._print_long(_id, width=10, align="<") + self._print_long(_value, width=16) + self._print_long(_bounds, width=24) + self._print_long(_active, width=8) + self._print_long(_coupled, width=9, end_line=True) print( - " ------------------------------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------------" ) return + + def _print_long(self, value, width=12, indent_line=0, end_line=False, align="^"): + if value is None: + value = "None" + if indent_line > 0: + print("{val:{wid}}".format(wid=indent_line, val=""), end="") + if not end_line: + print("|{val:{ali}{wid}}".format(wid=width, ali=align, val=value), end="") + else: + print( + "|{val:{ali}{wid}}|".format(wid=width, ali=align, val=value), end="\n" + ) + return diff --git a/funtofem/model/body.py b/funtofem/model/body.py index d1f2fa37..a9516783 100644 --- a/funtofem/model/body.py +++ b/funtofem/model/body.py @@ -1614,9 +1614,9 @@ def _distribute_aero_loads(self, data): for ind, aero_id in enumerate(self.aero_id): if self.transfer is not None: - self.aero_loads[scenario_id][ - 3 * ind : 3 * ind + 3 - ] = scenario_entry_dict[aero_id]["load"] + self.aero_loads[scenario_id][3 * ind : 3 * ind + 3] = ( + scenario_entry_dict[aero_id]["load"] + ) if self.thermal_transfer is not None: self.aero_heat_flux[scenario_id][ind] = scenario_entry_dict[ aero_id @@ -1902,7 +1902,8 @@ def shape_derivative(self, scenario, offset): return def __str__(self): - line1 = f"Body ( ): {self.id} {self.name}" + line0 = f"Body ( ): {self.id} {self.name}" + line1 = f" Analysis type: {self.analysis_type}" line2 = f" Boundary: {self.boundary}" line3 = f" Coupling Group: {self.group}" line4 = f" Motion type: {self.motion_type}" @@ -1910,7 +1911,7 @@ def __str__(self): line6 = f" Relaxation scheme: {type(self.relaxation_scheme)}" line7 = f" Shape parameterization: {type(self.shape)}" - output = (line1, line2, line3, line4, line5, line6, line7) + output = (line0, line1, line2, line3, line4, line5, line6, line7) return "\n".join(output) diff --git a/funtofem/model/funtofem_model.py b/funtofem/model/funtofem_model.py index 0cf6a269..f5d043c0 100644 --- a/funtofem/model/funtofem_model.py +++ b/funtofem/model/funtofem_model.py @@ -1021,13 +1021,16 @@ def print_summary( def _print_functions(self): model_functions = self.get_functions(all=True) print( - " ------------------------------------------------------------------------------------" - ) - print( - " | Function \t| Analysis Type\t| Comp. Adjoint\t| Time Range\t| Averaging\t|" + " --------------------------------------------------------------------------------" ) + self._print_long("Function", width=12, indent_line=5) + self._print_long("Analysis Type", width=15) + self._print_long("Comp. Adjoint", width=15) + self._print_long("Time Range", width=20) + self._print_long("Averaging", end_line=True) + print( - " ------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------" ) for func in model_functions: if isinstance(func, CompositeFunction): @@ -1042,40 +1045,15 @@ def _print_functions(self): start = func.start stop = func.stop averaging = func.averaging - if len(func.name) >= 8: - print( - " | ", - func.name, - "\t| ", - analysis_type, - "\t| ", - adjoint, - "\t| [", - start, - ",", - stop, - "] \t| ", - averaging, - "\t|", - ) - else: - print( - " | ", - func.name, - "\t\t| ", - analysis_type, - "\t| ", - adjoint, - "\t| [", - start, - ",", - stop, - "] \t| ", - averaging, - "\t|", - ) + _time_range = " ".join(("[", str(start), ",", str(stop), "]")) + adjoint = str(adjoint) + self._print_long(func.name, width=12, indent_line=5) + self._print_long(analysis_type, width=15) + self._print_long(adjoint, width=15) + self._print_long(_time_range, width=20) + self._print_long(averaging, end_line=True) print( - " ------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------" ) return @@ -1083,39 +1061,54 @@ def _print_functions(self): def _print_variables(self): model_variables = self.get_variables() print( - " ------------------------------------------------------------------------------------------------------------" - ) - print( - " | Variable\t\t| Var. ID\t| Value \t| Bounds\t\t| Active\t| Coupled\t|" + " --------------------------------------------------------------------------------------" ) + self._print_long("Variable", width=12, indent_line=5) + self._print_long("Var. ID", width=10) + self._print_long("Value", width=16) + self._print_long("Bounds", width=24) + self._print_long("Active", width=8) + self._print_long("Coupled", width=9, end_line=True) + print( - " ------------------------------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------------" ) for var in model_variables: - print( - " | ", - var.name, - "\t\t|", - var.id, - "\t\t|", - var.value, - " \t| [", - var.lower, - ",", - var.upper, - "] \t|", - var.active, - " \t|", - var.coupled, - "\t|", - ) + _name = "{:s}".format(var.name) + _id = "{: d}".format(var.id) + _value = "{:#.8g}".format(var.value) + _lower = "{:#.3g}".format(var.lower) + _upper = "{:#.3g}".format(var.upper) + _active = str(var.active) + _coupled = str(var.coupled) + _bounds = " ".join(("[", _lower, ",", _upper, "]")) + + self._print_long(_name, width=12, indent_line=5) + self._print_long(_id, width=10, align="<") + self._print_long(_value, width=16) + self._print_long(_bounds, width=24) + self._print_long(_active, width=8) + self._print_long(_coupled, width=9, end_line=True) print( - " ------------------------------------------------------------------------------------------------------------" + " --------------------------------------------------------------------------------------" ) return + def _print_long(self, value, width=12, indent_line=0, end_line=False, align="^"): + if value is None: + value = "None" + if indent_line > 0: + print("{val:{wid}}".format(wid=indent_line, val=""), end="") + if not end_line: + print("|{val:{ali}{wid}}".format(wid=width, ali=align, val=value), end="") + else: + print( + "|{val:{ali}{wid}}|".format(wid=width, ali=align, val=value), end="\n" + ) + return + def __str__(self): line1 = f"Model (): {self.name}" line2 = f" Number of bodies: {len(self.bodies)}" diff --git a/funtofem/mphys/mphys_meld.py b/funtofem/mphys/mphys_meld.py index aac9c751..e5620091 100644 --- a/funtofem/mphys/mphys_meld.py +++ b/funtofem/mphys/mphys_meld.py @@ -304,9 +304,9 @@ def compute(self, inputs, outputs): body.meld.transferLoads(f_a, f_s) for i in range(3): - outputs["f_struct"][ - body.struct_dof_indices[i :: self.struct_ndof] - ] = f_s[i::3] + outputs["f_struct"][body.struct_dof_indices[i :: self.struct_ndof]] = ( + f_s[i::3] + ) def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): """ From a08a97e71ee3bf5489c4dcf1edbd7876da61ab78 Mon Sep 17 00:00:00 2001 From: Brian Burke <97243450+bburke38@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:50:04 -0500 Subject: [PATCH 4/4] Read in inactive variables from design files (#274) * Root proc check on _setDictOptions for AFLR AIM * Add ability to read inactive variables from previous designs --- funtofem/interface/caps2fun/aflr_aim.py | 11 ++++++----- funtofem/model/_base.py | 17 +++++++++++++++++ funtofem/model/funtofem_model.py | 24 +++++++++++++++++++----- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/funtofem/interface/caps2fun/aflr_aim.py b/funtofem/interface/caps2fun/aflr_aim.py index 1d3ec871..b8fff8c5 100644 --- a/funtofem/interface/caps2fun/aflr_aim.py +++ b/funtofem/interface/caps2fun/aflr_aim.py @@ -104,12 +104,13 @@ def _setDictOptions(self): """ Set AFLR3 and AFLR4 options via dictionaries. """ - dictOptions = self._dictOptions + if self.root_proc: + dictOptions = self._dictOptions - for ind, option in enumerate(dictOptions["aflr4AIM"]): - self.surface_aim.input[option].value = dictOptions["aflr4AIM"][option] + for ind, option in enumerate(dictOptions["aflr4AIM"]): + self.surface_aim.input[option].value = dictOptions["aflr4AIM"][option] - for ind, option in enumerate(dictOptions["aflr3AIM"]): - self.volume_aim.input[option].value = dictOptions["aflr3AIM"][option] + for ind, option in enumerate(dictOptions["aflr3AIM"]): + self.volume_aim.input[option].value = dictOptions["aflr3AIM"][option] return self diff --git a/funtofem/model/_base.py b/funtofem/model/_base.py index 88f00cf6..5e6179a2 100644 --- a/funtofem/model/_base.py +++ b/funtofem/model/_base.py @@ -258,6 +258,23 @@ def get_active_variables(self): return full_list + def get_inactive_variables(self): + """ + Get the list of active variables in body or scenario + + Returns + ------- + active_list: list of variables + list of active variables + """ + full_list = [] + is_active = lambda var: var.active == False + + for vartype in self.variables: + full_list.extend(list(filter(is_active, self.variables[vartype]))) + + return full_list + def get_uncoupled_variables(self): """ Get the list of uncoupled, active variables in body or scenario diff --git a/funtofem/model/funtofem_model.py b/funtofem/model/funtofem_model.py index f5d043c0..c29d848b 100644 --- a/funtofem/model/funtofem_model.py +++ b/funtofem/model/funtofem_model.py @@ -150,8 +150,6 @@ def _send_struct_variables(self, base): shape_variables = base.variables["shape"] for var in struct_variables: - if not (var.active): - continue # check if matching shell property exists matching_prop = False for prop in self.structural.tacs_aim._properties: @@ -167,7 +165,10 @@ def _send_struct_variables(self, base): if matching_prop and not (matching_dv): caps2tacs.ThicknessVariable( - caps_group=var.name, value=var.value, name=var.name + caps_group=var.name, + value=var.value, + name=var.name, + active=var.active, ).register_to(self.structural) esp_caps_despmtrs = None @@ -241,7 +242,7 @@ def _send_flow_variables(self, base): self.flow.set_variables(active_shape_vars, active_aero_vars) return - def get_variables(self, names=None): + def get_variables(self, names=None, all=False): """ Get all the coupled and uncoupled variable objects for the entire model. Coupled variables only appear once. @@ -250,6 +251,8 @@ def get_variables(self, names=None): ---------- names: str or List[str] one variable name or a list of variable names + all: bool + Flag to include inactive variables. Returns ------- @@ -263,12 +266,16 @@ def get_variables(self, names=None): dv.extend(scenario.get_active_variables()) else: dv.extend(scenario.get_uncoupled_variables()) + if all: + dv.extend(scenario.get_inactive_variables()) for body in self.bodies: if body.group_root: dv.extend(body.get_active_variables()) else: dv.extend(body.get_uncoupled_variables()) + if all: + dv.extend(body.get_inactive_variables()) return dv elif isinstance(names, str): @@ -742,6 +749,9 @@ def read_design_variables_file(self, comm, filename, root=0): Discipline Var_name Var_value + IMPORTANT: To correctly set inactive variables, make sure to call (e.g.) tacs_aim.setup_aim(), + then read_design_variables_file, then tacs_aim.pre_analysis. + Parameters ---------- comm: MPI communicator @@ -773,10 +783,14 @@ def read_design_variables_file(self, comm, filename, root=0): variables_dict = comm.bcast(variables_dict, root=root) # update the variable values on each processor - for var in self.get_variables(): + for var in self.get_variables(all=True): if var.full_name in variables_dict: var.value = variables_dict[var.full_name] + if self.structural is not None: + input_dict = {var.name: var.value for var in self.get_variables(all=True)} + self.structural.update_design(input_dict) + return def write_design_variables_file(self, comm, filename, root=0):