From 7dd238898f82c724279c81688d0d43a38ae031b9 Mon Sep 17 00:00:00 2001 From: Sean Engelstad Date: Wed, 22 Jan 2025 12:48:28 -0500 Subject: [PATCH] Revert "Modal idf (#360)" This reverts commit d2127168bd4bd3de5cc54590a32f99c4b9f57f65. --- funtofem/driver/__init__.py | 11 +- funtofem/driver/_funtofem_driver.py | 24 +- funtofem/driver/custom/__init__.py | 4 - funtofem/driver/custom/multi_driver.py | 42 --- .../custom/oneway_struct_trim_driver.py | 209 ------------- funtofem/driver/funtofem_shape_driver.py | 2 +- funtofem/driver/modal_idf_driver.py | 283 ------------------ funtofem/driver/oneway_aero_driver.py | 24 +- funtofem/driver/oneway_struct_driver.py | 82 +++-- funtofem/interface/caps2fun/fun3d_aim.py | 5 - funtofem/interface/fun3d_14_interface.py | 9 - funtofem/interface/fun3d_interface.py | 5 - funtofem/interface/tacs_interface.py | 131 +------- funtofem/model/body.py | 255 ---------------- funtofem/model/funtofem_model.py | 28 +- funtofem/model/scenario.py | 6 - funtofem/optimization/optimization_manager.py | 4 +- .../unit_tests/framework/test_modal_driver.py | 151 ---------- 18 files changed, 63 insertions(+), 1212 deletions(-) delete mode 100644 funtofem/driver/custom/__init__.py delete mode 100644 funtofem/driver/custom/multi_driver.py delete mode 100644 funtofem/driver/custom/oneway_struct_trim_driver.py delete mode 100644 funtofem/driver/modal_idf_driver.py delete mode 100644 tests/unit_tests/framework/test_modal_driver.py diff --git a/funtofem/driver/__init__.py b/funtofem/driver/__init__.py index e6ad8703..ec03b839 100644 --- a/funtofem/driver/__init__.py +++ b/funtofem/driver/__init__.py @@ -12,15 +12,6 @@ from .funtofem_nlbgs_driver import * from .funtofem_nlbgs_fsi_subiters_driver import * from .transfer_settings import * -import importlib -caps_loader = importlib.util.find_spec("pyCAPS") -if caps_loader is not None: - from .funtofem_shape_driver import * +from .funtofem_shape_driver import * from .oneway_struct_driver import * from .oneway_aero_driver import * - -# modal IDF driver -from .modal_idf_driver import * - -# import all the custom or special drivers -from .custom import * diff --git a/funtofem/driver/_funtofem_driver.py b/funtofem/driver/_funtofem_driver.py index afcec0b1..b899f4c9 100644 --- a/funtofem/driver/_funtofem_driver.py +++ b/funtofem/driver/_funtofem_driver.py @@ -70,14 +70,6 @@ def __init__( whether to save and reload funtofem states """ - # assert at least one coupled scenario - self.coupled_scenarios = [] - if model is not None: # only case where model is None is fakeModel? - any_coupled = any([scenario.coupled for scenario in model.scenarios]) - assert any_coupled - - self.coupled_scenarios = [scenario for scenario in model.scenarios if scenario.coupled] - # add the comm manger if comm_manager is not None: comm_manager = comm_manager @@ -175,8 +167,7 @@ def solve_forward(self, steps=None): body.update_shape(complex_run) # loop over the forward problem for the different scenarios - for scenario in self.coupled_scenarios: - + for scenario in self.model.scenarios: # tell the solvers what the variable values and functions are for this scenario if not self.fakemodel: self._distribute_variables(scenario, self.model.bodies) @@ -232,10 +223,11 @@ def solve_adjoint(self): # Zero the derivative values stored in the function self._zero_derivatives() + for func in functions: + func.zero_derivatives() # Set the functions into the solvers - for scenario in self.coupled_scenarios: - + for scenario in self.model.scenarios: # tell the solvers what the variable values and functions are for this scenario self._distribute_variables(scenario, self.model.bodies) self._distribute_functions(scenario, self.model.bodies) @@ -296,11 +288,9 @@ def _initialize_adjoint(self, scenario, bodies): def _zero_derivatives(self): """zero all model derivatives""" - # TODO : only zero derivatives in coupled scenarios when using - for scenario in self.coupled_scenarios: # no need to zero composite functions, they are exactly diff later with no += effects - for func in scenario.functions: - for var in self.model.get_variables(): - func.derivatives[var] = 0.0 + for func in self.model.get_functions(all=True): + for var in self.model.get_variables(): + func.derivatives[var] = 0.0 return def _post_forward(self, scenario, bodies): diff --git a/funtofem/driver/custom/__init__.py b/funtofem/driver/custom/__init__.py deleted file mode 100644 index aa3fb6af..00000000 --- a/funtofem/driver/custom/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import importlib - -from .multi_driver import * -from .oneway_struct_trim_driver import * \ No newline at end of file diff --git a/funtofem/driver/custom/multi_driver.py b/funtofem/driver/custom/multi_driver.py deleted file mode 100644 index ac115f5e..00000000 --- a/funtofem/driver/custom/multi_driver.py +++ /dev/null @@ -1,42 +0,0 @@ - -__all__ = ["MultiDriver"] - -class MultiDriver: - def __init__(self, driver_list:list): - """ - call solve_forward, solve_adjoint on multiple drivers - - useful if one scenario uses a coupled driver and the - other uses an uncoupled driver - - in this way, we can include a mixture of each by combining these - drivers into one and still using the OptimizationManager - """ - self.driver_list = driver_list - - # copy comm and solvers from the first driver - # since these are used in optimizationManager - first_driver = self.driver_list[0] - self.comm = first_driver.comm - self.solvers = first_driver.solvers - self.model = first_driver.model - - def solve_forward(self): - driver_list = self.driver_list - for driver in driver_list: - driver.solve_forward() - - def solve_adjoint(self): - self._zero_derivatives() - driver_list = self.driver_list - for driver in driver_list: - driver.solve_adjoint() - - def _zero_derivatives(self): - """zero all model derivatives""" - # TODO : only zero derivatives in coupled scenarios when using - model = self.driver_list[0].model - for func in model.get_functions(all=True): - for var in self.model.get_variables(): - func.derivatives[var] = 0.0 - return diff --git a/funtofem/driver/custom/oneway_struct_trim_driver.py b/funtofem/driver/custom/oneway_struct_trim_driver.py deleted file mode 100644 index 74d1e09c..00000000 --- a/funtofem/driver/custom/oneway_struct_trim_driver.py +++ /dev/null @@ -1,209 +0,0 @@ - -__all__ = ["OnewayStructTrimDriver"] - -from ..oneway_struct_driver import OnewayStructDriver -import numpy as np -from mpi4py import MPI - -class OnewayStructTrimDriver(OnewayStructDriver): - - """ - goal of this class is to run oneway-coupled sizing - while also trimming the wing using a set of loads obtained - when pull up might not be satisfied.. - - aero / struct loads are scaled up as an AOA variable is scaled up - for all uncoupled scenarios (have scenario.coupled = False) - - the user should setup a load factor based composite function - such as lift - load_factor * weight = 0 - where load_factor is user-specified for that scenario - """ - - def __init__( - self, - solvers, - model, - initial_trim_dict:dict, - transfer_settings=None, - nprocs=None, - fun3d_dir=None, - external_shape=False, - timing_file=None, - ): - - # create base class OnewayStructDriver - super(OnewayStructTrimDriver,self).__init__( - solvers, - model, - transfer_settings, - nprocs, - fun3d_dir, - external_shape, - timing_file, - ) - - # get data from scenario initial trim dict - # assumed to hold initial values for (not case sensitive) - # and lift is C_L normalized by area and qinf - # {scenario_name : {'cl' : cl_0, 'AOA' : AOA_0}} - # only required to put scenarios which are uncoupled here - # with scenario.coupled = False boolean - - self.initial_trim_dict = initial_trim_dict - - # save initial struct loads vectors for each scenario - self._orig_struct_loads = {} - self.uncoupled_scenarios = [scenario for scenario in model.scenarios if not(scenario.coupled)] - for scenario in self.uncoupled_scenarios: - self._orig_struct_loads[scenario.name] = {} - for body in model.bodies: - struct_loads = body.struct_loads[scenario.id] - self._orig_struct_loads[scenario.name][body.name] = struct_loads * 1.0 - - @classmethod - def prime_loads_from_file( - cls, - filename, - solvers, - initial_trim_dict, - model, - nprocs, - transfer_settings, - external_shape=False, - init_transfer=False, - timing_file=None, - ): - # same as base class prime_loads_from_file but with extra input argument - # aka initial_trim_dict - comm = solvers.comm - world_rank = comm.Get_rank() - if world_rank < nprocs: - color = 1 - else: - color = MPI.UNDEFINED - tacs_comm = comm.Split(color, world_rank) - - # initialize transfer settings - comm_manager = solvers.comm_manager - - # read in the loads from the file - loads_data = model._read_aero_loads(comm, filename) - - # initialize the transfer scheme then distribute aero loads - for body in model.bodies: - body.initialize_transfer( - comm=comm, - struct_comm=tacs_comm, - struct_root=comm_manager.struct_root, - aero_comm=comm_manager.aero_comm, - aero_root=comm_manager.aero_root, - transfer_settings=transfer_settings, - ) - for scenario in model.scenarios: - body.initialize_variables(scenario) - assert scenario.steady - body._distribute_aero_loads(loads_data, steady=True) - - tacs_driver = cls( - solvers, - model, - initial_trim_dict, - nprocs=nprocs, - external_shape=external_shape, - timing_file=timing_file, - ) - if init_transfer: - tacs_driver._transfer_fixed_aero_loads() - - return tacs_driver - - def solve_forward(self): - - # scale up the loads by new AOA vs previous AOA - # note this only works for steady-state case - for scenario in self.uncoupled_scenarios: - orig_AOA = self.initial_trim_dict[scenario.name]['AOA'] - new_AOA = scenario.get_variable('AOA').value.real - for body in self.model.bodies: - orig_struct_loads = self._orig_struct_loads[scenario.name][body.name] - body.struct_loads[scenario.id][:] = (orig_struct_loads * new_AOA / orig_AOA)[:] - - # now do super class solve_forward which will include - # transferring fixed aero loads to the new struct loads and then linear static solve - super(OnewayStructTrimDriver,self).solve_forward() - - # compute new lift values, for function name cl - for scenario in self.uncoupled_scenarios: - orig_cl = self.initial_trim_dict[scenario.name]['cl'] - orig_AOA = self.initial_trim_dict[scenario.name]['AOA'] - new_AOA = scenario.get_variable('AOA').value.real - - for func in scenario.functions: - if func.name == 'cl': - func.value = orig_cl * new_AOA / orig_AOA - - # composite functions are evaluated in the OptimizationManager FYI and will also be updated after this.. - - def solve_adjoint(self): - - # do super class solve_adjoint (same adjoint solve as before) - # since modified f_A is output of adjoint solve and not coupled... - # so doesn't matter really - super(OnewayStructTrimDriver,self).solve_adjoint() - - def _solve_steady_adjoint(self, scenario, bodies): - super()._solve_steady_adjoint(scenario, bodies) - - # get additional derivative terms for custom - self._get_custom_derivatives(scenario) - - def _solve_unsteady_adjoint(self, scenario, bodies): - super()._solve_unsteady_adjoint(scenario, bodies) - - # get additional derivative terms for custom - self._get_custom_derivatives(scenario) - - def _get_custom_derivatives(self, scenario): - """get custom trim derivatives, this is used in the """ - - orig_cl = self.initial_trim_dict[scenario.name]['cl'] - orig_AOA = self.initial_trim_dict[scenario.name]['AOA'] - aoa_var = scenario.get_variable('AOA') - - # since mass not adjoint function only iterate over these guys - adjoint_functions = [func for func in scenario.functions if func.adjoint] - for ifunc,func in enumerate(adjoint_functions): - if func.name == 'cl': - func.derivatives[aoa_var] = orig_cl / orig_AOA - continue - - # account for changing loads terms in AOA - AOA_deriv = 0.0 - for body in self.model.bodies: - struct_loads_ajp = body.get_struct_loads_ajp(scenario) - func_fs_ajp = struct_loads_ajp[:,ifunc] - orig_struct_loads = self._orig_struct_loads[scenario.name][body.name] - free_struct_loads = orig_struct_loads * 1.0 - - # this didn't change anything - # # temp - try to zero BCs at ext force locations? - # structural = self.solvers.structural - # assembler = structural.assembler - # ext_force = structural.ext_force - # ext_force_array = ext_force.getArray() - # ndof = assembler.getVarsPerNode() - # for i in range(3): - # # set only 0,1,2 into ext_force, then we will apply BCs to zero out dirichlet BCs - # ext_force_array[i::ndof] = free_struct_loads[i::3] - # assembler.setBCs(ext_force) # zero out forces at dirichlet BCs (since have no effect on structure) - # for i in range(3): - # free_struct_loads[i::3] = ext_force_array[i::ndof] - - AOA_deriv += np.dot(func_fs_ajp, free_struct_loads / orig_AOA) - - # add across all processors then reduce - global_derivative = self.comm.reduce(AOA_deriv, op=MPI.SUM, root=0) - global_derivative = self.comm.bcast(global_derivative) - - func.derivatives[aoa_var] = global_derivative \ No newline at end of file diff --git a/funtofem/driver/funtofem_shape_driver.py b/funtofem/driver/funtofem_shape_driver.py index 91062c70..70fd2261 100644 --- a/funtofem/driver/funtofem_shape_driver.py +++ b/funtofem/driver/funtofem_shape_driver.py @@ -215,7 +215,7 @@ def __init__( self._flow_solver_type = "fun3d" # TBD on new types else: # check with shape change - if fun3d_loader is not None and caps_loader is not None: + if fun3d_loader is not None: if isinstance(model.flow, Fun3dModel): self._flow_solver_type = "fun3d" self.flow_aim = model.flow.fun3d_aim diff --git a/funtofem/driver/modal_idf_driver.py b/funtofem/driver/modal_idf_driver.py deleted file mode 100644 index b2431f63..00000000 --- a/funtofem/driver/modal_idf_driver.py +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/env python -""" -This file is part of the package FUNtoFEM for coupled aeroelastic simulation -and design optimization. - -Copyright (C) 2015 Georgia Tech Research Corporation. -Additional copyright (C) 2015 Kevin Jacobson, Jan Kiviaho and Graeme Kennedy. -All rights reserved. - -FUNtoFEM is licensed under the Apache License, Version 2.0 (the "License"); -you may not use this software except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__all__ = ["FUNtoFEMmodalDriver"] - -import numpy as np -from mpi4py import MPI -from funtofem import TransferScheme -from ._funtofem_driver import FUNtoFEMDriver -from ..optimization.optimization_manager import OptimizationManager -from ..interface.utils.general_utils import real_norm, imag_norm - -try: - from .hermes_transfer import HermesTransfer -except: - pass - - -class FUNtoFEMmodalDriver(FUNtoFEMDriver): - def __init__( - self, - solvers, - comm_manager=None, - transfer_settings=None, - model=None, - debug=False, - reload_funtofem_states=False, - ): - """ - Driver that is based on FUNtoFEMnlbgs, but uses an IDF - implementation to solve the coupled problem and a modal - reconstruction of the structural deformation. - - Parameters - ---------- - solvers: SolverManager - the various disciplinary solvers - comm_manager: CommManager - manager for various discipline communicators - transfer_settings: TransferSettings - options of the load and displacement transfer scheme - model: :class:`~funtofem_model.FUNtoFEMmodel` - The model containing the design data - reload_funtofem_states: bool - whether to save and reload funtofem states - """ - - super(FUNtoFEMmodalDriver, self).__init__( - solvers, - comm_manager=comm_manager, - transfer_settings=transfer_settings, - model=model, - debug=debug, - reload_funtofem_states=reload_funtofem_states, - ) - - # initialize modal variables - for scenario in model.scenarios: - for body in model.bodies: - body.initialize_modal_variables(scenario) - - # TODO : initialize adjoint modal variables also - - return - - @property - def manager(self, hot_start: bool = False, write_designs: bool = True): - """ - Create an optimization manager object for this driver - """ - return OptimizationManager( - driver=self, - write_designs=write_designs, - hot_start=hot_start, - ) - - def _initialize_adjoint_variables(self, scenario, bodies): - """ - Initialize the adjoint variables stored in the body. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The scenario - bodies: :class:`~body.Body` - List of FUNtoFEM bodies. - """ - - for body in bodies: - body.initialize_adjoint_variables(scenario) - return - - def _solve_steady_forward(self, scenario, steps=None): - """ - Evaluate the aerothermoelastic forward analysis. Does *not* solve - the coupled problem. - Evaluation path for e.g., aeroelastic is D->A->L->S. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - steps: int - Number of iterations if not set by the model - """ - - assert scenario.steady - fail = 0 - - # Transfer modal displacements and temperatures from structure to aerodynamic mesh - for body in self.model.bodies: - # Get the modal coordinates on the structure mesh and compute the product - # to get effective disps. - body.convert_modal_struct_disps(scenario) - body.convert_modal_struct_temps(scenario) - # At this stage, we've recreated u_s and T_s - - body.transfer_disps(scenario) - body.transfer_temps(scenario) - - # Solve the flow problem - for step in range(1, scenario.steps + 1): - fail = self.solvers.flow.iterate(scenario, self.model.bodies, step) - - fail = self.comm.allreduce(fail) - if fail != 0: - if self.comm.Get_rank() == 0: - print("Flow solver returned fail flag.") - return fail - - # Transfer forces and heat fluxes from aerodynamic to structure mesh - for body in self.model.bodies: - body.transfer_loads(scenario) - body.transfer_heat_flux(scenario) - - # Solve the structure problem - fail = self.solvers.structural.iterate(scenario, self.model.bodies, step) - - fail = self.comm.allreduce(fail) - if fail != 0: - if self.comm.Get_rank() == 0: - print("Structural solver returned fail flag.") - return fail - - # Additional computation to transpose modal coordinate matrix - for body in self.model.bodies: - body.convert_modal_struct_disps_transpose(scenario) - body.convert_modal_struct_temps_transpose(scenario) - - return fail - - def _initialize_modal_adjoint_variables(self, scenario, bodies): - for body in bodies: - body.initialize_modal_adjoint_variables(scenario) - - def _solve_steady_adjoint(self, scenario): - """ - Evaluate the aerothermoelastic adjoint analysis. Does *not* solve - the coupled problem. - Evaluation path for e.g., aeroelastic is S^bar->L^bar->A^bar->D^bar. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - assert scenario.steady - fail = 0 - - self._initialize_modal_adjoint_variables(scenario, self.model.bodies) - - # Load the current state (is this correct still for modal driver?) - for body in self.model.bodies: - body.transfer_disps(scenario) - body.transfer_loads(scenario) - - body.transfer_temps(scenario) - body.transfer_heat_flux(scenario) - - # Initialize the adjoint variables - self._initialize_adjoint_variables(scenario, self.model.bodies) - - # somewhere also need to get initial modal disp ajp or something? - - # TODO : double check my preliminary code here - for body in self.model.bodies: - body.convert_modal_struct_disps_transpose_adjoint(scenario) - body.convert_modal_struct_temps_transpose_adjoint(scenario) - - # Take a step in the structural adjoint - fail = self.solvers.structural.iterate_adjoint( - scenario, self.model.bodies, step=0 - ) - - # Solve the flow adjoint - for step in range(1, scenario.adjoint_steps + 1): - # Get force and heat flux terms for flow solver - for body in self.model.bodies: - body.transfer_loads_adjoint(scenario) - body.transfer_heat_flux_adjoint(scenario) - - fail = self.solvers.flow.iterate_adjoint(scenario, self.model.bodies, step) - - fail = self.comm.allreduce(fail) - if fail != 0: - if self.comm.Get_rank() == 0: - print("Flow solver returned fail flag.") - return fail - - for body in self.model.bodies: - body.transfer_disps_adjoint(scenario) - body.transfer_temps_adjoint(scenario) - - body.convert_modal_struct_disps_adjoint(scenario) - body.convert_modal_struct_temps_adjoint(scenario) - - # are we then extracting coordinate derivatives at correct time step (doesn't actually use time step for steady case here) - steps = ( - scenario.adjoint_steps * scenario.adjoint_coupling_frequency - + scenario.post_tight_adjoint_steps - ) - self._extract_coordinate_derivatives(scenario, self.model.bodies, steps) - return 0 - - def _solve_unsteady_forward(self, scenario, steps=None): - """ - This function solves the unsteady forward problem using NLBGS without FSI subiterations - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - the current scenario - steps: int - number of time steps if not using the value defined in the scenario - - Returns - ------- - fail: int - fail flag for the coupled solver - - """ - - pass - - def _solve_unsteady_adjoint(self, scenario): - """ - Solves the unsteady adjoint problem using LBGS without FSI subiterations - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - the current scenario - steps: int - number of time steps - - Returns - ------- - fail: int - fail flag - - """ - - pass diff --git a/funtofem/driver/oneway_aero_driver.py b/funtofem/driver/oneway_aero_driver.py index 38dae603..a528edc0 100644 --- a/funtofem/driver/oneway_aero_driver.py +++ b/funtofem/driver/oneway_aero_driver.py @@ -74,7 +74,6 @@ class methods OnewayAeroDriver.remote and OnewayAeroDriver.analysis which build # --------------------------------------------------- # 1) FUN3D fun3d_loader = importlib.util.find_spec("fun3d") -caps_loader = importlib.util.find_spec("pyCAPS") if fun3d_loader is not None: # check whether we can import FUN3D from funtofem.interface import Fun3d14Interface, Fun3dModel @@ -164,11 +163,6 @@ def __init__( self.is_paired = is_paired self.external_shape = external_shape - # assert at least one uncoupled scenario - self.uncoupled_scenarios = [scenario for scenario in model.scenarios if not(scenario.coupled)] - any_uncoupled = any([not(scenario.coupled) for scenario in model.scenarios]) - assert any_uncoupled - # store the shape variables list self.shape_variables = [ var for var in self.model.get_variables() if var.analysis_type == "shape" @@ -252,7 +246,7 @@ def _initialize_funtofem(self): aero_root=comm_manager.aero_root, transfer_settings=self.transfer_settings, # using minimal settings since we don't use the state variables here (almost a dummy obj) ) - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: body.initialize_variables(scenario) body.initialize_adjoint_variables( scenario @@ -281,7 +275,7 @@ def solve_forward(self): ): # FUN3D mesh morphing initialize body nodes assert not (self.solvers.flow.auto_coords) self.solvers.flow._initialize_body_nodes( - self.uncoupled_scenarios[0], self.model.bodies + self.model.scenarios[0], self.model.bodies ) # initialize funtofem transfer data with new aero_nnodes size self._initialize_funtofem() @@ -316,11 +310,11 @@ def solve_forward(self): # run the FUN3D forward analysis with no shape change if self.steady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_steady_forward(scenario, self.model.bodies) if self.unsteady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_unsteady_forward(scenario, self.model.bodies) # Write sens file for remote to read. Analysis functions/derivatives are being written to a file @@ -381,11 +375,11 @@ def solve_adjoint(self): if not (self.is_remote): if self.steady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_steady_adjoint(scenario, self.model.bodies) if self.unsteady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_unsteady_adjoint(scenario, self.model.bodies) # write sens file for remote to read or if shape change all in one @@ -411,7 +405,7 @@ def solve_adjoint(self): self.flow_aim.post_analysis(sens_file_src) # store the shape variables in the function gradients - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._get_shape_derivatives(scenario) return @@ -548,7 +542,7 @@ def _setup_grid_filepaths(self): else: fun3d_dir = self.remote.main_dir grid_filepaths = [] - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: filepath = os.path.join( fun3d_dir, scenario.name, @@ -561,7 +555,7 @@ def _setup_grid_filepaths(self): # also setup the mapbc files mapbc_filepaths = [] - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: filepath = os.path.join( fun3d_dir, scenario.name, diff --git a/funtofem/driver/oneway_struct_driver.py b/funtofem/driver/oneway_struct_driver.py index bd70bb60..ca62a3e3 100644 --- a/funtofem/driver/oneway_struct_driver.py +++ b/funtofem/driver/oneway_struct_driver.py @@ -102,11 +102,6 @@ def __init__( self.struct_interface = solvers.structural self.struct_aim = None - # assert at least one uncoupled scenario - self.uncoupled_scenarios = [scenario for scenario in model.scenarios if not(scenario.coupled)] - any_uncoupled = any([not(scenario.coupled) for scenario in model.scenarios]) - assert any_uncoupled - # figure out which discipline solver we are using self._struct_solver_type = None if model.structural is None: @@ -150,7 +145,7 @@ def __init__( # initializing transfer schemes is the responsibility of drivers with aerodynamic analysis since they come first body.update_transfer() - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: # perform disps transfer first to prevent seg fault body.transfer_disps(scenario) body.transfer_temps(scenario) @@ -496,7 +491,7 @@ def _transfer_fixed_aero_loads(self): ) # zero the initial struct loads and struct flux for each scenario - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: # initialize new struct shape term for new ns nf = scenario.count_adjoint_functions() body.struct_shape_term[scenario.id] = np.zeros( @@ -591,11 +586,11 @@ def solve_forward(self): _start_forward_analysis = time.time() if self.steady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_steady_forward(scenario, self.model.bodies) if self.unsteady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_unsteady_forward(scenario, self.model.bodies) dt_forward = (time.time() - _start_forward_analysis) / 60.0 @@ -615,19 +610,20 @@ def solve_adjoint(self): # timing data _start_adjoint = time.time() + # run the adjoint structural analysis + functions = self.model.get_functions() + # Zero the derivative values stored in the function self._zero_derivatives() - - # zero adjoint data - if self.uses_tacs: - self._zero_adjoint_data() + for func in functions: + func.zero_derivatives() if self.steady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_steady_adjoint(scenario, self.model.bodies) if self.unsteady: - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._solve_unsteady_adjoint(scenario, self.model.bodies) dt_adjoint = (time.time() - _start_adjoint) / 60.0 @@ -639,6 +635,15 @@ def solve_adjoint(self): ) _start_derivatives = time.time() + # transfer loads adjoint since fa -> fs has shape dependency + if self.change_shape: + # TODO : for unsteady this part might have to be included before extract coordinate derivatives? + for body in self.model.bodies: + body.transfer_loads_adjoint(scenario) + + # call get function gradients to store the gradients from tacs + self.struct_interface.get_function_gradients(scenario, self.model.bodies) + if self.change_shape and not self.external_shape: # write the sensitivity file for the tacs AIM self.model.write_sensitivity_file( @@ -678,7 +683,7 @@ def solve_adjoint(self): self.comm.Barrier() # store the shape variables in the function gradients - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: self._get_shape_derivatives(scenario) dt_derivatives = (time.time() - _start_derivatives) / 60.0 @@ -701,10 +706,9 @@ def solve_adjoint(self): def _zero_derivatives(self): """zero all model derivatives""" - for scenario in self.uncoupled_scenarios: - for func in scenario.functions: - for var in self.model.get_variables(): - func.derivatives[var] = 0.0 + for func in self.model.get_functions(all=True): + for var in self.model.get_variables(): + func.derivatives[var] = 0.0 return def _extract_coordinate_derivatives(self, scenario, bodies, step): @@ -833,6 +837,10 @@ def _solve_steady_adjoint(self, scenario, bodies): Similar to funtofem_driver """ + # zero adjoint data + if self.uses_tacs: + self._zero_adjoint_data() + # set functions and variables self.struct_interface.set_variables(scenario, bodies) self.struct_interface.set_functions(scenario, bodies) @@ -847,18 +855,6 @@ def _solve_steady_adjoint(self, scenario, bodies): self._extract_coordinate_derivatives(scenario, bodies, step=0) self.struct_interface.post_adjoint(scenario, bodies) - # get derivatives while adjoint variables for this scenario are stored - # ------------------------------- - - # transfer loads adjoint since fa -> fs has shape dependency - if self.change_shape: - # TODO : for unsteady this part might have to be included before extract coordinate derivatives? - for body in self.model.bodies: - body.transfer_loads_adjoint(scenario) - - # call get function gradients to store the gradients from tacs - self.struct_interface.get_function_gradients(scenario, self.model.bodies) - return def _solve_unsteady_adjoint(self, scenario, bodies): @@ -886,19 +882,6 @@ def _solve_unsteady_adjoint(self, scenario, bodies): self.struct_interface.iterate_adjoint(scenario, bodies, step=step) self.struct_interface.post_adjoint(scenario, bodies) - # transfer loads adjoint since fa -> fs has shape dependency - if self.change_shape: - # TODO : for unsteady this part might have to be included before extract coordinate derivatives? - for body in self.model.bodies: - body.transfer_loads_adjoint(scenario) - - # call get function gradients to store the gradients from tacs - self.struct_interface.get_function_gradients(scenario, self.model.bodies) - - return - - def _get_custom_derivatives(self, scenario): - """get custom derivatives see custom/ drivers folder""" return def _zero_tacs_data(self): @@ -913,10 +896,17 @@ def _zero_tacs_data(self): self.struct_interface.ext_force.zeroEntries() self.struct_interface.update.zeroEntries() + # zero any scenario data + for scenario in self.model.scenarios: + # zero state data + u = self.struct_interface.scenario_data[scenario].u + u.zeroEntries() + self.struct_interface.assembler.setVariables(u) + def _zero_adjoint_data(self): if self.struct_interface.tacs_proc: # zero adjoint variable - for scenario in self.uncoupled_scenarios: + for scenario in self.model.scenarios: psi = self.struct_interface.scenario_data[scenario].psi for vec in psi: vec.zeroEntries() diff --git a/funtofem/interface/caps2fun/fun3d_aim.py b/funtofem/interface/caps2fun/fun3d_aim.py index 5311e1d8..29d45800 100644 --- a/funtofem/interface/caps2fun/fun3d_aim.py +++ b/funtofem/interface/caps2fun/fun3d_aim.py @@ -168,11 +168,6 @@ def set_config_parameter(self, param_name: str, value: float): if self.root_proc: self.geometry.cfgpmtr[param_name].value = value return - - def set_design_parameter(self, param_name: str, value: float): - if self.root_proc: - self.geometry.despmtr[param_name].value = value - return def get_config_parameter(self, param_name: str): value = None diff --git a/funtofem/interface/fun3d_14_interface.py b/funtofem/interface/fun3d_14_interface.py index f12e99ff..cf33c579 100644 --- a/funtofem/interface/fun3d_14_interface.py +++ b/funtofem/interface/fun3d_14_interface.py @@ -157,8 +157,6 @@ def _initialize_body_nodes(self, scenario, bodies): options = {} else: options = self.forward_options - if "store_full_stencil" in options and options["store_full_stencil"]: - interface.set_adjoint_stencil() self.fun3d_flow.setOptions(kwargs=options) self.fun3d_flow.initialize_data() @@ -793,8 +791,6 @@ def initialize_adjoint(self, scenario, bodies): options = self.adjoint_options self.fun3d_adjoint.initialize_project(comm=self.comm) - if "store_full_stencil" in options and options["store_full_stencil"]: - interface.set_adjoint_stencil() self.fun3d_adjoint.setOptions(kwargs=options) self.fun3d_adjoint.initialize_data() interface.design_initialize() @@ -892,11 +888,6 @@ def initialize_adjoint(self, scenario, bodies): self.dFdqinf = np.zeros(len(scenario.functions), dtype=TransferScheme.dtype) self.dHdq = np.zeros(len(scenario.functions), dtype=TransferScheme.dtype) - # set the store_Full_stencil if specified - #if self.adjoint_options is not None: - # if "store_full_stencil" in self.adjoint_options and self.adjoint_options["store_full_stencil"]: - # interface.set_adjoint_stencil() # custom routine to set store_full_stencil in fun3d - return 0 def get_last_adjoint_step(self): diff --git a/funtofem/interface/fun3d_interface.py b/funtofem/interface/fun3d_interface.py index 4993e583..c7c37174 100644 --- a/funtofem/interface/fun3d_interface.py +++ b/funtofem/interface/fun3d_interface.py @@ -756,11 +756,6 @@ def initialize_adjoint(self, scenario, bodies): ) shutil.copy2(src, dest) - # set the store_full_loop_krylov if specified - if self.adjoint_options is not None: - if "store_full_stencil" in self.adjoint_options and self.adjoint_options["store_full_stencil"]: - interface.set_adjoint_stencil() # custom routine to set store_full_stencil in fun3d - if scenario.steady: # Initialize FUN3D adjoint - special order for static adjoint if self.adjoint_options is None: diff --git a/funtofem/interface/tacs_interface.py b/funtofem/interface/tacs_interface.py index fabf947a..78a6db06 100644 --- a/funtofem/interface/tacs_interface.py +++ b/funtofem/interface/tacs_interface.py @@ -626,7 +626,6 @@ def get_function_gradients(self, scenario, bodies): for i, var in enumerate(self.struct_variables): # func.set_gradient_component(var, func_grad[ifunc][i]) func.add_gradient_component(var, func_grad[ifunc][i]) - # print(f"\tdoneget function gradients start", flush=True) return @@ -902,9 +901,6 @@ def initialize_adjoint(self, scenario, bodies): list of FUNtoFEM bodies """ - # this is kind of like updateAssemblerVars - self.set_variables(scenario, bodies) - if self.tacs_proc: # Initialize Aitken adjoint variables. if self.use_aitken: @@ -915,7 +911,6 @@ def initialize_adjoint(self, scenario, bodies): # Set the solution data for this scenario u = self.scenario_data[scenario].u - self.assembler.setBCs(u) self.assembler.setVariables(u) # Assemble the transpose of the Jacobian matrix for the adjoint @@ -935,7 +930,7 @@ def initialize_adjoint(self, scenario, bodies): # require their evaluation to store internal data before the sensitivities # can be computed. func_list = self.scenario_data[scenario].func_list - feval = self.assembler.evalFunctions(func_list) + self.assembler.evalFunctions(func_list) # Zero the vectors in the sensitivity list dfdu = self.scenario_data[scenario].dfdu @@ -1138,11 +1133,6 @@ def iterate_adjoint(self, scenario, bodies, step): self.thermal_index :: ndof ].astype(body.dtype) - # if ifunc == 1: - # print(f"func {scenario.functions[1].full_name}, struct loads ajp = {struct_loads_ajp}") - # print(f"any nan ? : {np.any(np.isnan(struct_loads_ajp))}") - # exit(0) - # print(f"done with iterate adjoint", flush=True) return fail @@ -1439,126 +1429,7 @@ def create_from_bdf( struct_loads_file=struct_loads_file, tacs_panel_dimensions=tacs_panel_dimensions, ) - - @classmethod - def create_modal_problem_from_bdf( - cls, - model, - comm, - nprocs, - bdf_file, - sigma:float, - num_eigs:int, - callback=None, - struct_options={}, - ): - """ - create a TACS modal problem from BDF file - for use in IDF analysis - - Parameters - ---------- - model: :class:`FUNtoFEMmodel` - The model class associated with the problem - comm: MPI.comm - MPI communicator (typically MPI_COMM_WORLD) - bdf_file: str - The BDF file name - sigma: float - guess for lowest eigenvalue of the structure - num_eigs: int - number of eigenvalues to solve for - callback: function - The element callback function for pyTACS - struct_options: dictionary - The options passed to pyTACS - """ - - # Split the communicator - world_rank = comm.Get_rank() - if world_rank < nprocs: - color = 1 - else: - color = MPI.UNDEFINED - tacs_comm = comm.Split(color, world_rank) - - modal_problem = None - if world_rank < nprocs: - # Create the assembler class - fea_assembler = pytacs.pyTACS(bdf_file, tacs_comm, options=struct_options) - - # get dict of struct DVs from the bodies and structural variables - # only supports thickness DVs for the structure currently - structDV_dict = {} - variables = model.get_variables() - structDV_names = [] - # Get the structural variables from the global list of variables. - struct_variables = [] - for var in variables: - if var.analysis_type == "structural": - struct_variables.append(var) - structDV_dict[var.name.lower()] = var.value - structDV_names.append(var.name.lower()) - - # use the default funtofem callback if none is provided - if callback is None: - include_thermal = any( - ["therm" in body.analysis_type for body in model.bodies] - ) - callback = f2f_callback( - fea_assembler, structDV_names, structDV_dict, include_thermal - ) - - # Set up constitutive objects and elements in pyTACS - fea_assembler.initialize(callback) - - modal_problem = fea_assembler.createModalProblem("modal", sigma, num_eigs) - - return modal_problem - - @classmethod - def make_modal_basis( - cls, - modal_problem, - num_modes:int, - body, - output_dir:str=None, - ): - """get the modal basis matrix for IDF approach using the modal analysis problem class in TACS""" - modal_problem.solve() - modal_problem.writeSolution(output_dir) - - num_eigs = modal_problem.getNumEigs() - # get first eigenvector to determine vars_per_node*nnodes - _,first_eigvec = modal_problem.getVariables(0) - nvars = first_eigvec.shape[0] - - vars_per_node = modal_problem.assembler.getVarsPerNode() - num_nodes = modal_problem.meshLoader.bdfInfo.nnodes - - # assumes for shell elements right now - # just the u,v,w for elastic - elastic_modal_basis = np.zeros((3*num_nodes, num_modes), dtype=body.dtype) - thermal_modal_basis = None - has_thermal_dof = vars_per_node > 6 - if has_thermal_dof: - thermal_modal_basis = np.zeros((num_nodes, num_modes), dtype=body.dtype) - - for imode in range(num_modes): - eigval,eigvec = modal_problem.getVariables(imode) - for i in range(3): - # print(f"{i=} {vars_per_node=} {imode=}") - elastic_modal_basis[i::3, imode] = eigvec[i::vars_per_node] - - if has_thermal_dof: - thermal_index = vars_per_node - 1 # for shell elements (can generalize later) - for imode in range(num_modes): - eigval,eigvec = modal_problem.getVariables(imode) - thermal_modal_basis[:, imode] = eigvec[thermal_index::vars_per_node] - - body.set_modal_basis(elastic_modal_basis, thermal_modal_basis) - return class TacsOutputGenerator: def __init__(self, prefix, name="tacs_output_file", f5=None): diff --git a/funtofem/model/body.py b/funtofem/model/body.py index 96fef189..fbbf39d9 100644 --- a/funtofem/model/body.py +++ b/funtofem/model/body.py @@ -246,15 +246,6 @@ def __init__( self.struct_heat_flux = {} self.struct_shape_term = {} - # modal states - self.modal_struct_disp_coords = {} - self.modal_struct_temp_coords = {} - self.num_modes = None - self.elastic_modal_basis = None - self.thermal_modal_basis = None - self.modal_struct_disp_coords_ajp = {} - self.modal_struct_temp_coords_ajp = {} - self._fixed_struct_loads = {} self._fixed_struct_heat_flux = {} @@ -706,9 +697,6 @@ def initialize_variables(self, scenario): ns = self.struct_nnodes na = self.aero_nnodes - # modal analysis states for the aerothermal case - self.modal_struct_temp_coords[scenario.id] = np.zeros(ns, dtype=self.dtype) - if scenario.steady: self.struct_heat_flux[scenario.id] = np.zeros(ns, dtype=self.dtype) self.aero_heat_flux[scenario.id] = np.zeros(na, dtype=self.dtype) @@ -732,249 +720,6 @@ def initialize_variables(self, scenario): self.aero_temps[id].append(np.zeros(na, dtype=self.dtype)) return - - def set_modal_basis(self, elastic_modal_basis:np.ndarray, thermal_modal_basis:np.ndarray): - # assume modal basis matrix is (dof_per_node * num_struct_nodes, num_modes) - self.num_modes = elastic_modal_basis.shape[1] - self.elastic_modal_basis = elastic_modal_basis - self.thermal_modal_basis = thermal_modal_basis - return - - def initialize_modal_variables(self, scenario): - """ - Initialize the modal variables each time we run an analysis. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - nm = self.num_modes - - # modal analysis states for aeroelastic case - if self.transfer is not None: - self.modal_struct_disp_coords[scenario.id] = np.zeros((nm,), dtype=self.dtype) - - # modal analysis states for the aerothermal case - if self.thermal_transfer is not None: - self.modal_struct_temp_coords[scenario.id] = np.zeros((nm,), dtype=self.dtype) - return - - def initialize_modal_adjoint_variables(self, scenario): - """ - Initialize the modal variables each time we run an analysis. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - nm = self.num_modes - nf = scenario.count_adjoint_functions() - - # modal analysis states for aeroelastic case - if self.transfer is not None: - self.modal_struct_disp_coords_ajp = np.zeros((nm, nf), dtype=self.dtype) - - # modal analysis states for the aerothermal case - if self.thermal_transfer is not None: - self.modal_struct_temp_coords_ajp = np.zeros((nm, nf), dtype=self.dtype) - return - - def get_modal_struct_disp_coords(self, scenario): - """ - Get the modal struct displacements for the scenario - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - if self.transfer is not None: - modal_struct_disps = self.modal_struct_disp_coords[scenario.id] - return modal_struct_disps - else: - return None - - def get_modal_struct_temp_coords(self, scenario): - """ - Get the modal struct displacements for the scenario - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - if self.transfer is not None: - modal_struct_temps = self.modal_struct_temp_coords[scenario.id] - return modal_struct_temps - else: - return None - - def convert_modal_struct_disps(self, scenario): - """ - Convert from modal displacements to full discplacements on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - if self.transfer is not None: - modal_uS = self.get_modal_struct_disp_coords(scenario) - full_uS = self.get_struct_disps(scenario) - modal_basis = self.elastic_modal_basis - full_uS[:] = modal_basis @ modal_uS - return - - def convert_modal_struct_temps(self, scenario): - """ - Convert from modal temperatures to full temperatures on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - if self.thermal_transfer is not None: - modal_tS = self.get_modal_struct_temp_coords(scenario) - full_tS = self.get_struct_temps(scenario) - modal_basis = self.elastic_modal_basis - full_tS[:] = modal_basis @ modal_tS - return - - def convert_modal_struct_disps_transpose(self, scenario): - """ - Convert from full displacements to modal discplacements on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - if self.transfer is not None: - modal_uS = self.get_modal_struct_disp_coords(scenario) - full_uS = self.get_struct_disps(scenario) - modal_basis = self.elastic_modal_basis - modal_uS[:] = modal_basis.T @ full_uS - return - - def convert_modal_struct_temps_transpose(self, scenario): - """ - Convert from full temperatures to modal temperatures on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - if self.thermal_transfer is not None: - modal_tS = self.get_modal_struct_temp_coords(scenario) - full_tS = self.get_struct_temps(scenario) - modal_basis = self.elastic_modal_basis - modal_tS[:] = modal_basis.T @ full_tS - return - - def convert_modal_struct_disps_adjoint(self, scenario): - """ - Convert from full disp adjoint => modal disp adjoint on the structure - for the given scenario, adjoint - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - if self.transfer is None: return - - modal_uS_bar = self.modal_struct_disp_coords_ajp - full_uS_bar = self.struct_disps_ajp - modal_basis = self.elastic_modal_basis - - # forward analysis was full_uS = modal_basis @ modal_uS - # adjoint analysis is modal_uS_bar = modal_basis.T @ full_uS_bar - nfunc = scenario.count_adjoint_functions() - for ifunc in range(nfunc): - modal_uS_bar[:,ifunc] = modal_basis.T @ full_uS_bar[:,ifunc] - return - - def convert_modal_struct_temps_adjoint(self, scenario): - """ - Convert from modal temperatures to full temperatures on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - if self.thermal_transfer is None: return - - modal_tS_bar = self.modal_struct_temp_coords_ajp - full_tS_bar = self.struct_temps_ajp - modal_basis = self.thermal_modal_basis - - # forward analysis was full_tS = modal_basis @ modal_tS - # adjoint analysis is modal_tS_bar = modal_basis.T @ full_tS_bar - nfunc = scenario.count_adjoint_functions() - for ifunc in range(nfunc): - modal_tS_bar[:,ifunc] = modal_basis.T @ full_tS_bar[:,ifunc] - return - - def convert_modal_struct_disps_transpose_adjoint(self, scenario): - """ - Convert from full displacements to modal discplacements on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - - if self.transfer is None: return - - modal_uS_bar = self.modal_struct_disp_coords_ajp - full_uS_bar = self.struct_disps_ajp - modal_basis = self.elastic_modal_basis - - # forward analysis was modal_uS = modal_basis.T @ full_uS - # adjoint analysis is full_uS_bar = modal_basis @ modal_uS_bar - nfunc = scenario.count_adjoint_functions() - for ifunc in range(nfunc): - full_uS_bar[:,ifunc] = modal_basis @ modal_uS_bar[:,ifunc] - return - - def convert_modal_struct_temps_transpose_adjoint(self, scenario): - """ - Convert from full temperatures to modal temperatures (adjoint for that) on the structure - for the given scenario. - - Parameters - ---------- - scenario: :class:`~scenario.Scenario` - The current scenario - """ - if self.thermal_transfer is None: return - - modal_tS_bar = self.modal_struct_temp_coords_ajp - full_tS_bar = self.struct_temps_ajp - modal_basis = self.thermal_modal_basis - - # forward analysis was modal_tS = modal_basis.T @ full_tS - # adjoint analysis is full_tS_bar = modal_basis @ modal_tS_bar - nfunc = scenario.count_adjoint_functions() - for ifunc in range(nfunc): - full_tS_bar[:,ifunc] = modal_basis @ modal_tS_bar[:,ifunc] - return def initialize_adjoint_variables(self, scenario): """ diff --git a/funtofem/model/funtofem_model.py b/funtofem/model/funtofem_model.py index 3cc47086..7e1a95fe 100644 --- a/funtofem/model/funtofem_model.py +++ b/funtofem/model/funtofem_model.py @@ -507,34 +507,25 @@ def _read_aero_loads(self, comm, filename, root=0): scenario_data = None loads_data = {} mesh_data = {} - valid_scenario = False with open(filename, "r") as fp: for line in fp.readlines(): entries = line.strip().split(" ") # print("==> entries: ", entries) if len(entries) == 2: - # allow subset of scenarios for now - # assert int(entries[1]) == len(self.scenarios) + assert int(entries[1]) == len(self.scenarios) assert int(entries[0]) == len(self.bodies) elif len(entries) == 3 and entries[0] == "scenario": - - # save old scenario data - if scenario_data is not None: - loads_data[scenario.id] = scenario_data - matching_scenario = False for scenario in self.scenarios: if str(scenario.name).strip() == str(entries[2]).strip(): matching_scenario = True break - valid_scenario = matching_scenario - # assert matching_scenario - - # reset the scenario data + assert matching_scenario + if scenario_data is not None: + loads_data[scenario.id] = scenario_data scenario_data = [] - elif len(entries) == 4 and entries[0] == "body_mesh": body_name = entries[2] mesh_data[body_name] = {"aeroID": [], "aeroX": []} @@ -542,7 +533,7 @@ def _read_aero_loads(self, comm, filename, root=0): mesh_data[body_name]["aeroID"] += [int(entries[0])] mesh_data[body_name]["aeroX"] += entries[1:4] - elif len(entries) == 5 and valid_scenario: + elif len(entries) == 5: entry = { "bodyName": body_name, "aeroID": int(entries[0]), @@ -551,14 +542,7 @@ def _read_aero_loads(self, comm, filename, root=0): } scenario_data.append(entry) - # get the last scenario as well - if valid_scenario: - loads_data[scenario.id] = scenario_data - - # debug - # for scenario in self.scenarios: - # print(f"loads_data[{scenario.id}] = {loads_data[scenario.id][0]}") - # exit() + loads_data[scenario.id] = scenario_data loads_data = comm.bcast(loads_data, root=root) mesh_data = comm.bcast(mesh_data, root=root) diff --git a/funtofem/model/scenario.py b/funtofem/model/scenario.py index 4f7b0841..a19de897 100644 --- a/funtofem/model/scenario.py +++ b/funtofem/model/scenario.py @@ -47,7 +47,6 @@ def __init__( fun3d=True, steps=1000, uncoupled_steps=0, - coupled=True, adjoint_steps=None, min_forward_steps=50, min_adjoint_steps=None, @@ -149,7 +148,6 @@ def __init__( self.variables = {} self.functions = [] - self.coupled = coupled self.steady = steady self.steps = steps self.forward_coupling_frequency = forward_coupling_frequency @@ -209,7 +207,6 @@ def steady( cls, name: str, steps: int, - coupled:bool=True, uncoupled_steps: int = 0, forward_coupling_frequency: int = 1, adjoint_coupling_frequency: int = 1, @@ -221,7 +218,6 @@ def steady( name=name, steady=True, steps=steps, - coupled=coupled, forward_coupling_frequency=forward_coupling_frequency, adjoint_steps=adjoint_steps, adjoint_coupling_frequency=adjoint_coupling_frequency, @@ -235,7 +231,6 @@ def unsteady( cls, name: str, steps: int, - coupled:bool=True, uncoupled_steps: int = 0, tacs_integration_settings=None, ): @@ -243,7 +238,6 @@ def unsteady( name=name, steady=False, steps=steps, - coupled=coupled, tacs_integration_settings=tacs_integration_settings, uncoupled_steps=uncoupled_steps, ) diff --git a/funtofem/optimization/optimization_manager.py b/funtofem/optimization/optimization_manager.py index d1507260..fe8f6512 100644 --- a/funtofem/optimization/optimization_manager.py +++ b/funtofem/optimization/optimization_manager.py @@ -48,7 +48,7 @@ def __init__( debug: bool = False, sparse: bool = True, write_checkpoints=False, - plot_hist=False, + plot_hist=True, ): """ Constructs the optimization manager class using a funtofem model and driver @@ -177,7 +177,7 @@ def _gatekeeper(self, x_dict): if self.comm.rank == 0 and self.write_designs: if self.sparse: # all vars in same group regular_dict = { - var.full_name: float(x_dict[self.SPARSE_VARS_GROUP][ivar]) + var.name: float(x_dict[self.SPARSE_VARS_GROUP][ivar]) for ivar, var in enumerate(self.model.get_variables(optim=True)) } else: diff --git a/tests/unit_tests/framework/test_modal_driver.py b/tests/unit_tests/framework/test_modal_driver.py deleted file mode 100644 index f16946d7..00000000 --- a/tests/unit_tests/framework/test_modal_driver.py +++ /dev/null @@ -1,151 +0,0 @@ -import os -from tacs import TACS -from mpi4py import MPI -import numpy as np -from funtofem import TransferScheme - -from funtofem.model import FUNtoFEMmodel, Variable, Scenario, Body, Function -from funtofem.interface import ( - TestAerodynamicSolver, - TacsInterface, - TacsSteadyInterface, - SolverManager, - TestResult, - make_test_directories, -) -from funtofem.driver import TransferSettings, FUNtoFEMmodalDriver - -from _bdf_test_utils import elasticity_callback, thermoelasticity_callback -import unittest - -np.random.seed(123456) - -base_dir = os.path.dirname(os.path.abspath(__file__)) -bdf_filename = os.path.join(base_dir, "input_files", "test_bdf_file.bdf") - - -complex_mode = TransferScheme.dtype == complex and TACS.dtype == complex -nprocs = 1 -comm = MPI.COMM_WORLD -elastic_scheme = "rbf" - -results_folder, output_dir = make_test_directories(comm, base_dir) - - -class TestModalIDFDriver(unittest.TestCase): - FILENAME = "testmodal-idf-driver.txt" - FILEPATH = os.path.join(results_folder, FILENAME) - - def test_modal_analysis(self): - # Build the model - model = FUNtoFEMmodel("wedge") - plate = Body.aeroelastic("plate") - Variable.structural("thickness", value=1.0).register_to(plate) - plate.register_to(model) - - # Create a scenario to run - Scenario.steady("test", steps=150).register_to(model) - - # Build the solver interfaces - solvers = SolverManager(comm) - solvers.structural = TacsInterface.create_from_bdf( - model, - comm, - nprocs, - bdf_filename, - callback=elasticity_callback, - output_dir=output_dir, - ) - solvers.flow = TestAerodynamicSolver(comm, model) - - # run modal analysis now - modal_problem = TacsSteadyInterface.create_modal_problem_from_bdf( - model, - comm, - nprocs, - bdf_filename, - sigma=1.0, - num_eigs=10, - callback=elasticity_callback, - ) - TacsSteadyInterface.make_modal_basis( - modal_problem, num_modes=10, body=plate, output_dir="" - ) - - def test_modal_driver(self): - # Build the model - model = FUNtoFEMmodel("wedge") - plate = Body.aeroelastic("plate") - Variable.structural("thickness", value=1.0).register_to(plate) - plate.register_to(model) - - # Create a scenario to run - steady = Scenario.steady("test", steps=150) - Function.ksfailure().register_to(steady) - Function.test_aero().register_to(steady) - steady.register_to(model) - - # Build the solver interfaces - solvers = SolverManager(comm) - solvers.structural = TacsInterface.create_from_bdf( - model, - comm, - nprocs, - bdf_filename, - callback=elasticity_callback, - output_dir=output_dir, - ) - solvers.flow = TestAerodynamicSolver(comm, model) - - # run prelim modal analysis so that we set in the modal basis for each body - # (in this case just one) - modal_problem = TacsSteadyInterface.create_modal_problem_from_bdf( - model, - comm, - nprocs, - bdf_filename, - sigma=1.0, - num_eigs=10, - callback=elasticity_callback, - ) - TacsSteadyInterface.make_modal_basis( - modal_problem, num_modes=10, body=plate, output_dir="" - ) - - # create the modal driver - - # this just tests whether it runs first.. - # TODO : test it with derivatives and functionals - modal_driver = FUNtoFEMmodalDriver( - solvers, - transfer_settings=TransferSettings(npts=10, elastic_scheme=elastic_scheme), - model=model, - ) - modal_driver.solve_forward() - modal_driver.solve_adjoint() - - # ( - # solvers, - # transfer_settings=TransferSettings(npts=10, elastic_scheme=elastic_scheme), - # model=model, - # ) - - # epsilon = 1e-30 if complex_mode else 1e-5 - # rtol = 1e-9 if complex_mode else 1e-3 - # max_rel_error = TestResult.derivative_test( - # "tacs+testaero-aeroelastic", - # model, - # driver, - # self.FILEPATH, - # complex_mode, - # epsilon, - # ) - # self.assertTrue(max_rel_error < rtol) - - return - - -if __name__ == "__main__": - if comm.rank == 0: - open(TestModalIDFDriver.FILEPATH, "w").close() # clear file - unittest.main()