diff --git a/examples/temperature_final_part/input.yaml b/examples/temperature_final_part/input.yaml new file mode 100644 index 00000000..208afc0c --- /dev/null +++ b/examples/temperature_final_part/input.yaml @@ -0,0 +1,19 @@ +steps: +- macroadditivefoam: + class: temperature_final_part + application: additivefoam + execute: + coarse: 0.5e-3 + pad-xy: 5e-3 + pad-sub: 12.7e-3 + np: 24 + n-cells-per-layer: 1 + layer-time: 5 +data: + build: + datatype: Peregrine + name: myna_output + path: .. + parts: + P5: + layers: [51, 52] diff --git a/src/myna/application/additivefoam/additivefoam.py b/src/myna/application/additivefoam/additivefoam.py index 2dd1bff0..ab536cc9 100644 --- a/src/myna/application/additivefoam/additivefoam.py +++ b/src/myna/application/additivefoam/additivefoam.py @@ -90,7 +90,7 @@ def __init__( help="Multiple by which to scale the STL file dimensions (default = 0.001, mm -> m)", ) - self.args = self.parser.parse_args() + self.args, _ = self.parser.parse_known_args() super().set_procs() super().check_exe( @@ -100,6 +100,7 @@ def __init__( def update_template_path(self): """Updates the template path parameter""" + print(self.args.template) if self.args.template is None: template_path = os.path.join( os.environ["MYNA_APP_PATH"], @@ -108,6 +109,7 @@ def update_template_path(self): "template", ) self.args.template = template_path + print(self.args.template) def copy_template_to_dir(self, target_dir): """Copies the specified template directory to the specified target directory""" @@ -182,6 +184,21 @@ def update_material_properties(self, case_dir): + f' -set "{absorption}" {case_dir}/constant/heatSourceDict' ) + def get_part_resource_template_dir(self, part): + """Provides the path to the template directory in the myna_resources folder + + Args: + part: The name of the part + """ + return os.path.join( + os.path.dirname(self.input_file), + "myna_resources", + part, + "additivefoam", + self.simulation_type, + "template", + ) + def get_region_resource_template_dir(self, part, region): """Provides the path to the template directory in the myna_resources folder @@ -311,7 +328,7 @@ def update_region_start_and_end_times(self, case_dir, bb_dict, scanpath_name): end_time = np.round(end_time, 5) self.update_start_and_end_times(case_dir, start_time, end_time) - def update_start_and_end_times(self, case_dir, start_time, end_time): + def update_start_and_end_times(self, case_dir, start_time, end_time, n_write=2): """Updates the case to adjust the start and end time by adjusting:" - start and end times of the simulation in system/controlDict @@ -322,6 +339,7 @@ def update_start_and_end_times(self, case_dir, start_time, end_time): case_dir: case directory to update start_time: start time of the simulation end_time: end time of the simulation + n_write: number of times to write output (must be > 0) """ os.system( f"foamDictionary -entry startTime -set {start_time} " @@ -332,14 +350,20 @@ def update_start_and_end_times(self, case_dir, start_time, end_time): + f"{case_dir}/system/controlDict" ) os.system( - f"foamDictionary -entry writeInterval -set {np.round(0.5 * (end_time - start_time), 5)} " + f"foamDictionary -entry writeInterval -set {np.round((1 / n_write) * (end_time - start_time), 8)} " + f"{case_dir}/system/controlDict" ) source = os.path.abspath(os.path.join(case_dir, "0")) - target = os.path.abspath(os.path.join(case_dir, f"{start_time}")) - if os.path.exists(target): - shutil.rmtree(target) - shutil.move(source, target) + target = os.path.abspath( + os.path.join( + case_dir, + f"{int(start_time) if float(start_time).is_integer() else start_time}", + ) + ) + if target != source: + if os.path.exists(target): + shutil.rmtree(target) + shutil.move(source, target) def update_heatsource_scanfile(self, case_dir, scanpath_name): """Updates the heatSourceDict to point to the specified scan path file diff --git a/src/myna/application/additivefoam/path.py b/src/myna/application/additivefoam/path.py index 33988e34..c2e05bac 100644 --- a/src/myna/application/additivefoam/path.py +++ b/src/myna/application/additivefoam/path.py @@ -7,34 +7,75 @@ # License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause. # import pandas as pd +import polars as pl -def convert_peregrine_scanpath(filename, export_path, power=1): - """converts peregrine scan path units to additivefoam scan path units""" - df = pd.read_csv(filename, sep="\s+") +def convert_peregrine_scanpath(input_file, output_file, power=1): + """Convert Myna scan path to an AdditiveFOAM-compatible scan path - # convert X & Y distances to meters - df["X(m)"] = df["X(mm)"] * 1e-3 - df["Y(m)"] = df["Y(mm)"] * 1e-3 + Args: + input_file: Myna scan path + output_file: AdditiveFOAM scan path to write + power: nominal power of the laser (default 1 makes equivalent to Myna "Pmod") + """ - # set Z value to zero - df["Z(m)"] = df["Z(mm)"] * 0 + data = pl.read_csv(input_file, separator="\t") + data = data.rename( + { + "X(mm)": "X(mm)", + "Y(mm)": "Y(mm)", + "Z(mm)": "Z(mm)", + "Pmod": "Pmod", + "tParam": "tParam", + } + ) + data = data.with_columns( + [ + (pl.col("X(mm)") / 1000.0).alias("X(m)"), # Convert to meters + (pl.col("Y(mm)") / 1000.0).alias("Y(m)"), # Convert to meters + pl.lit(0.0).alias("Z(m)"), # Set Z to zero + (pl.col("Pmod") * power).alias("Power"), # Convert to Watts + ] + ).rename( + {"tParam": "Parameter"} + ) # Rename to Parameter + data = data.select(["Mode", "X(m)", "Y(m)", "Z(m)", "Power", "Parameter"]) + data.write_csv(output_file, separator="\t") - # format columns - round_cols = ["X(m)", "Y(m)", "Z(m)"] - df[round_cols] = df[round_cols].round(6) - for col in round_cols: - df[col] = df[col].map( - lambda x: f'{str(x).ljust(7+len(str(x).split(".")[0]),"0")}' - ) - # set the laser power - df["Power(W)"] = df["Pmod"] * power +def get_scanpath_bounding_box(scanpath, file_format="myna"): + """Returns the bounding box for given scanpath file(s) in meters - # write the converted path to a new file - df.to_csv( - export_path, - columns=["Mode", "X(m)", "Y(m)", "Z(m)", "Power(W)", "tParam"], - sep="\t", - index=False, - ) + Args: + scanpath: file or list of files of scanpaths to find the bounding box + format: the format of the scanpath file ("myna" or "additivefoam") + + Returns: + [[minx, miny, minz],[maxx, maxy, maxz]] + """ + if not isinstance(scanpath, list) and isinstance(scanpath, str): + scanpath = [scanpath] + + xmin, ymin, zmin = [1e10] * 3 + xmax, ymax, zmax = [-1e10] * 3 + + if file_format.lower() == "myna": + xcol, ycol, zcol = ["X(mm)", "Y(mm)", "Z(mm)"] + scale = 1e-3 + elif file_format.lower() == "additivefoam": + xcol, ycol, zcol = ["X(m)", "Y(m)", "Z(m)"] + scale = 1 + + else: + assert file_format.lower() in ["myna", "additivefoam"] + + for f in scanpath: + df = pd.read_csv(f, sep="\t") + xmin = min(xmin, df[xcol].min() * scale) + xmax = max(xmax, df[xcol].max() * scale) + ymin = min(ymin, df[ycol].min() * scale) + ymax = max(ymax, df[ycol].max() * scale) + zmin = min(zmin, df[zcol].min() * scale) + zmax = max(zmax, df[zcol].max() * scale) + + return [[xmin, ymin, zmin], [xmax, ymax, zmax]] diff --git a/src/myna/application/additivefoam/solidification_region_reduced/template/system/ExaCA b/src/myna/application/additivefoam/solidification_region_reduced/template/system/ExaCA new file mode 100644 index 00000000..9b7fd047 --- /dev/null +++ b/src/myna/application/additivefoam/solidification_region_reduced/template/system/ExaCA @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +ExaCA +{ + type ExaCA; + libs ("libExaCAFunctionObject.so"); + + box ( 0.0495 -0.0495 -0.0003 ) ( 0.0505 -0.0485 0 ); + dx 2.5e-6; + isoValue 1730; +} + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_reduced/template/system/controlDict b/src/myna/application/additivefoam/solidification_region_reduced/template/system/controlDict index 81e4d42d..27edb701 100644 --- a/src/myna/application/additivefoam/solidification_region_reduced/template/system/controlDict +++ b/src/myna/application/additivefoam/solidification_region_reduced/template/system/controlDict @@ -42,11 +42,18 @@ timeFormat general; timePrecision 8; -runTimeModifiable yes; +runTimeModifiable no; adjustTimeStep yes; maxCo 0.5; -maxFo 50; -maxAlphaCo 0.5; + +maxDi 100; + +maxAlphaCo 1; + +functions +{ + #includeFunc ExaCA +} // ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_reduced/template/system/fvSchemes b/src/myna/application/additivefoam/solidification_region_reduced/template/system/fvSchemes index 3d73700d..5189fec1 100644 --- a/src/myna/application/additivefoam/solidification_region_reduced/template/system/fvSchemes +++ b/src/myna/application/additivefoam/solidification_region_reduced/template/system/fvSchemes @@ -31,7 +31,8 @@ divSchemes laplacianSchemes { - default Gauss linear corrected; + default Gauss linear corrected; + laplacian(kappa,T) Gauss harmonic corrected; } interpolationSchemes diff --git a/src/myna/application/additivefoam/solidification_region_stl/template/system/ExaCA b/src/myna/application/additivefoam/solidification_region_stl/template/system/ExaCA new file mode 100644 index 00000000..59f20dca --- /dev/null +++ b/src/myna/application/additivefoam/solidification_region_stl/template/system/ExaCA @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +ExaCA +{ + type ExaCA; + libs ("libExaCAFunctionObject.so"); + + box ( 0.172 0.072 -0.0003 ) ( 0.173 0.073 0 ); + dx 2.5e-6; + isoValue 1730; +} + + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_stl/template/system/controlDict b/src/myna/application/additivefoam/solidification_region_stl/template/system/controlDict index 81e4d42d..0978485e 100644 --- a/src/myna/application/additivefoam/solidification_region_stl/template/system/controlDict +++ b/src/myna/application/additivefoam/solidification_region_stl/template/system/controlDict @@ -47,6 +47,13 @@ runTimeModifiable yes; adjustTimeStep yes; maxCo 0.5; -maxFo 50; -maxAlphaCo 0.5; + +maxDi 100; + +maxAlphaCo 1; + +functions +{ + #includeFunc ExaCA +} // ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_stl/template/system/fvSchemes b/src/myna/application/additivefoam/solidification_region_stl/template/system/fvSchemes index 3d73700d..5189fec1 100644 --- a/src/myna/application/additivefoam/solidification_region_stl/template/system/fvSchemes +++ b/src/myna/application/additivefoam/solidification_region_stl/template/system/fvSchemes @@ -31,7 +31,8 @@ divSchemes laplacianSchemes { - default Gauss linear corrected; + default Gauss linear corrected; + laplacian(kappa,T) Gauss harmonic corrected; } interpolationSchemes diff --git a/src/myna/application/additivefoam/temperature_final_part/execute.py b/src/myna/application/additivefoam/temperature_final_part/execute.py new file mode 100644 index 00000000..f66128b3 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/execute.py @@ -0,0 +1,292 @@ +# +# Copyright (c) 2024 Oak Ridge National Laboratory. +# +# This file is part of Myna. For details, see the top-level license +# at https://github.com/ORNL-MDF/Myna/LICENSE.md. +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause. +# +import os +import subprocess +import shutil +import polars as pl +from myna.core.workflow.load_input import load_input +from myna.core.utils.nested_dict_tools import nested_find_all +from myna.application.additivefoam import AdditiveFOAM +from myna.application.additivefoam.path import ( + convert_peregrine_scanpath, + get_scanpath_bounding_box, +) +import myna.application.openfoam as openfoam + + +def get_myna_file_part(myna_file): + """Returns the string of the part component of the Myna file path + + Args: + - myna_file: path to the Myna file + + Returns: String of the part name + """ + return myna_file.split(os.path.sep)[-4] + + +def get_myna_file_layer(myna_file): + """Returns the string of the layer component of the Myna file path + + Args: + - myna_file: path to the Myna file + + Returns: String of the layer name + """ + return myna_file.split(os.path.sep)[-3] + + +def update_parallel_cmd(app, cmd): + """Adds mpirun and parallel arguments for running OpenFOAM applications in parallel + + Args: + app: AdditiveFOAM(MynaApp) + cmd: list of command arguments to update + """ + if app.args.np > 1: + cmd = ["mpirun", "-np", str(app.args.np)] + cmd + cmd.append("-parallel") + return cmd + + +def convert_temperature_output(case_dir, output_file): + """Extract the top surface temperature and write as csv + + Args: + case_dir: path to the case directory to process + output_file: path to the output file to write + """ + + end_time = float( + openfoam.update.foam_dict_get("endTime", f"{case_dir}/system/controlDict") + ) + if end_time.is_integer(): + end_time = int(end_time) + input_file = os.path.join(case_dir, f"postProcessing/topSurface/{end_time}/top.xy") + + # Read and clean data in-memory + with open(input_file, "r", encoding="utf-8") as f: + data = [ + line.split() + for line in f + if not line.strip().startswith("#") and line.strip() + ] + + # Convert the data to a Polars DataFrame with explicit orientation + df = pl.DataFrame( + data, schema=["x (m)", "y (m)", "z (m)", "T (K)", "is_solid"], orient="row" + ).with_columns( + [ + pl.col("x (m)").cast(pl.Float64), + pl.col("y (m)").cast(pl.Float64), + pl.col("z (m)").cast(pl.Float64), + pl.col("T (K)").cast(pl.Float64), + pl.col("is_solid").cast(pl.Float64), + ] + ) + + # Write the DataFrame to a CSV file + df.write_csv(output_file) + + +def main(): + """Assembles and runs coarse heat transfer simulation for all specified layers. + + Due to each layer relying on the output of the previous layer, this app requires + each layer to run sequentially.""" + + # Create app instance + app = AdditiveFOAM("temperature_final_part") + app.parser.add_argument( + "--n-cells-per-layer", type=int, default=1, help="Number of cells per layer" + ) + app.parser.add_argument( + "--layer-time", + type=float, + default=60.0, + help="Simulation time for each layer in seconds", + ) + app.args, _ = app.parser.parse_known_args() + + # recalculate app arguments after new argparse + app.update_template_path() + app.set_procs() + app.check_exe("macroAdditiveFoam") + + # For each part, get list of directories for each layer, configure and launch + # the corresponding simulations + myna_files = app.settings["data"]["output_paths"][app.step_name] + parts = list(app.settings["data"]["build"]["parts"].keys()) + for part in parts: + # Get list of files associated with the part and extract layer numbers + part_files = [f for f in myna_files if get_myna_file_part(f) == part] + layers = [int(get_myna_file_layer(f)) for f in part_files] + + # Sort the lists by layer integers + part_files = [x for _, x in sorted(zip(layers, part_files))] + layers = sorted(layers) + + # Create a resource directory for the part's background mesh + template_dir = os.path.abspath(app.get_part_resource_template_dir(part)) + app.copy_template_to_dir(template_dir) + + # Create the background mesh based on the bounding box of the scan path + layer_data_dict = app.settings["data"]["build"]["parts"][part]["layer_data"] + all_scanpaths = [ + x["file_local"] for x in nested_find_all(layer_data_dict, "scanpath") + ] + scanpath_box = get_scanpath_bounding_box(all_scanpaths, file_format="myna") + layer_thickness = app.settings["data"]["build"]["build_data"][ + "layer_thickness" + ]["value"] + scanpath_box[0][2] = -app.args.pad_sub + scanpath_box[1][2] = 0.0 + pad = [app.args.pad_xy, app.args.pad_xy, 0.0] + _, _ = openfoam.mesh.create_cube_mesh( + template_dir, + [app.args.coarse, app.args.coarse, app.args.coarse], + 1.0e-08, + scanpath_box, + pad, + ) + + for index, (layer, myna_file) in enumerate(zip(layers, part_files)): + # Get case settings + case_dir = os.path.dirname(myna_file) + case_settings = load_input(os.path.join(case_dir, "myna_data.yaml")) + part = list(case_settings["build"]["parts"].keys())[0] + part_dict = case_settings["build"]["parts"][part] + layer_height = layer * layer_thickness + + # Copy the template case + shutil.copytree(template_dir, case_dir, dirs_exist_ok=True) + + # Update scanpath and heat source files + myna_scanfile = part_dict["layer_data"][str(layer)]["scanpath"][ + "file_local" + ] + power = case_settings["build"]["parts"][part]["laser_power"]["value"] + path_name = os.path.basename(myna_scanfile) + new_scan_path_file = os.path.join(case_dir, "constant", path_name) + convert_peregrine_scanpath(myna_scanfile, new_scan_path_file, power) + app.update_heatsource_scanfile(case_dir, path_name) + app.update_beam_spot_size(part, case_dir) + + # TODO: Update material properties from Mist data. Cannot use default + # Mist AdditiveFOAM file generation `app.update_material_properties()` + # because different properties are needed + + # Update number of processors + openfoam.update.foam_dict_set( + "numberOfSubdomains", app.args.np, f"{case_dir}/system/decomposeParDict" + ) + + # Extrude mesh + cells_to_extrude = app.args.n_cells_per_layer * layer + openfoam.update.foam_dict_set( + "nLayers", cells_to_extrude, f"{case_dir}/system/extrudeMeshDict" + ) + openfoam.update.foam_dict_set( + "linearDirectionCoeffs/thickness", + layer_height, + f"{case_dir}/system/extrudeMeshDict", + ) + openfoam.update.foam_dict_set( + "sourceCase", + f'"{case_dir}"', + f"{case_dir}/system/extrudeMeshDict", + ) + subprocess.run(["extrudeMesh", "-case", case_dir], check=True) + + # Update times and map fields between layers + if index == 0: + start_time = 0 + end_time = start_time + app.args.layer_time + openfoam.update.foam_dict_set( + "endTime", end_time, f"{case_dir}/system/controlDict" + ) + app.update_start_and_end_times(case_dir, start_time, end_time, 1) + subprocess.run(["decomposePar", "-case", case_dir], check=True) + else: + # Get directory for previous case + previous_case_dir = os.path.dirname(part_files[index - 1]) + start_time = float( + openfoam.update.foam_dict_get( + "endTime", + os.path.join(previous_case_dir, "system", "controlDict"), + ) + ) + end_time = start_time + app.args.layer_time + app.update_start_and_end_times(case_dir, start_time, end_time, 1) + subprocess.run(["decomposePar", "-case", case_dir], check=True) + cmd = [ + "mapFieldsPar", + "-case", + case_dir, + "-mapMethod", + "direct", + "-sourceTime", + str(start_time), + previous_case_dir, + ] + subprocess.run(update_parallel_cmd(app, cmd), check=True) + + # Run macroAdditiveFoam + subprocess.run( + update_parallel_cmd( + app, + [ + "transformPoints", + f"translate=(0 0 -{layer_height})", + "-case", + case_dir, + ], + ), + check=True, + ) + subprocess.run( + update_parallel_cmd(app, ["setFields", "-case", case_dir]), check=True + ) + subprocess.run( + update_parallel_cmd(app, ["macroAdditiveFoam", "-case", case_dir]), + check=True, + ) + subprocess.run( + update_parallel_cmd( + app, + [ + "transformPoints", + f"translate=(0 0 {layer_height})", + "-case", + case_dir, + ], + ), + check=True, + ) + + # Post-process + subprocess.run( + update_parallel_cmd( + app, + [ + "postProcess", + "-case", + case_dir, + "-func", + "topSurface", + "-latestTime", + ], + ), + check=True, + ) + convert_temperature_output(case_dir, myna_file) + + +if __name__ == "__main__": + main() diff --git a/src/myna/application/additivefoam/temperature_final_part/template/0/T b/src/myna/application/additivefoam/temperature_final_part/template/0/T new file mode 100644 index 00000000..a92aefb3 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/0/T @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + format binary; + class volScalarField; + location "0"; + object T; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 300; + +boundaryField +{ + bottom + { + type mixedTemperature; + h 500.0; + emissivity 0.4; + Tinf uniform 300; + value uniform 300; + } + top + { + type mixedTemperature; + h 25.0; + emissivity 0.4; + Tinf uniform 300; + value uniform 300; + } + sides + { + type zeroGradient; + value uniform 300; + } +} + + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/0/alpha.solid b/src/myna/application/additivefoam/temperature_final_part/template/0/alpha.solid new file mode 100644 index 00000000..a2dad075 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/0/alpha.solid @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + format binary; + class volScalarField; + location "0"; + object alpha.solid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 1; + +boundaryField +{ + bottom + { + type zeroGradient; + } + top + { + type zeroGradient; + } + sides + { + type zeroGradient; + } +} diff --git a/src/myna/application/additivefoam/temperature_final_part/template/Allclean b/src/myna/application/additivefoam/temperature_final_part/template/Allclean new file mode 100644 index 00000000..98353b9b --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/Allclean @@ -0,0 +1,9 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +# Source tutorial clean functions +. $WM_PROJECT_DIR/bin/tools/CleanFunctions + +cleanCase + +rm -rf layer* diff --git a/src/myna/application/additivefoam/temperature_final_part/template/constant/g b/src/myna/application/additivefoam/temperature_final_part/template/constant/g new file mode 100644 index 00000000..ee422709 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/constant/g @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2.0; + format ascii; + class uniformDimensionedVectorField; + location "constant"; + object g; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -2 0 0 0 0]; +value (0 0 -9.81); + + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/constant/heatSourceDict b/src/myna/application/additivefoam/temperature_final_part/template/constant/heatSourceDict new file mode 100644 index 00000000..181b774c --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/constant/heatSourceDict @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2; + format ascii; + class dictionary; + object heatSourceProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +sources ( beam ); + +beam +{ + pathName scanpath.txt; + absorptionModel constant; + constantCoeffs + { + eta 0.3; + } + heatSourceModel Gaussian; + GaussianCoeffs + { + dimensions ( 6.5e-05 6.5e-05 3e-05 ); + nPoints ( 2 2 2 ); + } +} + + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/constant/scanpath.txt b/src/myna/application/additivefoam/temperature_final_part/template/constant/scanpath.txt new file mode 100644 index 00000000..f2fffc1e --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/constant/scanpath.txt @@ -0,0 +1,2 @@ +Mode X(m) Y(m) Z(m) Power(W) tParam +1 0.0 0.0 0.0 0 0.0 diff --git a/src/myna/application/additivefoam/temperature_final_part/template/constant/thermoPath b/src/myna/application/additivefoam/temperature_final_part/template/constant/thermoPath new file mode 100644 index 00000000..7915d61f --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/constant/thermoPath @@ -0,0 +1,4 @@ +( +1670.0000 1.0000 +1730.0000 0.0000 +) diff --git a/src/myna/application/additivefoam/temperature_final_part/template/constant/transportProperties b/src/myna/application/additivefoam/temperature_final_part/template/constant/transportProperties new file mode 100644 index 00000000..7a95fc35 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/constant/transportProperties @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object transportProperties; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +solid +{ + kappa (4.957 0.01571 0.0); + Cp (770.4 0.0 0.0); +} + +powder +{ + kappa (0.4957 0.001571 0.0); + Cp (42.3 0.01329 0.0); +} + +rho [1 -3 0 0 0 0 0] 7955.0; + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/blockMeshDict b/src/myna/application/additivefoam/temperature_final_part/template/system/blockMeshDict new file mode 100644 index 00000000..11d8edc4 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/blockMeshDict @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2; + format ascii; + class dictionary; + object blockMeshDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +xmin 0.16503199000000002; +ymin 0.06701219; +zmin -1.55476e-05; + +xmax 0.17983100999999999; +ymax 0.08132541; +zmax 0.016320510000000003; + +nx 92; +ny 89; +nz 102; + +vertices +( + ( $xmin $ymin $zmin ) + ( $xmax $ymin $zmin ) + ( $xmax $ymax $zmin ) + ( $xmin $ymax $zmin ) + ( $xmin $ymin $zmax ) + ( $xmax $ymin $zmax ) + ( $xmax $ymax $zmax ) + ( $xmin $ymax $zmax ) +); + +blocks +( + hex ( 0 1 2 3 4 5 6 7 ) ( $nx $ny $nz ) simpleGrading ( 1 1 1 ) +); + +edges( ); + +boundary +( + bottom + { + type wall; + faces + ( + (0 3 2 1) + ); + } + top + { + type wall; + faces + ( + (4 5 6 7) + ); + } + sides + { + type wall; + faces + ( + (0 4 7 3) + (2 6 5 1) + (1 5 4 0) + (3 7 6 2) + ); + } +); + + +mergePatchPairs ( ); + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/controlDict b/src/myna/application/additivefoam/temperature_final_part/template/system/controlDict new file mode 100644 index 00000000..fd661736 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/controlDict @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + location "system"; + object controlDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +application macroAdditiveFoam; + +startFrom startTime; + +startTime 0.0; + +stopAt endTime; + +endTime 0.0; + +deltaT 0.01; + +writeControl adjustableRunTime; + +writeInterval 0.5; + +purgeWrite 0; + +writeFormat binary; + +writePrecision 8; + +writeCompression off; + +timeFormat general; + +timePrecision 8; + +runTimeModifiable no; + +adjustTimeStep yes; + +nPathIntervals 100; +maxDi 1000; +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_stl/template/constant/foamToExaCADict b/src/myna/application/additivefoam/temperature_final_part/template/system/decomposeParDict similarity index 76% rename from src/myna/application/additivefoam/solidification_region_stl/template/constant/foamToExaCADict rename to src/myna/application/additivefoam/temperature_final_part/template/system/decomposeParDict index 93c46491..d0fd8b1f 100644 --- a/src/myna/application/additivefoam/solidification_region_stl/template/constant/foamToExaCADict +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/decomposeParDict @@ -9,17 +9,14 @@ FoamFile version 2; format ascii; class dictionary; - object foamToExaCADict; + location "system"; + object decomposeParDict; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -execute on; +numberOfSubdomains 8; -box ( 0.172 0.072 -0.0003 ) ( 0.173 0.073 0 ); - -dx 2.5e-06; - -isotherm 1730; +method scotch; // ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/extrudeMeshDict b/src/myna/application/additivefoam/temperature_final_part/template/system/extrudeMeshDict new file mode 100644 index 00000000..3fbe9691 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/extrudeMeshDict @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + + FoamFile +{ + version 2; + format ascii; + class dictionary; + location "system"; + object extrudeMeshDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +constructFrom mesh; +sourceCase "."; +sourcePatches (top); + +flipNormals false; + +nLayers 0; +expansionRatio 1; + +extrudeModel linearDirection; + +linearDirectionCoeffs +{ + direction (0 0 1); + thickness 0; +} + +mergeFaces false; + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/fvSchemes b/src/myna/application/additivefoam/temperature_final_part/template/system/fvSchemes new file mode 100644 index 00000000..8ee17875 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/fvSchemes @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + location "system"; + object fvSchemes; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +ddtSchemes +{ + default backward; +} + +gradSchemes +{ + default Gauss linear; +} + +divSchemes +{ + default Gauss upwind; +} + +laplacianSchemes +{ + default Gauss linear corrected; + laplacian(kappa,T) Gauss harmonic corrected; +} + +interpolationSchemes +{ + default linear; +} + +snGradSchemes +{ + default corrected; +} + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/fvSolution b/src/myna/application/additivefoam/temperature_final_part/template/system/fvSolution new file mode 100644 index 00000000..16a35d3b --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/fvSolution @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + location "system"; + object fvSolution; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +solvers +{ + "T.*" + { + solver PBiCGStab; + preconditioner DIC; + tolerance 1e-10; + relTol 0; + minIter 1; + maxIter 20; + } +} + +PIMPLE +{ + momentumPredictor no; + nOuterCorrectors 0; + nCorrectors 1; + nNonOrthogonalCorrectors 0; + pRefCell 0; + pRefValue 0; +} + +relaxationFactors +{ + equations + { + ".*" 1; + } +} + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/solidification_region_reduced/template/constant/foamToExaCADict b/src/myna/application/additivefoam/temperature_final_part/template/system/mapFieldsDict similarity index 74% rename from src/myna/application/additivefoam/solidification_region_reduced/template/constant/foamToExaCADict rename to src/myna/application/additivefoam/temperature_final_part/template/system/mapFieldsDict index cda7d1a5..1ec93bb0 100644 --- a/src/myna/application/additivefoam/solidification_region_reduced/template/constant/foamToExaCADict +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/mapFieldsDict @@ -4,22 +4,17 @@ Created for simulation with Myna ---------------------------------------------------------------------------*/ -FoamFile + FoamFile { version 2; format ascii; class dictionary; - object foamToExaCADict; + location "system"; + object mapFieldsDict; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -execute on; - -box ( 0.0495 -0.0495 -0.0003 ) ( 0.0505 -0.0485 0 ); - -dx 2.5e-06; - -isotherm 1730; - +patchMap (); +cuttingPatches ( bottom top sides ); // ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/setFieldsDict b/src/myna/application/additivefoam/temperature_final_part/template/system/setFieldsDict new file mode 100644 index 00000000..98747ed3 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/setFieldsDict @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + + FoamFile +{ + version 2; + format ascii; + class dictionary; + location "system"; + object setFieldsDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +regions +( + boxToCell + { + box (-1 -1 -5e-5) (1 1 1); + + fieldValues + ( + volScalarFieldValue alpha.solid 0 + ); + } +); + +// ************************************************************************* // diff --git a/src/myna/application/additivefoam/temperature_final_part/template/system/topSurface b/src/myna/application/additivefoam/temperature_final_part/template/system/topSurface new file mode 100644 index 00000000..94385958 --- /dev/null +++ b/src/myna/application/additivefoam/temperature_final_part/template/system/topSurface @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------- + AdditiveFOAM template input file (compatible with 1.0, OpenFOAM 10) + + Created for simulation with Myna + ---------------------------------------------------------------------------*/ + +topSurface +{ + type surfaces; + libs ("libsampling.so"); + + surfaceFormat raw; + + fields ( T alpha.solid ); + + interpolationScheme cellPoint; + + surfaces + ( + top + { + type patch; + patches ( "top" ); + } + ); +} + +// ************************************************************************* // diff --git a/src/myna/application/openfoam/__init__.py b/src/myna/application/openfoam/__init__.py index 7f3a2f67..56e83bc5 100644 --- a/src/myna/application/openfoam/__init__.py +++ b/src/myna/application/openfoam/__init__.py @@ -13,3 +13,4 @@ """ from . import mesh +from . import update diff --git a/src/myna/application/openfoam/mesh.py b/src/myna/application/openfoam/mesh.py index 9c907675..5165c2a6 100644 --- a/src/myna/application/openfoam/mesh.py +++ b/src/myna/application/openfoam/mesh.py @@ -189,7 +189,7 @@ def create_cube_mesh(case_dir, spacing, tolerance, rve, rve_pad): origin = [a + b / 2.0 for (a, b) in zip(bb_min, span)] # update the background mesh file - blockMeshDict = os.path.join(case_dir, "system/blockMeshDict") + blockMeshDict = os.path.join(case_dir, "system", "blockMeshDict") lines = open(blockMeshDict, "r").readlines() keys = ["xmin", "ymin", "zmin", "xmax", "ymax", "zmax"] @@ -317,10 +317,10 @@ def slice(case_dir, height): def refine_RVE(case_dir, bb): os.system( - f"foamDictionary -entry box -set " + f"foamDictionary -entry ExaCA/box -set " f'"( {bb[0][0]} {bb[0][1]} {bb[0][2]} ) ' f'( {bb[1][0]} {bb[1][1]} {bb[1][2]} )" ' - f"{case_dir}/constant/foamToExaCADict" + f"{case_dir}/system/ExaCA" ) os.system( diff --git a/src/myna/application/openfoam/update.py b/src/myna/application/openfoam/update.py new file mode 100644 index 00000000..7e1c4269 --- /dev/null +++ b/src/myna/application/openfoam/update.py @@ -0,0 +1,27 @@ +# +# Copyright (c) 2024 Oak Ridge National Laboratory. +# +# This file is part of Myna. For details, see the top-level license +# at https://github.com/ORNL-MDF/Myna/LICENSE.md. +# +# License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause. +# +import subprocess + + +def foam_dict_get(entry, filepath): + """Gets a value from a foamDictionary file.""" + command = ["foamDictionary", "-entry", entry, "-value", filepath] + return subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ).stdout.strip() + + +def foam_dict_set(entry, value, filepath): + """Gets a value from a foamDictionary file.""" + command = "foamDictionary", "-entry", entry, "-set", str(value), filepath + subprocess.run(command, check=True) diff --git a/src/myna/core/components/component_class_lookup.py b/src/myna/core/components/component_class_lookup.py index e6762478..5d142e7d 100644 --- a/src/myna/core/components/component_class_lookup.py +++ b/src/myna/core/components/component_class_lookup.py @@ -34,12 +34,14 @@ def return_step_class(step_name): "general": Component(), "solidification_part": ComponentSolidificationPart(), "solidification_build_region": ComponentSolidificationBuildRegion(), - "solidification_region_reduced": ComponentSolidificationRegion(), + "solidification_region": ComponentSolidificationRegion(), "solidification_part_solidification": ComponentSolidificationPartReduced(), "solidification_region_reduced": ComponentSolidificationRegionReduced(), "solidification_part_stl": ComponentSolidificationPartSTL(), "solidification_region_stl": ComponentSolidificationRegionSTL(), "temperature_part": ComponentTemperaturePart(), + "temperature_final_part": ComponentTemperatureFinalPart(), + "temperature_final_part_stl": ComponentTemperatureFinalPartSTL(), "cluster_solidification": ComponentClusterSolidification(), "cluster_supervoxel": ComponentClusterSupervoxel(), "microstructure_part": ComponentMicrostructurePart(), diff --git a/src/myna/core/components/component_temperature.py b/src/myna/core/components/component_temperature.py index 887ddccf..26119567 100644 --- a/src/myna/core/components/component_temperature.py +++ b/src/myna/core/components/component_temperature.py @@ -13,8 +13,8 @@ ComponentTemperaturePart """ -from .component import * -from myna.core.files import FileTemperature +from myna.core.files import FileTemperature, FileTemperatureFinal +from .component import Component ################## # Base Component # @@ -52,3 +52,24 @@ class ComponentTemperaturePart(ComponentTemperature): def __init__(self): ComponentTemperature.__init__(self) self.types.extend(["part", "layer"]) + + +class ComponentTemperatureFinalPart(ComponentTemperaturePart): + """Layer-wise Component that outputs the domain temperature + at the end of a layer for a part in the format of the class `FileTemperatureFinal` + """ + + def __init__(self): + ComponentTemperaturePart.__init__(self) + self.output_requirement = FileTemperatureFinal + + +class ComponentTemperatureFinalPartSTL(ComponentTemperatureFinalPart): + """Layer-wise Component that outputs the domain temperature + at the end of a layer for a part in the format of the class `FileTemperatureFinal`. + Requires an STL file as input. + """ + + def __init__(self): + ComponentTemperatureFinalPart.__init__(self) + self.data_requirements.extend(["stl"]) diff --git a/src/myna/core/files/file_temperature.py b/src/myna/core/files/file_temperature.py index c4453a23..455b0e2a 100644 --- a/src/myna/core/files/file_temperature.py +++ b/src/myna/core/files/file_temperature.py @@ -6,7 +6,7 @@ # # License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause. # -"""Define file format class related to the spatial distribution of temperature (T) +"""Define file format classes related to the spatial distribution of temperature (T) """ import pandas as pd @@ -43,3 +43,10 @@ def file_is_valid(self): expected_cols = ["x (m)", "y (m)", "t (k)"] expected_cols_types = [float, float, float] return self.columns_are_valid(cols, expected_cols, expected_cols_types) + + +class FileTemperatureFinal(FileTemperature): + """File format class for temperature (T) after a layer is deposited""" + + def __init__(self, file): + FileTemperature.__init__(self, file) diff --git a/src/myna/core/utils/nested_dict_tools.py b/src/myna/core/utils/nested_dict_tools.py index 3371a379..a5968151 100644 --- a/src/myna/core/utils/nested_dict_tools.py +++ b/src/myna/core/utils/nested_dict_tools.py @@ -24,6 +24,23 @@ def nested_get(dict, keys, default_value=None): return dict[keys[-1]] +def nested_find_all(dictionary, target_key): + """Gets the values of all matching keys in the nested dictionary at any level + + Args: + dictionary: dictionary to parse + key: key to match + """ + matches = [] + if target_key in dictionary: + matches.append(dictionary[target_key]) + for key, value in dictionary.items(): + if isinstance(value, dict): + nested_matches = nested_find_all(dictionary[key], target_key) + matches.extend(nested_matches) + return matches + + def get_synonymous_key(dict_obj, synonym_list): """Returns the object at the first matching key from a dictionary-like object given a list of synonymous keys