Skip to content

Commit 16e6486

Browse files
FUNtoFEM read and write unsteady aero loads files (#289)
* working unsteady aero loads files * black reformat
1 parent 7231ae1 commit 16e6486

File tree

4 files changed

+307
-29
lines changed

4 files changed

+307
-29
lines changed

funtofem/driver/oneway_struct_driver.py

+113-12
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,86 @@ def prime_loads(
257257
timing_file=timing_file,
258258
)
259259

260+
@classmethod
261+
def prime_loads_from_unsteady_files(
262+
cls,
263+
files: list,
264+
solvers,
265+
model,
266+
nprocs,
267+
transfer_settings,
268+
external_shape=False,
269+
init_transfer=False,
270+
timing_file=None,
271+
):
272+
"""
273+
Used to prime aero loads for optimization over tacs analysis with shape change and tacs aim
274+
Built from an aero loads file of a previous CFD analysis in FuntofemNlbgs driver (TODO : make uncoupled fluid solver features)
275+
The loads file is written from the FUNtoFEM model after a forward analysis of a flow solver
276+
277+
Parameters
278+
----------
279+
filename : str or path to aero loads file
280+
the filepath of the aerodynamic loads file from a previous CFD analysis (written from FUNtoFEM model)
281+
solvers: :class:`~interface.SolverManager`
282+
Solver Interface for CFD such as Fun3dInterface, Su2Interface, or test aero solver
283+
model : :class:`~model.FUNtoFEMmodel
284+
nprocs: int
285+
Number of processes that TACS is running on.
286+
transfer_settings: :class:`~transfer_settings.TransferSettings`
287+
Settings for transfer of state variables across aero and struct meshes
288+
struct_aim: `caps2tacs.TacsAim`
289+
Interface object from TACS to ESP/CAPS, wraps the tacsAIM object.
290+
external_shape: bool
291+
whether the tacs aim shape analysis is performed outside this class
292+
timing_file: str or path
293+
path to funtofem timing file statistics
294+
"""
295+
comm = solvers.comm
296+
world_rank = comm.Get_rank()
297+
if world_rank < nprocs:
298+
color = 1
299+
else:
300+
color = MPI.UNDEFINED
301+
tacs_comm = comm.Split(color, world_rank)
302+
303+
# initialize transfer settings
304+
comm_manager = solvers.comm_manager
305+
306+
for itime, file in enumerate(files):
307+
# read in the loads from the file for each time step
308+
loads_data = model._read_aero_loads(comm, file)
309+
310+
# initialize the transfer scheme then distribute aero loads
311+
for body in model.bodies:
312+
if (
313+
itime == 0
314+
): # only initialize transfer and scenario data on the first time step after the aero mesh is loaded
315+
body.initialize_transfer(
316+
comm=comm,
317+
struct_comm=tacs_comm,
318+
struct_root=comm_manager.struct_root,
319+
aero_comm=comm_manager.aero_comm,
320+
aero_root=comm_manager.aero_root,
321+
transfer_settings=transfer_settings,
322+
)
323+
for scenario in model.scenarios:
324+
assert not scenario.steady
325+
body.initialize_variables(scenario)
326+
327+
body._distribute_aero_loads(loads_data, steady=False, itime=itime)
328+
329+
tacs_driver = cls(
330+
solvers,
331+
model,
332+
nprocs=nprocs,
333+
external_shape=external_shape,
334+
timing_file=timing_file,
335+
)
336+
if init_transfer:
337+
tacs_driver._transfer_fixed_aero_loads()
338+
return tacs_driver
339+
260340
@classmethod
261341
def prime_loads_from_file(
262342
cls,
@@ -304,7 +384,7 @@ def prime_loads_from_file(
304384
comm_manager = solvers.comm_manager
305385

306386
# read in the loads from the file
307-
loads_data = model.read_aero_loads(comm, filename)
387+
loads_data = model._read_aero_loads(comm, filename)
308388

309389
# initialize the transfer scheme then distribute aero loads
310390
for body in model.bodies:
@@ -318,7 +398,8 @@ def prime_loads_from_file(
318398
)
319399
for scenario in model.scenarios:
320400
body.initialize_variables(scenario)
321-
body._distribute_aero_loads(loads_data)
401+
assert scenario.steady
402+
body._distribute_aero_loads(loads_data, steady=True)
322403

323404
tacs_driver = cls(
324405
solvers,
@@ -418,16 +499,36 @@ def _transfer_fixed_aero_loads(self):
418499
)
419500

420501
# initialize new elastic struct vectors
421-
if body.transfer is not None:
422-
body.struct_loads[scenario.id] = np.zeros(3 * ns, dtype=dtype)
423-
body.struct_disps[scenario.id] = np.zeros(3 * ns, dtype=dtype)
424-
425-
# initialize new struct heat flux
426-
if body.thermal_transfer is not None:
427-
body.struct_heat_flux[scenario.id] = np.zeros(ns, dtype=dtype)
428-
body.struct_temps[scenario.id] = (
429-
np.ones(ns, dtype=dtype) * scenario.T_ref
430-
)
502+
if scenario.steady:
503+
if body.transfer is not None:
504+
body.struct_loads[scenario.id] = np.zeros(3 * ns, dtype=dtype)
505+
body.struct_disps[scenario.id] = np.zeros(3 * ns, dtype=dtype)
506+
507+
# initialize new struct heat flux
508+
if body.thermal_transfer is not None:
509+
body.struct_heat_flux[scenario.id] = np.zeros(ns, dtype=dtype)
510+
body.struct_temps[scenario.id] = (
511+
np.ones(ns, dtype=dtype) * scenario.T_ref
512+
)
513+
514+
else: # unsteady
515+
if body.transfer is not None:
516+
body.struct_loads[scenario.id] = [
517+
np.zeros(3 * ns, dtype=dtype) for _ in range(scenario.steps)
518+
]
519+
body.struct_disps[scenario.id] = [
520+
np.zeros(3 * ns, dtype=dtype) for _ in range(scenario.steps)
521+
]
522+
523+
# initialize new struct heat flux
524+
if body.thermal_transfer is not None:
525+
body.struct_heat_flux[scenario.id] = [
526+
np.zeros(ns, dtype=dtype) for _ in range(scenario.steps)
527+
]
528+
body.struct_temps[scenario.id] = [
529+
(np.ones(ns, dtype=dtype) * scenario.T_ref)
530+
for _ in range(scenario.steps)
531+
]
431532

432533
# transfer disps to prevent seg fault if coming from OnewayAeroDriver
433534
body.transfer_disps(scenario)

funtofem/model/body.py

+41-14
Original file line numberDiff line numberDiff line change
@@ -1594,11 +1594,11 @@ def aitken_adjoint_relax(self, comm, scenario, tol=1e-16):
15941594

15951595
return
15961596

1597-
def _distribute_aero_loads(self, data):
1597+
def _distribute_aero_loads(self, data, steady: bool = True, itime: int = 0):
15981598
"""
15991599
distribute the aero loads and heat flux from a loads file
16001600
"""
1601-
print(f"F2F - starting to distribute loads")
1601+
print(f"F2F - starting to distribute loads Time Step {itime}")
16021602

16031603
for scenario_id in data:
16041604
scenario_data = data[scenario_id]
@@ -1613,16 +1613,31 @@ def _distribute_aero_loads(self, data):
16131613
}
16141614

16151615
for ind, aero_id in enumerate(self.aero_id):
1616-
if self.transfer is not None:
1617-
self.aero_loads[scenario_id][3 * ind : 3 * ind + 3] = (
1618-
scenario_entry_dict[aero_id]["load"]
1619-
)
1620-
if self.thermal_transfer is not None:
1621-
self.aero_heat_flux[scenario_id][ind] = scenario_entry_dict[
1622-
aero_id
1623-
]["hflux"]
1616+
if steady:
1617+
if self.transfer is not None:
1618+
self.aero_loads[scenario_id][3 * ind : 3 * ind + 3] = (
1619+
scenario_entry_dict[aero_id]["load"]
1620+
)
1621+
if self.thermal_transfer is not None:
1622+
self.aero_heat_flux[scenario_id][ind] = scenario_entry_dict[
1623+
aero_id
1624+
]["hflux"]
1625+
1626+
else: # unsteady
1627+
if self.transfer is not None:
1628+
# make sure this time index exists in the case of scenarios with different # of time steps
1629+
if itime < len(self.aero_loads[scenario_id]):
1630+
self.aero_loads[scenario_id][itime][
1631+
3 * ind : 3 * ind + 3
1632+
] = scenario_entry_dict[aero_id]["load"]
1633+
if self.thermal_transfer is not None:
1634+
# make sure this time index exists in the case of scenarios with different # of time steps
1635+
if itime < len(self.aero_heat_flux[scenario_id]):
1636+
self.aero_heat_flux[scenario_id][itime][ind] = (
1637+
scenario_entry_dict[aero_id]["hflux"]
1638+
)
16241639

1625-
print(f"F2F - done distribute loads")
1640+
print(f"\tF2F - done distribute loads Time step {itime}")
16261641

16271642
def _collect_aero_mesh(self, comm, root=0):
16281643
"""
@@ -1658,18 +1673,30 @@ def _collect_aero_mesh(self, comm, root=0):
16581673

16591674
return aero_ids, aero_X
16601675

1661-
def _collect_aero_loads(self, comm, scenario, root=0):
1676+
def _collect_aero_loads(self, comm, scenario, itime: int = 0, root=0):
16621677
"""
16631678
gather the aerodynamic load and heat flux from each MPI processor onto the root
16641679
Then return the global aero ids, heat fluxes, and loads, which are later written to a file
16651680
"""
1681+
if itime >= scenario.steps:
1682+
return [], [], []
16661683
all_aero_ids = comm.gather(self.aero_id, root=root)
16671684
if self.transfer is not None:
1668-
all_aero_loads = comm.gather(self.aero_loads[scenario.id], root=root)
1685+
_loads = (
1686+
self.aero_loads[scenario.id]
1687+
if scenario.steady
1688+
else self.aero_loads[scenario.id][itime]
1689+
)
1690+
all_aero_loads = comm.gather(_loads, root=root)
16691691
else:
16701692
all_aero_loads = []
16711693
if self.thermal_transfer is not None:
1672-
all_aero_hflux = comm.gather(self.aero_heat_flux[scenario.id], root=root)
1694+
_hflux = (
1695+
self.aero_heat_flux[scenario.id]
1696+
if scenario.steady
1697+
else self.aero_heat_flux[scenario.id][itime]
1698+
)
1699+
all_aero_hflux = comm.gather(_hflux, root=root)
16731700
else:
16741701
all_aero_hflux = []
16751702

funtofem/model/funtofem_model.py

+38-3
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def evaluate_composite_functions(self, compute_grad=True):
414414
composite_func.evaluate_gradient()
415415
return
416416

417-
def read_aero_loads(self, comm, filename, root=0):
417+
def _read_aero_loads(self, comm, filename, root=0):
418418
"""
419419
Read the aerodynamic loads file for the OnewayStructDriver.
420420
@@ -573,7 +573,20 @@ def write_struct_loads(self, comm, filename, root=0):
573573
fp.write(data)
574574
return
575575

576-
def write_aero_loads(self, comm, filename, root=0):
576+
@classmethod
577+
def _get_loads_filename(cls, prefix, itime: int, suffix=".txt", padding=3):
578+
"""routine to get default padded 0 aero loads filenames"""
579+
return prefix + "_" + f"%0{padding}d" % itime + suffix
580+
581+
def get_loads_files(self, prefix, suffix=".txt"):
582+
"""get a list of the loads files for this unsteady scenario"""
583+
max_steps = max([scenario.steps for scenario in self.scenarios])
584+
return [
585+
FUNtoFEMmodel._get_loads_filename(prefix, itime=itime, suffix=suffix)
586+
for itime in range(max_steps)
587+
]
588+
589+
def write_aero_loads(self, comm, filename, itime=0, root=0):
577590
"""
578591
Write the aerodynamic loads file for the OnewayStructDriver.
579592
@@ -599,6 +612,8 @@ def write_aero_loads(self, comm, filename, root=0):
599612
Global communicator across all FUNtoFEM processors
600613
filename: str
601614
The name of the file to be generated
615+
itime: int
616+
The time step of the loads to write out (irrelevant if steady)
602617
root: int
603618
The rank of the processor that will write the file
604619
"""
@@ -630,7 +645,9 @@ def write_aero_loads(self, comm, filename, root=0):
630645
data += f"scenario {scenario.id} {scenario.name} \n"
631646

632647
for body in self.bodies:
633-
id, hflux, load = body._collect_aero_loads(comm, scenario, root=root)
648+
id, hflux, load = body._collect_aero_loads(
649+
comm, scenario, itime=itime, root=root
650+
)
634651

635652
if comm.rank == root:
636653
data += f"body {body.id} {body.name} {body.aero_nnodes} \n"
@@ -647,6 +664,24 @@ def write_aero_loads(self, comm, filename, root=0):
647664
fp.write(data)
648665
return
649666

667+
def write_unsteady_aero_loads(self, comm, prefix, suffix=".txt", root=0):
668+
"""
669+
Write the aerodynamic loads files for unsteady scenarios for the OnewayStructDriver.
670+
671+
Parameters
672+
----------
673+
comm: MPI communicator
674+
Global communicator across all FUNtoFEM processors
675+
prefix: str
676+
file prefix
677+
root: int
678+
The rank of the processor that will write the file
679+
"""
680+
loads_files = self.get_loads_files(prefix, suffix)
681+
for itime, load_file in enumerate(loads_files):
682+
self.write_aero_loads(comm, load_file, itime=itime, root=root)
683+
return loads_files
684+
650685
def write_sensitivity_file(
651686
self, comm, filename, discipline="aerodynamic", root=0, write_dvs: bool = True
652687
):

0 commit comments

Comments
 (0)