From 68a360fa0eb71f4273d1b7e3a36426c2fb60905c Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 21 May 2025 09:35:25 -0600 Subject: [PATCH 01/86] template case and utilities to move spargers --- applications/sparger_placement_opt.py | 17 + bird/__init__.py | 1 + bird/preprocess/json_gen/design_io.py | 85 ++ bird/preprocess/json_gen/generate_designs.py | 531 +++++++++++++ .../.vim/.netrwhist | 3 + .../0.orig/CO2.gas | 47 ++ .../0.orig/CO2.liquid | 42 + .../0.orig/H2.gas | 47 ++ .../0.orig/H2.liquid | 42 + .../0.orig/N2.gas | 47 ++ .../0.orig/T.gas | 44 ++ .../0.orig/T.liquid | 43 + .../0.orig/U.gas | 47 ++ .../0.orig/U.liquid | 46 ++ .../0.orig/Ydefault.gas | 42 + .../0.orig/Ydefault.liquid | 42 + .../0.orig/alpha.gas | 43 + .../0.orig/alpha.liquid | 40 + .../0.orig/alphat.gas | 46 ++ .../0.orig/alphat.liquid | 44 ++ .../0.orig/epsilon.gas | 48 ++ .../0.orig/epsilon.liquid | 43 + .../0.orig/f.gas | 41 + .../0.orig/k.gas | 43 + .../0.orig/k.liquid | 44 ++ .../0.orig/nut.gas | 48 ++ .../0.orig/nut.liquid | 43 + .../0.orig/p | 39 + .../0.orig/p_rgh | 43 + .../Allclean | 18 + .../computeQOI.sh | 13 + .../constant/dynamicMix_util.H | 37 + .../constant/fvModels | 128 +++ .../constant/g | 21 + .../constant/globalVars | 83 ++ .../constant/globalVars_temp | 83 ++ .../constant/momentumTransport.gas | 26 + .../constant/momentumTransport.liquid | 27 + .../constant/phaseProperties | 295 +++++++ .../constant/phaseProperties_constantd | 261 ++++++ .../constant/phaseProperties_pbe | 295 +++++++ .../constant/thermophysicalProperties.gas | 142 ++++ .../constant/thermophysicalProperties.liquid | 108 +++ .../get_qoi.py | 183 +++++ .../presteps.sh | 71 ++ .../read_history.py | 239 ++++++ .../run.sh | 5 + .../script | 14 + .../script_post | 10 + .../system/blockMeshDict | 746 ++++++++++++++++++ .../system/controlDict | 67 ++ .../system/decomposeParDict | 30 + .../system/fvConstraints | 56 ++ .../system/fvSchemes | 70 ++ .../system/fvSolution | 120 +++ .../system/inlets_outlets.json | 28 + .../system/mesh.json | 26 + .../system/mixers.json | 29 + .../system/setFieldsDict | 37 + .../writeGlobalVars.py | 47 ++ 60 files changed, 5006 insertions(+) create mode 100644 applications/sparger_placement_opt.py create mode 100644 bird/preprocess/json_gen/design_io.py create mode 100644 bird/preprocess/json_gen/generate_designs.py create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh create mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh create mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script create mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict create mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict create mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py diff --git a/applications/sparger_placement_opt.py b/applications/sparger_placement_opt.py new file mode 100644 index 00000000..9074715d --- /dev/null +++ b/applications/sparger_placement_opt.py @@ -0,0 +1,17 @@ +import os +import pickle +import shutil + +import numpy as np + +from bird.preprocess.json_gen.design_io import * +from bird.preprocess.json_gen.generate_designs import * + +if __name__ == "__main__": + + generate_single_scaledup_reactor_sparger_cases( + sparger_locs=[0.3, 0.5, 1.4], + sim_id=0, + vvm=0.4, + study_folder=".", + ) diff --git a/bird/__init__.py b/bird/__init__.py index 1c16af5e..38b9be87 100644 --- a/bird/__init__.py +++ b/bird/__init__.py @@ -5,6 +5,7 @@ from bird.version import __version__ BIRD_DIR = os.path.dirname(os.path.realpath(__file__)) +BIRD_CASE_DIR = os.path.join(BIRD_DIR, "../tutorial_cases") BIRD_MESH_DIR = os.path.join(BIRD_DIR, "meshing") BIRD_POST_DIR = os.path.join(BIRD_DIR, "postprocess") BIRD_PRE_DIR = os.path.join(BIRD_DIR, "preprocess") diff --git a/bird/preprocess/json_gen/design_io.py b/bird/preprocess/json_gen/design_io.py new file mode 100644 index 00000000..d6ed2c72 --- /dev/null +++ b/bird/preprocess/json_gen/design_io.py @@ -0,0 +1,85 @@ +import json + + +def generate_stl_patch(filename, bc_dict, geom_dict): + final_dict = {} + final_dict["Geometry"] = geom_dict["Geometry"] + for patch in bc_dict: + final_dict[patch] = bc_dict[patch] + with open(filename, "w+") as f: + json.dump(final_dict, f, indent=2) + + +def generate_dynamic_mixer(filename, mixers_list, geom_dict): + final_dict = {} + final_dict["Meshing"] = geom_dict["Meshing"] + final_dict["Geometry"] = geom_dict["Geometry"] + final_dict["mixers"] = mixers_list + with open(filename, "w+") as f: + json.dump(final_dict, f, indent=2) + + +def make_default_geom_dict_from_file(filename, rescale=2.7615275385627096): + with open(filename, "r+") as f: + geom_dict = json.load(f) + if "rescale" not in geom_dict["Geometry"]["OverallDomain"]["x"]: + geom_dict["Geometry"]["OverallDomain"]["x"]["rescale"] = rescale + geom_dict["Geometry"]["OverallDomain"]["y"]["rescale"] = rescale + geom_dict["Geometry"]["OverallDomain"]["z"]["rescale"] = rescale + assert "Meshing" in geom_dict + assert "Geometry" in geom_dict + return geom_dict + + +if __name__ == "__main__": + bc_dict = {} + bc_dict["inlets"] = [] + bc_dict["outlets"] = [] + tmp_dict = {} + tmp_dict["type"] = "circle" + tmp_dict["centx"] = 5.0 + tmp_dict["centy"] = 0.0 + tmp_dict["centz"] = 0.5 + tmp_dict["radius"] = 0.4 + tmp_dict["normal_dir"] = 1 + tmp_dict["nelements"] = 50 + bc_dict["inlets"].append(tmp_dict) + tmp_dict = {} + tmp_dict["type"] = "circle" + tmp_dict["centx"] = 2.5 + tmp_dict["centy"] = 0.0 + tmp_dict["centz"] = 0.5 + tmp_dict["radius"] = 0.4 + tmp_dict["normal_dir"] = 1 + tmp_dict["nelements"] = 50 + bc_dict["inlets"].append(tmp_dict) + tmp_dict = {} + tmp_dict["type"] = "circle" + tmp_dict["centx"] = 7.5 + tmp_dict["centy"] = 0.0 + tmp_dict["centz"] = 0.5 + tmp_dict["radius"] = 0.4 + tmp_dict["normal_dir"] = 1 + tmp_dict["nelements"] = 50 + bc_dict["inlets"].append(tmp_dict) + + tmp_dict = {} + tmp_dict["type"] = "circle" + tmp_dict["centx"] = 0.5 + tmp_dict["centy"] = 5.0 + tmp_dict["centz"] = 0.5 + tmp_dict["radius"] = 0.4 + tmp_dict["normal_dir"] = 1 + tmp_dict["nelements"] = 50 + bc_dict["outlets"].append(tmp_dict) + tmp_dict = {} + tmp_dict["type"] = "circle" + tmp_dict["centx"] = 0.5 + tmp_dict["centy"] = 5.0 + tmp_dict["centz"] = 0.5 + tmp_dict["radius"] = 0.4 + tmp_dict["normal_dir"] = 1 + tmp_dict["nelements"] = 50 + bc_dict["outlets"].append(tmp_dict) + + generate_stl_patch("test.json", bc_dict) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py new file mode 100644 index 00000000..354cabd7 --- /dev/null +++ b/bird/preprocess/json_gen/generate_designs.py @@ -0,0 +1,531 @@ +import os +import pickle +import shutil + +import numpy as np + +from bird import BIRD_CASE_DIR +from bird.preprocess.json_gen.design_io import * + + +def compare_config(config1, config2): + same = True + for key in config1: + if np.linalg.norm(config1[key] - config2[key]) > 1e-6: + same = False + return same + return same + + +def check_config(config): + success = False + inlet_exist = False + for key in config: + if len(np.argwhere(config[key] == 1)) > 0: + inlet_exist = True + break + if inlet_exist: + success = True + else: + success = False + return success + + +def save_config_dict(filename, config_dict): + with open(filename, "wb") as f: + pickle.dump(config_dict, f) + + +def load_config_dict(filename): + with open(filename, "rb") as f: + config_dict = pickle.load(f) + return config_dict + + +def write_script_start(filename, n): + with open(filename, "w+") as f: + for i in range(n): + f.write(f"cd Sim_{i}\n") + f.write(f"sbatch script\n") + f.write(f"cd ..\n") + + +def write_script_post(filename, n): + with open(filename, "w+") as f: + for i in range(n): + f.write(f"cd Sim_{i}\n") + f.write(f"sbatch script_post\n") + f.write(f"cd ..\n") + + +def write_prep(filename, n): + with open(filename, "w+") as f: + f.write("prep () {\n") + f.write(f"\tcd $1\n") + f.write(f"\treconstructPar -newTimes\n") + f.write(f"\tcd ..\n") + f.write("}\n") + f.write(f"\n") + f.write( + f"source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc\n" + ) + for i in range(n): + f.write(f"prep Sim_{i}\n") + + +def overwrite_vvm(case_folder, vvm): + list_dir = os.listdir(case_folder) + if not "constant" in list_dir: + sys.exit( + f"ERROR: {case_folder} is likely not a case folder, could not find constant/" + ) + else: + filename = os.path.join(case_folder, "constant", "globalVars_temp") + filename_write = os.path.join( + case_folder, "constant", "globalVars_temp2" + ) + with open(filename, "r+") as f: + lines = f.readlines() + with open(filename_write, "w+") as f: + for line in lines: + if line.startswith("VVM"): + f.write(f"VVM\t{vvm};\n") + else: + f.write(line) + shutil.copy( + os.path.join(case_folder, "constant", "globalVars_temp2"), + os.path.join(case_folder, "constant", "globalVars_temp"), + ) + os.remove(os.path.join(case_folder, "constant", "globalVars_temp2")) + + +def overwrite_bubble_size_model(case_folder, constantD=False): + list_dir = os.listdir(case_folder) + if not "constant" in list_dir: + sys.exit( + f"ERROR: {case_folder} is likely not a case folder, could not find constant/" + ) + else: + filename = os.path.join(case_folder, "presteps.sh") + filename_write = os.path.join(case_folder, "presteps2.sh") + with open(filename, "r+") as f: + lines = f.readlines() + with open(filename_write, "w+") as f: + for line in lines: + if line.startswith("cp constant/phaseProperties"): + if constantD: + f.write( + "cp constant/phaseProperties_constantd constant/phaseProperties\n" + ) + else: + f.write( + "cp constant/phaseProperties_pbe constant/phaseProperties\n" + ) + else: + f.write(line) + shutil.copy( + os.path.join(case_folder, "presteps2.sh"), + os.path.join(case_folder, "presteps.sh"), + ) + os.remove(os.path.join(case_folder, "presteps2.sh")) + + +def generate_small_reactor_cases( + config_dict, branchcom_spots, vvm, power, constantD, study_folder +): + templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch" + + geom_dict = make_default_geom_dict_from_file( + f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json", + rescale=0.05, + ) + try: + shutil.rmtree(study_folder) + except: + pass + os.makedirs(study_folder) + for simid in config_dict: + shutil.copytree( + f"{BIRD_CASE_DIR}/{templateFolder}", + f"{study_folder}/Sim_{simid}", + ) + bc_dict = {} + bc_dict["inlets"] = [] + bc_dict["outlets"] = [] + bc_dict["outlets"].append( + { + "branch_id": 6, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + bc_dict["outlets"].append( + { + "branch_id": 4, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + for branch in config_dict: + if branch in [0, 1, 2]: + ind = np.argwhere(config_dict[simid][branch] == 1) + if len(ind) > 0: + ind = list(ind[:, 0]) + for iind in ind: + bc_dict["inlets"].append( + { + "branch_id": branch, + "type": "circle", + "frac_space": branchcom_spots[branch][iind], + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "bottom", + } + ) + generate_stl_patch( + os.path.join( + study_folder, f"Sim_{simid}", "system", "inlets_outlets.json" + ), + bc_dict, + geom_dict, + ) + + mix_list = [] + for branch in config_dict: + if branch in [0, 1, 2]: + ind = np.argwhere(config_dict[simid][branch] == 0) + if len(ind) > 0: + ind = list(ind[:, 0]) + for iind in ind: + if branch == 0: + sign = "+" + else: + sign = "-" + mix_list.append( + { + "branch_id": branch, + "frac_space": branchcom_spots[branch][iind], + "start_time": 1, + "power": power, + "sign": sign, + } + ) + generate_dynamic_mixer( + os.path.join( + study_folder, f"Sim_{simid}", "system", "mixers.json" + ), + mix_list, + geom_dict, + ) + overwrite_vvm( + case_folder=os.path.join(study_folder, f"Sim_{simid}"), vvm=vvm + ) + overwrite_bubble_size_model( + case_folder=os.path.join(study_folder, f"Sim_{simid}"), + constantD=constantD, + ) + + geom_dict = make_default_geom_dict_from_file( + f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json", + rescale=0.05, + ) + + +def generate_scaledup_reactor_cases( + config_dict, branchcom_spots, vvm, power, constantD, study_folder +): + + templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup" + geom_dict = make_default_geom_dict_from_file( + f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json" + ) + try: + shutil.rmtree(study_folder) + except: + pass + os.makedirs(study_folder) + for simid in config_dict: + shutil.copytree( + f"{BIRD_CASE_DIR}/{templateFolder}", + f"{study_folder}/Sim_{simid}", + ) + bc_dict = {} + bc_dict["inlets"] = [] + bc_dict["outlets"] = [] + bc_dict["outlets"].append( + { + "branch_id": 6, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + bc_dict["outlets"].append( + { + "branch_id": 4, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + for branch in config_dict: + if branch in [0, 1, 2]: + ind = np.argwhere(config_dict[simid][branch] == 1) + if len(ind) > 0: + ind = list(ind[:, 0]) + for iind in ind: + bc_dict["inlets"].append( + { + "branch_id": branch, + "type": "circle", + "frac_space": branchcom_spots[branch][iind], + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "bottom", + } + ) + generate_stl_patch( + os.path.join( + study_folder, f"Sim_{simid}", "system", "inlets_outlets.json" + ), + bc_dict, + geom_dict, + ) + + mix_list = [] + for branch in config_dict: + if branch in [0, 1, 2]: + ind = np.argwhere(config_dict[simid][branch] == 0) + if len(ind) > 0: + ind = list(ind[:, 0]) + for iind in ind: + if branch == 0: + sign = "+" + else: + sign = "-" + mix_list.append( + { + "branch_id": branch, + "frac_space": branchcom_spots[branch][iind], + "start_time": 3, + "power": power, + "sign": sign, + } + ) + generate_dynamic_mixer( + os.path.join( + study_folder, f"Sim_{simid}", "system", "mixers.json" + ), + mix_list, + geom_dict, + ) + overwrite_vvm( + case_folder=os.path.join(study_folder, f"Sim_{simid}"), vvm=vvm + ) + overwrite_bubble_size_model( + case_folder=os.path.join(study_folder, f"Sim_{simid}"), + constantD=constantD, + ) + + +def check_sparger_config( + sparger_locs: list[float], + n_spargers: int | None, + sparger_spacing: float, + edge_spacing: float, + n_branches: int, +) -> None: + """ + Check realizability of the sparger placement configuration + + Parameters + ---------- + sparger_locs : list[float] + Location of every sparger along the loop reactor coordinate [-] + There are 3 branches. Spargers can be placed anywhere + between edge_spacing and (1-edge_spacing) fractions of the branch + Each sparger locations must be between 0 and 3*1=3 + n_spargers : int|None + Number of spargers + sparger_spacing : float + Spacing between two spargers [-] + edge_spacing : float + Spacing required between any sparger and the edges of the branches [-] + n_branches : int + Number of loop reactor branches + """ + + # Check that number of spargers is consistent + if n_spargers is None: + n_spargers = len(sparger_locs) + else: + assert n_spargers == len(sparger_locs) + assert n_spargers >= 1 + + # Basis check on the number of branches + assert n_branches > 0 + + # Check that locations of spargers is consistent + # There are n_branches branches. Spargers can be placed anywhere + # between edge_spacing and (1-edge_spacing) fractions of the branch + # Each sparger locations must be between 0 and n_branches*1=n_branches + assert edge_spacing > 0 + assert edge_spacing < 1 + assert all(np.array(sparger_locs) >= 0) + assert all(np.array(sparger_locs) <= float(n_branches)) + for ibranch in range(n_branches): + if ibranch == 0: + assert not np.any(np.array(sparger_locs) < edge_spacing) + if ibranch == n_branches - 1: + assert not np.any( + np.array(sparger_locs) > float(n_branches) - edge_spacing + ) + assert not np.any( + (np.array(sparger_locs) > float(ibranch) + 1.0 - edge_spacing) + & (np.array(sparger_locs) < float(ibranch) + 1.0 + edge_spacing) + ) + + # Check that spargers are sufficiently spaced out + assert sparger_spacing >= 0 + assert all(np.diff(np.sort(np.array(sparger_locs))) >= sparger_spacing) + + +def generate_single_scaledup_reactor_sparger_cases( + sparger_locs: list[float], + n_spargers: int | None = None, + sparger_spacing: float = 0.15, + edge_spacing: float = 0.2, + n_branches: int = 3, + sim_id: int = 0, + constantD: bool = True, + vvm: float = 0.4, + study_folder: str = ".", +): + """ + Generates loop reactor case with desired sparger placement configuration + + Parameters + ---------- + sparger_locs : list[float] + Location of every sparger along the loop reactor coordinate [-] + n_spargers : int|None + Number of spargers + sparger_spacing : float + Spacing between two spargers [-] + edge_spacing : float + Spacing required between any sparger and the edges of the branches [-] + n_branches : int + Number of loop reactor branches + sim_id : int + Index identifier of the simulation + constantD : bool + If true, use constant bubble diameter + If false, use population balance + vvm : float + VVM value [-] + study_folder : str + Where to generate the case + """ + + # Sanity checks + check_sparger_config( + sparger_locs=sparger_locs, + n_spargers=n_spargers, + sparger_spacing=sparger_spacing, + edge_spacing=edge_spacing, + n_branches=n_branches, + ) + + # Find on which branch is each sparger + branch_id = [int(entry) for entry in sparger_locs] + + # Case generation + templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup" + geom_dict = make_default_geom_dict_from_file( + os.path.join( + f"{BIRD_CASE_DIR}", f"{templateFolder}", "system", "mesh.json" + ) + ) + + # Start from template + shutil.copytree( + os.path.join(f"{BIRD_CASE_DIR}", f"{templateFolder}"), + os.path.join(f"{study_folder}", f"Sim_{sim_id}"), + ) + + bc_dict = {} + bc_dict["inlets"] = [] + bc_dict["outlets"] = [] + bc_dict["outlets"].append( + { + "branch_id": 6, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + bc_dict["outlets"].append( + { + "branch_id": 4, + "type": "circle", + "frac_space": 1, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "top", + } + ) + + for branch, loc in zip(branch_id, sparger_locs): + bc_dict["inlets"].append( + { + "branch_id": branch, + "type": "circle", + "frac_space": loc - branch, + "normal_dir": 1, + "radius": 0.4, + "nelements": 50, + "block_pos": "bottom", + } + ) + + generate_stl_patch( + os.path.join( + study_folder, f"Sim_{sim_id}", "system", "inlets_outlets.json" + ), + bc_dict, + geom_dict, + ) + + mix_list = [] + generate_dynamic_mixer( + os.path.join(study_folder, f"Sim_{sim_id}", "system", "mixers.json"), + mix_list, + geom_dict, + ) + overwrite_vvm( + case_folder=os.path.join(study_folder, f"Sim_{sim_id}"), vvm=vvm + ) + overwrite_bubble_size_model( + case_folder=os.path.join(study_folder, f"Sim_{sim_id}"), + constantD=constantD, + ) diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist new file mode 100644 index 00000000..b9691c3b --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist @@ -0,0 +1,3 @@ +let g:netrw_dirhistmax =10 +let g:netrw_dirhistcnt =1 +let g:netrw_dirhist_1='/home/openfoam/postProcessing/patchIntegrate(patch=inlet,field=alpha.gas)/0' diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas new file mode 100644 index 00000000..e4165b1a --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object CO2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 0; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_CO2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_CO2; + //value $f_CO2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid new file mode 100644 index 00000000..4b8ea6a0 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object CO2.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type zeroGradient; + //type fixedValue; + //value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas new file mode 100644 index 00000000..9f66b2d2 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object H2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 0; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_H2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_H2; + //value $f_H2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid new file mode 100644 index 00000000..65ae8d34 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object H2.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type zeroGradient; + //type fixedValue; + //value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas new file mode 100644 index 00000000..c1d7225f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object N2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 1; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_N2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_N2; + //value $f_N2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas new file mode 100644 index 00000000..bf0199a0 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 300; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.gas; + inletValue $internalField; + value $internalField; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid new file mode 100644 index 00000000..7101ea31 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 300; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue $internalField; + value $internalField; + } + inlet + { + type fixedValue; + value $internalField; + } + defaultFaces + { + type zeroGradient; + } + +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas new file mode 100644 index 00000000..e696566f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volVectorField; + object U.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +internalField uniform (0 0.0 0); + +#include "${FOAM_CASE}/constant/globalVars" + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + //type flowRateInletVelocity; + //massFlowRate $mflowRateGas; + //rho thermo:rho.gas; + //value $internalField; + type fixedValue; + value uniform (0 $uGasPhase 0); + } + outlet + { + type pressureInletOutletVelocity; + phi phi.gas; + value $internalField; + } + defaultFaces + { + type slip; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid new file mode 100644 index 00000000..1879e020 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volVectorField; + object U.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform (0 0 0); + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + //type flowRateInletVelocity; + //massFlowRate $mflowRateLiq; + //rho thermo:rho.liquid; + //value $internalField; + type fixedValue; + value uniform (0 0 0); + } + outlet + { + type noSlip; + } + defaultFaces + { + type noSlip; + } + +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas new file mode 100644 index 00000000..fba2945d --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object Ydefault.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid new file mode 100644 index 00000000..a5108564 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object Ydefault.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 1.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 1.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas new file mode 100644 index 00000000..1e303fbe --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + location "0"; + object alpha.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $alphaGas; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $alphaGas; + } + outlet + { + type inletOutlet; + phi phi.gas; + inletValue uniform 1; + value uniform 1; + } + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid new file mode 100644 index 00000000..5c92070b --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alpha.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 1; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $alphaLiq; + } + outlet + { + type fixedValue; + value uniform 0; + } + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas new file mode 100644 index 00000000..b867958f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type calculated; + value $internalField; + //type compressible::alphatWallFunction; + //Prt 0.85; + //value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid new file mode 100644 index 00000000..2569c3ee --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type compressible::alphatWallFunction; + Prt 0.85; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas new file mode 100644 index 00000000..707a1cda --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $eps_inlet_gas; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $eps_inlet_gas; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + //type epsilonWallFunction; + //value $internalField; + } + + // defaultFaces + // { + // type empty; + // } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid new file mode 100644 index 00000000..0a4236fd --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $eps_inlet_liq; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $eps_inlet_liq; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type epsilonWallFunction; + value $internalField; + } + +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas new file mode 100644 index 00000000..76ee77a9 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas @@ -0,0 +1,41 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object f.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 1.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 1.0; //$internalField; // + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas new file mode 100644 index 00000000..4a3d44ca --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $k_inlet_gas; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $k_inlet_gas; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid new file mode 100644 index 00000000..cde8f6c1 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $k_inlet_liq; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $k_inlet_liq; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type kqRWallFunction; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas new file mode 100644 index 00000000..ba16dd4c --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-8; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + //type nutkWallFunction; + //value $internalField; + type calculated; + value $internalField; + } + + // defaultFaces + // { + // type empty; + // } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid new file mode 100644 index 00000000..1442e07f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-4; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type nutkWallFunction; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p new file mode 100644 index 00000000..b3a295fb --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p @@ -0,0 +1,39 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 101325; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + outlet + { + type calculated; + value $internalField; + } + defaultFaces + { + type calculated; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh new file mode 100644 index 00000000..88ee7d80 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p_rgh; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 101325; + +boundaryField +{ + inlet + { + type fixedFluxPressure; + value $internalField; + } + outlet + { + type prghTotalPressure; + p0 $internalField; + U U.gas; + phi phi.gas; + rho thermo:rho.gas; + value $internalField; + } + defaultFaces + { + type fixedFluxPressure; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean new file mode 100755 index 00000000..f55e0ec9 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean @@ -0,0 +1,18 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +# Source tutorial clean functions +. $WM_PROJECT_DIR/bin/tools/CleanFunctions + +# Remove surface, features and solution +#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 +#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 +#rm -rf constant/polyMesh > /dev/null 2>&1 +#rm -rf processor* > /dev/null 2>&1 +rm -rf 0 +cleanCase + +#rm *.obj +#rm *.stl + +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh new file mode 100644 index 00000000..3756ed7f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh @@ -0,0 +1,13 @@ +if [ ! -f qoi.txt ]; then + # Reconstruct if needed + source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc + reconstructPar -newTimes + module load anaconda3/2023 + conda activate /projects/gas2fuels/conda_env/bird + python read_history.py -cr .. -cn local -df data + python get_qoi.py + conda deactivate +else + echo "WARNING: QOI already computed" +fi + diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H new file mode 100644 index 00000000..8fa7daf7 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H @@ -0,0 +1,37 @@ +#include + +double gradfunMix(double V1, double V2){ + return 3.0*V2*V2 + 2.0*V1*V2 - V1*V1; +} + +double funMix(double V1, double V2, double P, double rhoL, double A){ + return V2*V2*V2 + V1*V2*V2 - V2*V1*V1 - V1*V1*V1 - 4.0*P/(rhoL * A); +} + +double findV2(double P, double rhoL, double A, double V1) { + int newton_iter = 100; + double V2 = 2*V1; + double V2_old; + double V2_new; + if (std::abs(V1) < 1e-12) { + V2=std::pow((4.0*P/rhoL/A),0.333333); + V2_new = V2; + V2_old = V2; + } else { + for (int i=0; i("thermo:rho.liquid"); + const volScalarField& alphaL = + mesh().lookupObject("alpha.liquid"); + const volVectorField& UL = + mesh().lookupObject("U.liquid"); + double pi=3.141592654; + double source_pt_x=13.807637692813548; + double source_pt_y=1.3807637692813548; + double source_pt_z=12.426873923532193; + double disk_rad=0.9665346384969483; + double disk_area=pi*disk_rad*disk_rad; + double power=7626.034346240216; + double smear_factor=3.0; + const scalar startTime = 1; + if (time.value() > startTime) + { + // Get V1 + double source_sign_factor = 1.0; + double V1 = 0; + double V2 = 0; + double rhoV; + double dist_tol = disk_rad*5; + + double dist_n; + double upV = 0; + double uprhoV = 0; + double upVvol = 0; + double downV = 0; + double downrhoV = 0; + double downVvol = 0; + double dist2; + forAll(C,i) + { + dist2 = (C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); + dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); + dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); + + dist_n = (C[i].x()-source_pt_x); + + if (dist2 < dist_tol*dist_tol && dist_n < -dist_tol/2) { + upVvol += V[i] * alphaL[i]; + upV += V[i] * alphaL[i] * UL[i][0]; + uprhoV += V[i] * alphaL[i] * rhoL[i]; + } + if (dist2 < dist_tol*dist_tol && dist_n > dist_tol/2) { + downVvol += V[i] * alphaL[i]; + downV += V[i] * alphaL[i] * UL[i][0]; + downrhoV += V[i] * alphaL[i] * rhoL[i]; + } + } + + reduce(uprhoV, sumOp()); + reduce(downrhoV, sumOp()); + reduce(upV, sumOp()); + reduce(downV, sumOp()); + reduce(downVvol, sumOp()); + reduce(upVvol, sumOp()); + + downV /= downVvol; + upV /= upVvol; + downrhoV /= downVvol; + uprhoV /= upVvol; + + if (upV <= 0 && downV <= 0) { + source_sign_factor = -1.0; + V1 = std::abs(upV); + rhoV = uprhoV; + } else if (upV >= 0 && downV >= 0) { + source_sign_factor = 1.0; + V1 = std::abs(downV); + rhoV = downrhoV; + } else { + V1 = 0.0; + source_sign_factor = -1.0; + rhoV = uprhoV; + Foam::Info << "[BIRD:DYNMIX WARN] " << "upV = " << upV << " downV = " << downV << " for source at " << source_pt_x << ", " << source_pt_y << ", " << source_pt_z << endl; + } + Foam::Info << "[BIRD:DYNMIX INFO] V1 = " << V1 << endl; + + // Get V2 + V2 = findV2(power, rhoV, disk_area, V1); + + forAll(C,i) + { + double Thrust=0.5*rhoL[i]*(V2*V2 - V1*V1)*disk_area; + double dist2=(C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); + dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); + dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); + double epsilon=pow(V[i],0.33333)*smear_factor; + double sourceterm=alphaL[i]*(Thrust/pow(pi,1.5)/pow(epsilon,3.0))* + exp(-dist2/(epsilon*epsilon)); + Usource[i][0] -= source_sign_factor*sourceterm*V[i]; + } + } + #}; +}; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g new file mode 100644 index 00000000..770a5619 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g @@ -0,0 +1,21 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class uniformDimensionedVectorField; + location "constant"; + object g; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -2 0 0 0 0]; +value (0 -9.81 0); + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars new file mode 100644 index 00000000..1a0ce724 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars @@ -0,0 +1,83 @@ +T0 300; //initial T(K) which stays constant +VVM 1.6; +//****water Liquid properties************** +CpMixLiq 4181; +muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) +kThermLiq 0.62; // W/m-K +rho0MixLiq 1000; // kg/m^3 +sigmaLiq 0.07; //surface tension N/m +//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) +WC_psi 2.6; +WC_M 18; // kg/kmol +WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) +WC_V_H2 14.3e-3; +WC_V_CO2 34e-3; +WC_V_CO 30.7e-3; +WC_V_N2 31.2e-3; +WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) +//****** diffusion coeff *********** +D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; +D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; +D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; +D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; +D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; +//****** Henry coeff *************** +H_O2_298 0.032; +DH_O2 1700; +H_CO2_298 0.83; +DH_CO2 2400; +H_CO_298 0.023; +DH_CO 1300; +H_H2_298 0.019; +DH_H2 500; +H_CH4_298 0.032; +DH_CH4 1900; +H_N2_298 0.015; +DH_N2 1300; +He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; +He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; +He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; +He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; +He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; +//*******inlet gas frac************* +f_H2 0.1; +f_CO2 0.9; +f_N2 0.0; +//*******inlet gas frac************* +inletA 11.8966; +liqVol 606.514; +alphaGas 1; +alphaLiq 0; +uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; +//********************************* +LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; +LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; +LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 +LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; +LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; +LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; +PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; +//********************************* +kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; + +kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; + +kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; + +kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; + +kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; +//********************************* +l_scale 0.5; +intensity 0.05; +k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; +eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; +omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; +omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp new file mode 100644 index 00000000..fcb076a7 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp @@ -0,0 +1,83 @@ +T0 300; //initial T(K) which stays constant +VVM 0.2; +//****water Liquid properties************** +CpMixLiq 4181; +muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) +kThermLiq 0.62; // W/m-K +rho0MixLiq 1000; // kg/m^3 +sigmaLiq 0.07; //surface tension N/m +//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) +WC_psi 2.6; +WC_M 18; // kg/kmol +WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) +WC_V_H2 14.3e-3; +WC_V_CO2 34e-3; +WC_V_CO 30.7e-3; +WC_V_N2 31.2e-3; +WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) +//****** diffusion coeff *********** +D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; +D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; +D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; +D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; +D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; +//****** Henry coeff *************** +H_O2_298 0.032; +DH_O2 1700; +H_CO2_298 0.83; +DH_CO2 2400; +H_CO_298 0.023; +DH_CO 1300; +H_H2_298 0.019; +DH_H2 500; +H_CH4_298 0.032; +DH_CH4 1900; +H_N2_298 0.015; +DH_N2 1300; +He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; +He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; +He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; +He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; +He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; +//*******inlet gas frac************* +f_H2 0.1; +f_CO2 0.9; +f_N2 0.0; +//*******inlet gas frac************* +inletA ; +liqVol ; +alphaGas 1; +alphaLiq 0; +uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; +//********************************* +LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; +LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; +LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 +LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; +LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; +LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; +PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; +//********************************* +kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; + +kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; + +kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; + +kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; + +kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; +//********************************* +l_scale 0.5; +intensity 0.05; +k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; +eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; +omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; +omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas new file mode 100644 index 00000000..ca916714 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas @@ -0,0 +1,26 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +//simulationType laminar; +simulationType RAS; +RAS +{ + model mixtureKEpsilon; + turbulence on; + printCoeff on; +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid new file mode 100644 index 00000000..2063de0d --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid @@ -0,0 +1,27 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +//simulationType laminar; +simulationType RAS; + +RAS +{ + model mixtureKEpsilon; + turbulence on; + printCoeffs on; +} + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties new file mode 100644 index 00000000..a3c90f5a --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties @@ -0,0 +1,295 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; + +phases (gas liquid); + +populationBalances (bubbles); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel velocityGroup; + + velocityGroupCoeffs + { + populationBalance bubbles; + + shapeModel spherical; + + sizeGroups + ( + f1 {dSph 1.4e-3; value 0.0;} + f2 {dSph 1.8e-3; value 0.0;} + f3 {dSph 2.2e-3; value 0.0;} + f4 {dSph 2.6e-3; value 0.0;} + f5 {dSph 3e-3; value 1.0;} + f6 {dSph 3.4e-3; value 0.0;} + f7 {dSph 3.8e-3; value 0.0;} + f8 {dSph 4.2e-3; value 0.0;} + f9 {dSph 4.6e-3; value 0.0;} + f10 {dSph 5.0e-3; value 0.0;} + ); + } + + residualAlpha 1e-6; + + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + ( + LehrMilliesMewes{ + efficiency 4.695; + uCrit 0.08; + alphaMax 0.6; + } + ); + + binaryBreakupModels + (); + + breakupModels + ( + Laakkonen { + efficiency 13.83; + daughterSizeDistributionModel Laakkonen; + } + + ); + + driftModels + ( + densityChange{} + ); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd new file mode 100644 index 00000000..e029df99 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd @@ -0,0 +1,261 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangeMultiphaseSystem; + +phases (gas liquid); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 3e-3; + } + residualAlpha 1e-6; + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + (); + + binaryBreakupModels + (); + + breakupModels + (); + + driftModels + (); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe new file mode 100644 index 00000000..a3c90f5a --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe @@ -0,0 +1,295 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; + +phases (gas liquid); + +populationBalances (bubbles); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel velocityGroup; + + velocityGroupCoeffs + { + populationBalance bubbles; + + shapeModel spherical; + + sizeGroups + ( + f1 {dSph 1.4e-3; value 0.0;} + f2 {dSph 1.8e-3; value 0.0;} + f3 {dSph 2.2e-3; value 0.0;} + f4 {dSph 2.6e-3; value 0.0;} + f5 {dSph 3e-3; value 1.0;} + f6 {dSph 3.4e-3; value 0.0;} + f7 {dSph 3.8e-3; value 0.0;} + f8 {dSph 4.2e-3; value 0.0;} + f9 {dSph 4.6e-3; value 0.0;} + f10 {dSph 5.0e-3; value 0.0;} + ); + } + + residualAlpha 1e-6; + + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + ( + LehrMilliesMewes{ + efficiency 4.695; + uCrit 0.08; + alphaMax 0.6; + } + ); + + binaryBreakupModels + (); + + breakupModels + ( + Laakkonen { + efficiency 13.83; + daughterSizeDistributionModel Laakkonen; + } + + ); + + driftModels + ( + densityChange{} + ); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas new file mode 100644 index 00000000..11b1c4b9 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas @@ -0,0 +1,142 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +thermoType +{ + type heRhoThermo; + mixture multiComponentMixture; + transport sutherland; + thermo janaf; + equationOfState perfectGas; + specie specie; + energy sensibleInternalEnergy; + //energy sensibleEnthalpy; +} + + +species +( + H2 + CO2 + N2 +); + +defaultSpecie N2; + +CO2 +{ + specie + { + molWeight 44.00995; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.85746029 0.00441437026 -2.21481404e-06 5.23490188e-10 -4.72084164e-14 -48759.166 2.27163806 ); + lowCpCoeffs ( 2.35677352 0.00898459677 -7.12356269e-06 2.45919022e-09 -1.43699548e-13 -48371.9697 9.90105222 ); + } + transport + { + As 1.572e-06; + Ts 240; + } + elements + { + C 1; + O 2; + } +} + +water +{ + specie + { + molWeight 18.01534; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.03399249 0.00217691804 -1.64072518e-07 -9.7041987e-11 1.68200992e-14 -30004.2971 4.9667701 ); + lowCpCoeffs ( 4.19864056 -0.0020364341 6.52040211e-06 -5.48797062e-09 1.77197817e-12 -30293.7267 -0.849032208 ); + } + transport + { + As 1.512e-06; + Ts 120; + } + elements + { + H 2; + O 1; + } +} + +N2 +{ + specie + { + molWeight 28.0134; + } + thermodynamics + { + Tlow 250; + Thigh 5000; + Tcommon 1000; + highCpCoeffs ( 2.92664 0.0014879768 -5.68476e-07 1.0097038e-10 -6.753351e-15 -922.7977 5.980528 ); + lowCpCoeffs ( 3.298677 0.0014082404 -3.963222e-06 5.641515e-09 -2.444854e-12 -1020.8999 3.950372 ); + } + transport + { + As 1.512e-06; + Ts 120; + } + elements + { + N 2; + } +} + +H2 +{ + specie + { + molWeight 2.01594; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.3372792 -4.94024731e-05 4.99456778e-07 -1.79566394e-10 2.00255376e-14 -950.158922 -3.20502331 ); + lowCpCoeffs ( 2.34433112 0.00798052075 -1.9478151e-05 2.01572094e-08 -7.37611761e-12 -917.935173 0.683010238 ); + } + transport + { + As 6.362e-07; + Ts 72; + } + elements + { + H 2; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid new file mode 100644 index 00000000..d324ec51 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid @@ -0,0 +1,108 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +thermoType +{ + type heRhoThermo; + mixture multiComponentMixture; + transport const; + thermo hConst; + equationOfState rhoConst;//rPolynomial; + specie specie; + energy sensibleInternalEnergy; + //energy sensibleEnthalpy; +} + +species +( + CO2 + water + H2 +); + +inertSpecie water; + +water +{ + specie + { + molWeight 18.0153; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07; + } + transport + { + mu $muMixLiq; + Pr $PrMixLiq; + } +} + +CO2 +{ + specie + { + molWeight 44.00995; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07; + } + transport + { + mu $muMixLiq; + Pr $PrCO2; + } +} + +H2 +{ + specie + { + molWeight 2.01594; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07;//-9402451; + } + transport + { + mu $muMixLiq; + Pr $PrH2; + } +} + + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py new file mode 100644 index 00000000..9562cc65 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py @@ -0,0 +1,183 @@ +import json +import os +import pickle as pkl + +import matplotlib as mpl +import numpy as np +from prettyPlot.plotting import * +from scipy.optimize import curve_fit + + +def get_sim_folds(path): + folds = os.listdir(path) + sim_folds = [] + for fold in folds: + if fold.startswith("loop"): + sim_folds.append(fold) + return sim_folds + + +def func(t, cstar, kla): + t = t + t0 = 0 + c0 = 0 + return (cstar - c0) * (1 - np.exp(-kla * (t - t0))) + c0 + + +def get_vl(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("liqVol"): + vol = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read liqVol = {vol}m3") + return vol + + +def get_vvm(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("VVM"): + vvm = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read VVM = {vvm} [-]") + return vvm + + +def get_As(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("inletA"): + As = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read As = {As}m2") + return As + + +def get_pmix(verb=False): + with open("system/mixers.json", "r+") as f: + data = json.load(f) + mixer_list = data["mixers"] + pmix = 0 + for mix in mixer_list: + pmix += mix["power"] / 1000 + if verb: + print(f"Read Mixing power = {pmix}kW") + return pmix + + +def get_lh(verb=False): + filename = os.path.join("system", "setFieldsDict") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if "box (-1.0 -1.0 -1.0)" in line: + height = float(line.split("(")[2].split()[1]) + break + if verb: + print(f"Read Height = {height}m") + return height + + +def get_pinj(vvm, Vl, As, lh): + rhog = 1.25 # kg /m3 + Vg = Vl * vvm / (60 * As * 1) # m/s + Ptank = 101325 # Pa + # Ptank = 0 # Pa + rhoL = 1000 # kg / m3 + Pl = 101325 + rhoL * 9.8 * lh # Pa + # W + P1 = rhog * As * Vg**3 + # W + P2 = (Pl - Ptank) * As * Vg + # kg /s + MF = rhog * Vg * As + # kwh / kg + e_m = (P1 + P2) / (3600 * 1000 * MF) + + # returns kW + return (P1 + P2) * 1e-3 + + +def get_qoi(kla_co2, cs_co2, kla_h2, cs_h2, verb=False): + vvm = get_vvm(verb) + As = get_As(verb) + V_l = get_vl(verb) + liqh = get_lh(verb) + P_inj = get_pinj(vvm, V_l, As, liqh) + P_mix = get_pmix(verb) + + qoi_co2 = kla_co2 * cs_co2 * V_l * 0.04401 / (P_mix / 3600 + P_inj / 3600) + qoi_h2 = kla_h2 * cs_h2 * V_l * 0.002016 / (P_mix / 3600 + P_inj / 3600) + return qoi_co2 * qoi_h2 + + +def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): + qoi = [] + for i in range(len(kla_co2)): + if i == 0: + verb = True + else: + verb = False + qoi.append(get_qoi(kla_co2[i], cs_co2[i], kla_h2[i], cs_h2[i], verb)) + qoi = np.array(qoi) + return np.mean(qoi), np.std(qoi) + + +os.makedirs("Figures", exist_ok=True) + +dataFolder = "data" +fold = "local" + +nuq = 100 +mean_cstar_co2 = np.random.uniform(12.6, 13.3, nuq) +mean_cstar_h2 = np.random.uniform(0.902, 0.96, nuq) + + +tmp_cs_h2 = [] +tmp_cs_co2 = [] +tmp_kla_h2 = [] +tmp_kla_co2 = [] +cs_co2 = mean_cstar_co2 +cs_h2 = mean_cstar_h2 + +a = np.load(os.path.join(dataFolder, fold, "conv.npz")) +endindex = -1 +if ( + "c_h2" in a + and "c_co2" in a + and len(a["time"][:endindex] > 0) + and (a["time"][:endindex][-1] > 95) +): + for i in range(nuq): + fitparamsH2, _ = curve_fit( + func, + np.array(a["time"][:endindex]), + np.array(a["c_h2"][:endindex]), + bounds=[(cs_h2[i] - 1e-6, 0), (cs_h2[i] + 1e-6, 1)], + ) + fitparamsCO2, _ = curve_fit( + func, + np.array(a["time"][:endindex]), + np.array(a["c_co2"][:endindex]), + bounds=[(cs_co2[i] - 1e-6, 0), (cs_co2[i] + 1e-6, 1)], + ) + tmp_kla_co2.append(fitparamsCO2[1]) + tmp_kla_h2.append(fitparamsH2[1]) + tmp_cs_h2.append(cs_h2[i]) + tmp_cs_co2.append(cs_co2[i]) + +qoi_m, qoi_s = get_qoi_uq(tmp_kla_co2, tmp_cs_co2, tmp_kla_h2, tmp_cs_h2) + + +with open("qoi.txt", "w+") as f: + f.write(f"{qoi_m},{qoi_s}\n") diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh new file mode 100644 index 00000000..6d419bf9 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh @@ -0,0 +1,71 @@ +# Clean case +module load anaconda3/2023 +conda activate /projects/gas2fuels/conda_env/bird +source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc +./Allclean + +echo PRESTEP 1 +# Generate blockmeshDict +python /projects/gas2fuels/BioReactorDesign/applications/write_block_rect_mesh.py -i system/mesh.json -o system +#python ../../../applications/write_block_rect_mesh.py -i system/mesh.json -o system + +# Generate boundary stl +python /projects/gas2fuels/BioReactorDesign/applications/write_stl_patch.py -i system/inlets_outlets.json +#python ../../../applications/write_stl_patch.py -i system/inlets_outlets.json + +# Generate mixers +python /projects/gas2fuels/BioReactorDesign/applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant +#python ../../../applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant + +echo PRESTEP 2 +# Mesh gen +blockMesh -dict system/blockMeshDict + +# Inlet BC +surfaceToPatch -tol 1e-3 inlets.stl +export newmeshdir=$(foamListTimes -latestTime) +rm -rf constant/polyMesh/ +cp -r $newmeshdir/polyMesh ./constant +rm -rf $newmeshdir +cp constant/polyMesh/boundary /tmp +sed -i -e 's/inlets\.stl/inlet/g' /tmp/boundary +cat /tmp/boundary > constant/polyMesh/boundary + +# Outlet BC +surfaceToPatch -tol 1e-3 outlets.stl +export newmeshdir=$(foamListTimes -latestTime) +rm -rf constant/polyMesh/ +cp -r $newmeshdir/polyMesh ./constant +rm -rf $newmeshdir +cp constant/polyMesh/boundary /tmp +sed -i -e 's/outlets\.stl/outlet/g' /tmp/boundary +cat /tmp/boundary > constant/polyMesh/boundary + + +# Scale +transformPoints "scale=(2.7615275385627096 2.7615275385627096 2.7615275385627096)" + + +# setup IC +cp -r 0.orig 0 +setFields + +# Setup mass flow rate +# Get inlet area +postProcess -func 'patchIntegrate(patch="inlet", field="alpha.gas")' +postProcess -func writeCellVolumes +writeMeshObj + +echo PRESTEP 3 +python writeGlobalVars.py +cp constant/phaseProperties_constantd constant/phaseProperties + +conda deactivate + +if [ -f qoi.txt ]; then + rm qoi.txt +fi +if [ -f data/local/conv.npz ]; then + rm data/local/conv.npz +fi + diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py new file mode 100644 index 00000000..264711f8 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -0,0 +1,239 @@ +import argparse +import os +import sys + +import numpy as np +from prettyPlot.plotting import plt, pretty_labels + +from bird.utilities.ofio import * + + +def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + alpha_liq = field_dict["alpha_liq"] + volume = field_dict["volume"] + holdup = np.sum((1 - alpha_liq) * volume) / np.sum(volume) + return holdup, field_dict + + +def co2liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: + co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") + co2_liq = readOFScal(co2_liq_file, nCells) + # print("computing co2 liq") + field_dict["co2_liq"] = co2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + co2_liq = field_dict["co2_liq"] + met = np.sum( + alpha_liq[indliq] * co2_liq[indliq] * volume[indliq] + ) / np.sum(volume[indliq]) + return met, field_dict + + +def cliq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: + rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") + rho_liq = readOFScal(rho_liq_file, nCells) + field_dict["rho_liq"] = rho_liq + if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: + co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") + co2_liq = readOFScal(co2_liq_file, nCells) + # print("computing co2 liq") + field_dict["co2_liq"] = co2_liq + if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: + h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") + h2_liq = readOFScal(h2_liq_file, nCells) + # print("computing h2 liq") + field_dict["h2_liq"] = h2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + co2_liq = field_dict["co2_liq"] + h2_liq = field_dict["h2_liq"] + rho_liq = field_dict["rho_liq"] + + # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 + # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + + c_h2 = 1000 * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 + c_co2 = 1000 * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + + c_h2 = np.sum(c_h2 * volume[indliq]) / np.sum(volume[indliq]) + c_co2 = np.sum(c_co2 * volume[indliq]) / np.sum(volume[indliq]) + + return c_co2, c_h2, field_dict + + +def h2liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: + h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") + h2_liq = readOFScal(h2_liq_file, nCells) + # print("computing h2 liq") + field_dict["h2_liq"] = h2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + h2_liq = field_dict["h2_liq"] + met = np.sum(alpha_liq[indliq] * h2_liq[indliq] * volume[indliq]) / np.sum( + volume[indliq] + ) + return met, field_dict + + +def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + volume = field_dict["volume"] + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.0) + liqvol = np.sum(alpha_liq[indliq] * volume[indliq]) / np.sum( + volume[indliq] + ) + return liqvol, field_dict + + +parser = argparse.ArgumentParser(description="Convergence of GH") +parser.add_argument( + "-cn", + "--case_name", + type=str, + metavar="", + required=True, + help="Case name", +) +parser.add_argument( + "-df", + "--data_folder", + type=str, + metavar="", + required=False, + help="data folder name", + default="data", +) + +args, unknown = parser.parse_known_args() + + +case_root = "." # "../" +case_name = args.case_name # "12_hole_sparger_snappyRefine_700rpm_opt_coeff" +case_path = "." +dataFolder = args.data_folder + +if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): + sys.exit("WARNING: History already created, Skipping") + +time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) +cellCentres = readMesh(os.path.join(case_path, f"meshCellCentres_0.obj")) +nCells = len(cellCentres) + + +co2_history = np.zeros(len(time_str_sorted)) +c_co2_history = np.zeros(len(time_str_sorted)) +h2_history = np.zeros(len(time_str_sorted)) +c_h2_history = np.zeros(len(time_str_sorted)) +gh_history = np.zeros(len(time_str_sorted)) +liqvol_history = np.zeros(len(time_str_sorted)) +print(f"case_path = {case_path}") +field_dict = {} +for itime, time in enumerate(time_float_sorted): + time_folder = time_str_sorted[itime] + print(f"\tTime : {time_folder}") + if not field_dict == {}: + new_field_dict = {} + if "volume" in field_dict: + new_field_dict["volume"] = field_dict["volume"] + field_dict = new_field_dict + gh_history[itime], field_dict = compute_gas_holdup( + case_path, time_str_sorted[itime], nCells, field_dict + ) + co2_history[itime], field_dict = co2liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + h2_history[itime], field_dict = h2liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + liqvol_history[itime], field_dict = vol_liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + c_co2_history[itime], c_h2_history[itime], field_dict = cliq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + + +os.makedirs(dataFolder, exist_ok=True) +os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) +np.savez( + os.path.join(dataFolder, case_name, "conv.npz"), + time=np.array(time_float_sorted), + gh=gh_history, + co2=co2_history, + h2=h2_history, + vol_liq=liqvol_history, + c_h2=c_h2_history, + c_co2=c_co2_history, +) diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh new file mode 100644 index 00000000..6b7eb516 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh @@ -0,0 +1,5 @@ +multiphaseEulerFoam + + + + diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script new file mode 100755 index 00000000..090e5c05 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script @@ -0,0 +1,14 @@ +#!/bin/bash +#SBATCH --qos=high +#SBATCH --job-name=val2 +##SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=16 +#SBATCH --time=07:59:00 +#SBATCH --account=co2snow + +bash presteps.sh +source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc +decomposePar -fileHandler collated +srun -n 16 multiphaseEulerFoam -parallel -fileHandler collated +reconstructPar -newTimes diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post new file mode 100755 index 00000000..aabbc33e --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post @@ -0,0 +1,10 @@ +#!/bin/bash +#SBATCH --qos=high +#SBATCH --job-name=val2 +##SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=16 +#SBATCH --time=00:59:00 +#SBATCH --account=co2snow + +bash computeQOI.sh diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict new file mode 100644 index 00000000..1183d262 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict @@ -0,0 +1,746 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object blockMeshDict; +} + +convertToMeters 1.0; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +vertices +( +( 0.0 0.0 0.0) +( 1.0 0.0 0.0) +( 2.0 0.0 0.0) +( 3.0 0.0 0.0) +( 4.0 0.0 0.0) +( 5.0 0.0 0.0) +( 6.0 0.0 0.0) +( 7.0 0.0 0.0) +( 8.0 0.0 0.0) +( 9.0 0.0 0.0) +( 10.0 0.0 0.0) +( 0.0 1.0 0.0) +( 1.0 1.0 0.0) +( 2.0 1.0 0.0) +( 3.0 1.0 0.0) +( 4.0 1.0 0.0) +( 5.0 1.0 0.0) +( 6.0 1.0 0.0) +( 7.0 1.0 0.0) +( 8.0 1.0 0.0) +( 9.0 1.0 0.0) +( 10.0 1.0 0.0) +( 0.0 2.0 0.0) +( 1.0 2.0 0.0) +( 2.0 2.0 0.0) +( 3.0 2.0 0.0) +( 4.0 2.0 0.0) +( 5.0 2.0 0.0) +( 6.0 2.0 0.0) +( 7.0 2.0 0.0) +( 8.0 2.0 0.0) +( 9.0 2.0 0.0) +( 10.0 2.0 0.0) +( 0.0 3.0 0.0) +( 1.0 3.0 0.0) +( 2.0 3.0 0.0) +( 3.0 3.0 0.0) +( 4.0 3.0 0.0) +( 5.0 3.0 0.0) +( 6.0 3.0 0.0) +( 7.0 3.0 0.0) +( 8.0 3.0 0.0) +( 9.0 3.0 0.0) +( 10.0 3.0 0.0) +( 0.0 4.0 0.0) +( 1.0 4.0 0.0) +( 2.0 4.0 0.0) +( 3.0 4.0 0.0) +( 4.0 4.0 0.0) +( 5.0 4.0 0.0) +( 6.0 4.0 0.0) +( 7.0 4.0 0.0) +( 8.0 4.0 0.0) +( 9.0 4.0 0.0) +( 10.0 4.0 0.0) +( 0.0 5.0 0.0) +( 1.0 5.0 0.0) +( 2.0 5.0 0.0) +( 3.0 5.0 0.0) +( 4.0 5.0 0.0) +( 5.0 5.0 0.0) +( 6.0 5.0 0.0) +( 7.0 5.0 0.0) +( 8.0 5.0 0.0) +( 9.0 5.0 0.0) +( 10.0 5.0 0.0) +( 0.0 6.0 0.0) +( 1.0 6.0 0.0) +( 2.0 6.0 0.0) +( 3.0 6.0 0.0) +( 4.0 6.0 0.0) +( 5.0 6.0 0.0) +( 6.0 6.0 0.0) +( 7.0 6.0 0.0) +( 8.0 6.0 0.0) +( 9.0 6.0 0.0) +( 10.0 6.0 0.0) +( 0.0 7.0 0.0) +( 1.0 7.0 0.0) +( 2.0 7.0 0.0) +( 3.0 7.0 0.0) +( 4.0 7.0 0.0) +( 5.0 7.0 0.0) +( 6.0 7.0 0.0) +( 7.0 7.0 0.0) +( 8.0 7.0 0.0) +( 9.0 7.0 0.0) +( 10.0 7.0 0.0) +( 0.0 0.0 1.0) +( 1.0 0.0 1.0) +( 2.0 0.0 1.0) +( 3.0 0.0 1.0) +( 4.0 0.0 1.0) +( 5.0 0.0 1.0) +( 6.0 0.0 1.0) +( 7.0 0.0 1.0) +( 8.0 0.0 1.0) +( 9.0 0.0 1.0) +( 10.0 0.0 1.0) +( 0.0 1.0 1.0) +( 1.0 1.0 1.0) +( 2.0 1.0 1.0) +( 3.0 1.0 1.0) +( 4.0 1.0 1.0) +( 5.0 1.0 1.0) +( 6.0 1.0 1.0) +( 7.0 1.0 1.0) +( 8.0 1.0 1.0) +( 9.0 1.0 1.0) +( 10.0 1.0 1.0) +( 0.0 2.0 1.0) +( 1.0 2.0 1.0) +( 2.0 2.0 1.0) +( 3.0 2.0 1.0) +( 4.0 2.0 1.0) +( 5.0 2.0 1.0) +( 6.0 2.0 1.0) +( 7.0 2.0 1.0) +( 8.0 2.0 1.0) +( 9.0 2.0 1.0) +( 10.0 2.0 1.0) +( 0.0 3.0 1.0) +( 1.0 3.0 1.0) +( 2.0 3.0 1.0) +( 3.0 3.0 1.0) +( 4.0 3.0 1.0) +( 5.0 3.0 1.0) +( 6.0 3.0 1.0) +( 7.0 3.0 1.0) +( 8.0 3.0 1.0) +( 9.0 3.0 1.0) +( 10.0 3.0 1.0) +( 0.0 4.0 1.0) +( 1.0 4.0 1.0) +( 2.0 4.0 1.0) +( 3.0 4.0 1.0) +( 4.0 4.0 1.0) +( 5.0 4.0 1.0) +( 6.0 4.0 1.0) +( 7.0 4.0 1.0) +( 8.0 4.0 1.0) +( 9.0 4.0 1.0) +( 10.0 4.0 1.0) +( 0.0 5.0 1.0) +( 1.0 5.0 1.0) +( 2.0 5.0 1.0) +( 3.0 5.0 1.0) +( 4.0 5.0 1.0) +( 5.0 5.0 1.0) +( 6.0 5.0 1.0) +( 7.0 5.0 1.0) +( 8.0 5.0 1.0) +( 9.0 5.0 1.0) +( 10.0 5.0 1.0) +( 0.0 6.0 1.0) +( 1.0 6.0 1.0) +( 2.0 6.0 1.0) +( 3.0 6.0 1.0) +( 4.0 6.0 1.0) +( 5.0 6.0 1.0) +( 6.0 6.0 1.0) +( 7.0 6.0 1.0) +( 8.0 6.0 1.0) +( 9.0 6.0 1.0) +( 10.0 6.0 1.0) +( 0.0 7.0 1.0) +( 1.0 7.0 1.0) +( 2.0 7.0 1.0) +( 3.0 7.0 1.0) +( 4.0 7.0 1.0) +( 5.0 7.0 1.0) +( 6.0 7.0 1.0) +( 7.0 7.0 1.0) +( 8.0 7.0 1.0) +( 9.0 7.0 1.0) +( 10.0 7.0 1.0) +( 0.0 0.0 2.0) +( 1.0 0.0 2.0) +( 2.0 0.0 2.0) +( 3.0 0.0 2.0) +( 4.0 0.0 2.0) +( 5.0 0.0 2.0) +( 6.0 0.0 2.0) +( 7.0 0.0 2.0) +( 8.0 0.0 2.0) +( 9.0 0.0 2.0) +( 10.0 0.0 2.0) +( 0.0 1.0 2.0) +( 1.0 1.0 2.0) +( 2.0 1.0 2.0) +( 3.0 1.0 2.0) +( 4.0 1.0 2.0) +( 5.0 1.0 2.0) +( 6.0 1.0 2.0) +( 7.0 1.0 2.0) +( 8.0 1.0 2.0) +( 9.0 1.0 2.0) +( 10.0 1.0 2.0) +( 0.0 2.0 2.0) +( 1.0 2.0 2.0) +( 2.0 2.0 2.0) +( 3.0 2.0 2.0) +( 4.0 2.0 2.0) +( 5.0 2.0 2.0) +( 6.0 2.0 2.0) +( 7.0 2.0 2.0) +( 8.0 2.0 2.0) +( 9.0 2.0 2.0) +( 10.0 2.0 2.0) +( 0.0 3.0 2.0) +( 1.0 3.0 2.0) +( 2.0 3.0 2.0) +( 3.0 3.0 2.0) +( 4.0 3.0 2.0) +( 5.0 3.0 2.0) +( 6.0 3.0 2.0) +( 7.0 3.0 2.0) +( 8.0 3.0 2.0) +( 9.0 3.0 2.0) +( 10.0 3.0 2.0) +( 0.0 4.0 2.0) +( 1.0 4.0 2.0) +( 2.0 4.0 2.0) +( 3.0 4.0 2.0) +( 4.0 4.0 2.0) +( 5.0 4.0 2.0) +( 6.0 4.0 2.0) +( 7.0 4.0 2.0) +( 8.0 4.0 2.0) +( 9.0 4.0 2.0) +( 10.0 4.0 2.0) +( 0.0 5.0 2.0) +( 1.0 5.0 2.0) +( 2.0 5.0 2.0) +( 3.0 5.0 2.0) +( 4.0 5.0 2.0) +( 5.0 5.0 2.0) +( 6.0 5.0 2.0) +( 7.0 5.0 2.0) +( 8.0 5.0 2.0) +( 9.0 5.0 2.0) +( 10.0 5.0 2.0) +( 0.0 6.0 2.0) +( 1.0 6.0 2.0) +( 2.0 6.0 2.0) +( 3.0 6.0 2.0) +( 4.0 6.0 2.0) +( 5.0 6.0 2.0) +( 6.0 6.0 2.0) +( 7.0 6.0 2.0) +( 8.0 6.0 2.0) +( 9.0 6.0 2.0) +( 10.0 6.0 2.0) +( 0.0 7.0 2.0) +( 1.0 7.0 2.0) +( 2.0 7.0 2.0) +( 3.0 7.0 2.0) +( 4.0 7.0 2.0) +( 5.0 7.0 2.0) +( 6.0 7.0 2.0) +( 7.0 7.0 2.0) +( 8.0 7.0 2.0) +( 9.0 7.0 2.0) +( 10.0 7.0 2.0) +( 0.0 0.0 3.0) +( 1.0 0.0 3.0) +( 2.0 0.0 3.0) +( 3.0 0.0 3.0) +( 4.0 0.0 3.0) +( 5.0 0.0 3.0) +( 6.0 0.0 3.0) +( 7.0 0.0 3.0) +( 8.0 0.0 3.0) +( 9.0 0.0 3.0) +( 10.0 0.0 3.0) +( 0.0 1.0 3.0) +( 1.0 1.0 3.0) +( 2.0 1.0 3.0) +( 3.0 1.0 3.0) +( 4.0 1.0 3.0) +( 5.0 1.0 3.0) +( 6.0 1.0 3.0) +( 7.0 1.0 3.0) +( 8.0 1.0 3.0) +( 9.0 1.0 3.0) +( 10.0 1.0 3.0) +( 0.0 2.0 3.0) +( 1.0 2.0 3.0) +( 2.0 2.0 3.0) +( 3.0 2.0 3.0) +( 4.0 2.0 3.0) +( 5.0 2.0 3.0) +( 6.0 2.0 3.0) +( 7.0 2.0 3.0) +( 8.0 2.0 3.0) +( 9.0 2.0 3.0) +( 10.0 2.0 3.0) +( 0.0 3.0 3.0) +( 1.0 3.0 3.0) +( 2.0 3.0 3.0) +( 3.0 3.0 3.0) +( 4.0 3.0 3.0) +( 5.0 3.0 3.0) +( 6.0 3.0 3.0) +( 7.0 3.0 3.0) +( 8.0 3.0 3.0) +( 9.0 3.0 3.0) +( 10.0 3.0 3.0) +( 0.0 4.0 3.0) +( 1.0 4.0 3.0) +( 2.0 4.0 3.0) +( 3.0 4.0 3.0) +( 4.0 4.0 3.0) +( 5.0 4.0 3.0) +( 6.0 4.0 3.0) +( 7.0 4.0 3.0) +( 8.0 4.0 3.0) +( 9.0 4.0 3.0) +( 10.0 4.0 3.0) +( 0.0 5.0 3.0) +( 1.0 5.0 3.0) +( 2.0 5.0 3.0) +( 3.0 5.0 3.0) +( 4.0 5.0 3.0) +( 5.0 5.0 3.0) +( 6.0 5.0 3.0) +( 7.0 5.0 3.0) +( 8.0 5.0 3.0) +( 9.0 5.0 3.0) +( 10.0 5.0 3.0) +( 0.0 6.0 3.0) +( 1.0 6.0 3.0) +( 2.0 6.0 3.0) +( 3.0 6.0 3.0) +( 4.0 6.0 3.0) +( 5.0 6.0 3.0) +( 6.0 6.0 3.0) +( 7.0 6.0 3.0) +( 8.0 6.0 3.0) +( 9.0 6.0 3.0) +( 10.0 6.0 3.0) +( 0.0 7.0 3.0) +( 1.0 7.0 3.0) +( 2.0 7.0 3.0) +( 3.0 7.0 3.0) +( 4.0 7.0 3.0) +( 5.0 7.0 3.0) +( 6.0 7.0 3.0) +( 7.0 7.0 3.0) +( 8.0 7.0 3.0) +( 9.0 7.0 3.0) +( 10.0 7.0 3.0) +( 0.0 0.0 4.0) +( 1.0 0.0 4.0) +( 2.0 0.0 4.0) +( 3.0 0.0 4.0) +( 4.0 0.0 4.0) +( 5.0 0.0 4.0) +( 6.0 0.0 4.0) +( 7.0 0.0 4.0) +( 8.0 0.0 4.0) +( 9.0 0.0 4.0) +( 10.0 0.0 4.0) +( 0.0 1.0 4.0) +( 1.0 1.0 4.0) +( 2.0 1.0 4.0) +( 3.0 1.0 4.0) +( 4.0 1.0 4.0) +( 5.0 1.0 4.0) +( 6.0 1.0 4.0) +( 7.0 1.0 4.0) +( 8.0 1.0 4.0) +( 9.0 1.0 4.0) +( 10.0 1.0 4.0) +( 0.0 2.0 4.0) +( 1.0 2.0 4.0) +( 2.0 2.0 4.0) +( 3.0 2.0 4.0) +( 4.0 2.0 4.0) +( 5.0 2.0 4.0) +( 6.0 2.0 4.0) +( 7.0 2.0 4.0) +( 8.0 2.0 4.0) +( 9.0 2.0 4.0) +( 10.0 2.0 4.0) +( 0.0 3.0 4.0) +( 1.0 3.0 4.0) +( 2.0 3.0 4.0) +( 3.0 3.0 4.0) +( 4.0 3.0 4.0) +( 5.0 3.0 4.0) +( 6.0 3.0 4.0) +( 7.0 3.0 4.0) +( 8.0 3.0 4.0) +( 9.0 3.0 4.0) +( 10.0 3.0 4.0) +( 0.0 4.0 4.0) +( 1.0 4.0 4.0) +( 2.0 4.0 4.0) +( 3.0 4.0 4.0) +( 4.0 4.0 4.0) +( 5.0 4.0 4.0) +( 6.0 4.0 4.0) +( 7.0 4.0 4.0) +( 8.0 4.0 4.0) +( 9.0 4.0 4.0) +( 10.0 4.0 4.0) +( 0.0 5.0 4.0) +( 1.0 5.0 4.0) +( 2.0 5.0 4.0) +( 3.0 5.0 4.0) +( 4.0 5.0 4.0) +( 5.0 5.0 4.0) +( 6.0 5.0 4.0) +( 7.0 5.0 4.0) +( 8.0 5.0 4.0) +( 9.0 5.0 4.0) +( 10.0 5.0 4.0) +( 0.0 6.0 4.0) +( 1.0 6.0 4.0) +( 2.0 6.0 4.0) +( 3.0 6.0 4.0) +( 4.0 6.0 4.0) +( 5.0 6.0 4.0) +( 6.0 6.0 4.0) +( 7.0 6.0 4.0) +( 8.0 6.0 4.0) +( 9.0 6.0 4.0) +( 10.0 6.0 4.0) +( 0.0 7.0 4.0) +( 1.0 7.0 4.0) +( 2.0 7.0 4.0) +( 3.0 7.0 4.0) +( 4.0 7.0 4.0) +( 5.0 7.0 4.0) +( 6.0 7.0 4.0) +( 7.0 7.0 4.0) +( 8.0 7.0 4.0) +( 9.0 7.0 4.0) +( 10.0 7.0 4.0) +( 0.0 0.0 5.0) +( 1.0 0.0 5.0) +( 2.0 0.0 5.0) +( 3.0 0.0 5.0) +( 4.0 0.0 5.0) +( 5.0 0.0 5.0) +( 6.0 0.0 5.0) +( 7.0 0.0 5.0) +( 8.0 0.0 5.0) +( 9.0 0.0 5.0) +( 10.0 0.0 5.0) +( 0.0 1.0 5.0) +( 1.0 1.0 5.0) +( 2.0 1.0 5.0) +( 3.0 1.0 5.0) +( 4.0 1.0 5.0) +( 5.0 1.0 5.0) +( 6.0 1.0 5.0) +( 7.0 1.0 5.0) +( 8.0 1.0 5.0) +( 9.0 1.0 5.0) +( 10.0 1.0 5.0) +( 0.0 2.0 5.0) +( 1.0 2.0 5.0) +( 2.0 2.0 5.0) +( 3.0 2.0 5.0) +( 4.0 2.0 5.0) +( 5.0 2.0 5.0) +( 6.0 2.0 5.0) +( 7.0 2.0 5.0) +( 8.0 2.0 5.0) +( 9.0 2.0 5.0) +( 10.0 2.0 5.0) +( 0.0 3.0 5.0) +( 1.0 3.0 5.0) +( 2.0 3.0 5.0) +( 3.0 3.0 5.0) +( 4.0 3.0 5.0) +( 5.0 3.0 5.0) +( 6.0 3.0 5.0) +( 7.0 3.0 5.0) +( 8.0 3.0 5.0) +( 9.0 3.0 5.0) +( 10.0 3.0 5.0) +( 0.0 4.0 5.0) +( 1.0 4.0 5.0) +( 2.0 4.0 5.0) +( 3.0 4.0 5.0) +( 4.0 4.0 5.0) +( 5.0 4.0 5.0) +( 6.0 4.0 5.0) +( 7.0 4.0 5.0) +( 8.0 4.0 5.0) +( 9.0 4.0 5.0) +( 10.0 4.0 5.0) +( 0.0 5.0 5.0) +( 1.0 5.0 5.0) +( 2.0 5.0 5.0) +( 3.0 5.0 5.0) +( 4.0 5.0 5.0) +( 5.0 5.0 5.0) +( 6.0 5.0 5.0) +( 7.0 5.0 5.0) +( 8.0 5.0 5.0) +( 9.0 5.0 5.0) +( 10.0 5.0 5.0) +( 0.0 6.0 5.0) +( 1.0 6.0 5.0) +( 2.0 6.0 5.0) +( 3.0 6.0 5.0) +( 4.0 6.0 5.0) +( 5.0 6.0 5.0) +( 6.0 6.0 5.0) +( 7.0 6.0 5.0) +( 8.0 6.0 5.0) +( 9.0 6.0 5.0) +( 10.0 6.0 5.0) +( 0.0 7.0 5.0) +( 1.0 7.0 5.0) +( 2.0 7.0 5.0) +( 3.0 7.0 5.0) +( 4.0 7.0 5.0) +( 5.0 7.0 5.0) +( 6.0 7.0 5.0) +( 7.0 7.0 5.0) +( 8.0 7.0 5.0) +( 9.0 7.0 5.0) +( 10.0 7.0 5.0) +); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +blocks +( + + //block 0 +hex (0 1 12 11 88 89 100 99 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 1 +hex (1 2 13 12 89 90 101 100 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 2 +hex (2 3 14 13 90 91 102 101 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 3 +hex (3 4 15 14 91 92 103 102 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 4 +hex (4 5 16 15 92 93 104 103 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 5 +hex (5 6 17 16 93 94 105 104 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 6 +hex (6 7 18 17 94 95 106 105 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 7 +hex (7 8 19 18 95 96 107 106 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 8 +hex (8 9 20 19 96 97 108 107 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 9 +hex (9 10 21 20 97 98 109 108 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 10 +hex (97 98 109 108 185 186 197 196 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 11 +hex (185 186 197 196 273 274 285 284 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 12 +hex (273 274 285 284 361 362 373 372 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 13 +hex (361 362 373 372 449 450 461 460 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 14 +hex (360 361 372 371 448 449 460 459 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 15 +hex (359 360 371 370 447 448 459 458 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 16 +hex (358 359 370 369 446 447 458 457 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 17 +hex (357 358 369 368 445 446 457 456 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 18 +hex (356 357 368 367 444 445 456 455 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 19 +hex (355 356 367 366 443 444 455 454 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 20 +hex (354 355 366 365 442 443 454 453 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 21 +hex (353 354 365 364 441 442 453 452 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 22 +hex (352 353 364 363 440 441 452 451 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 23 +hex (363 364 375 374 451 452 463 462 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 24 +hex (374 375 386 385 462 463 474 473 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 25 +hex (385 386 397 396 473 474 485 484 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 26 +hex (396 397 408 407 484 485 496 495 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 27 +hex (407 408 419 418 495 496 507 506 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 28 +hex (418 419 430 429 506 507 518 517 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 29 +hex (308 309 320 319 396 397 408 407 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 30 +hex (220 221 232 231 308 309 320 319 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 31 +hex (132 133 144 143 220 221 232 231 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 32 +hex (44 45 56 55 132 133 144 143 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 33 +hex (55 56 67 66 143 144 155 154 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 34 +hex (66 67 78 77 154 155 166 165 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 35 +hex (33 34 45 44 121 122 133 132 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 36 +hex (22 23 34 33 110 111 122 121 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 37 +hex (11 12 23 22 99 100 111 110 ) +( 10 10 10 ) +SimpleGrading (1 1 1) +); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +defaultPatch +{ type wall;} + +patches +( +); diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict new file mode 100644 index 00000000..7f457c4b --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict @@ -0,0 +1,67 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object controlDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +application multiphaseEulerFoam; + +startFrom latestTime;//startTime; + +startTime 0; + +stopAt endTime; + +endTime 200; + +deltaT 0.0001; + +writeControl adjustableRunTime; + +writeInterval 2; + +purgeWrite 0; + +writeFormat ascii; + +writePrecision 6; + +writeCompression off; + +timeFormat general; + +timePrecision 6; + +runTimeModifiable yes; + +adjustTimeStep yes; + +maxCo 0.5; + +maxDeltaT 0.01; + + +functions +{ + + #includeFunc writeObjects(d.gas) + #includeFunc writeObjects(thermo:rho.gas) + #includeFunc writeObjects(thermo:rho.liquid) +} +//functions +//{ +// #includeFunc fieldAverage(U.air, U.water, alpha.air, p) +//} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict new file mode 100755 index 00000000..f8397e73 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict @@ -0,0 +1,30 @@ +/*--------------------------------*- C++ -*----------------------------------*\ +| ========= | | +| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | +| \\ / O peration | Version: 3.0.x | +| \\ / A nd | Web: www.OpenFOAM.org | +| \\/ M anipulation | | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object decomposeParDict; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +numberOfSubdomains 16; + +method scotch; + +hierarchicalCoeffs +{ + n (4 4 1); + delta 0.001; + order xyz; +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints new file mode 100644 index 00000000..334f1c8f --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints @@ -0,0 +1,56 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object fvConstraints; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +limitp +{ + type limitPressure; + + min 1e4; +} +limitUliq +{ + type limitVelocity; + active yes; + U U.liquid; + selectionMode all; + max 1e1; +} +limitUgas +{ + type limitVelocity; + active yes; + U U.gas; + selectionMode all; + max 2e1; +} +limitTgas +{ + type limitTemperature; + selectionMode all; + min 290; + max 310; + phase gas; +} +limitTliq +{ + type limitTemperature; + selectionMode all; + min 290; + max 310; + phase liquid; +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes new file mode 100644 index 00000000..52e6e13a --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes @@ -0,0 +1,70 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSchemes; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +ddtSchemes +{ + default Euler; +} + +gradSchemes +{ + default Gauss linear; + limited cellLimited Gauss linear 1; +} + +divSchemes +{ + default none; + + "div\(phi,alpha.*\)" Gauss vanLeer; + + "div\(phir,alpha.*,alpha.*\)" Gauss vanLeer; + + "div\(alphaRhoPhi.*,U.*\)" Gauss limitedLinearV 1; + "div\(phi.*,U.*\)" Gauss limitedLinearV 1; + "div\(alphaRhoPhi.*,Yi\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,(h|e).*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,(K|k|epsilon|omega).*\)" Gauss limitedLinear 1; + "div\(alphaPhi.*,f.*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,\(p\|thermo:rho.*\)\)" Gauss limitedLinear 1; + + "div\(phim,(k|epsilon)m\)" Gauss upwind; + "div\(\(\(\(alpha.*\*thermo:rho.*\)*nuEff.*\)*dev2\(T\(grad\(U.*\)\)\)\)\)" Gauss linear; +} + +laplacianSchemes +{ + default Gauss linear corrected; +} + +interpolationSchemes +{ + default linear; +} + +snGradSchemes +{ + default uncorrected; +} + +wallDist +{ + method Poisson; + nRequired true; +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution new file mode 100644 index 00000000..2e69fdfa --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution @@ -0,0 +1,120 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSolution; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +solvers +{ + "alpha.*" + { + nAlphaCorr 2; + nAlphaSubCycles 5; + } + + bubbles + { + nCorr 1; + tolerance 1e-4; + scale true; + solveOnFinalIterOnly true; + sourceUpdateInterval 1; + } + + p_rgh + { + solver GAMG; + smoother DIC; + tolerance 1e-7; + relTol 0; + } + + p_rghFinal + { + $p_rgh; + relTol 0; + } + + "(k|omega).*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-7; + relTol 0; + minIter 1; + } + + "(e|h).*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-8; + relTol 0; + minIter 0; + maxIter 3; + } + + "f.*" + { + solver PBiCGStab; + preconditioner DILU; + tolerance 1e-6; + relTol 0; + } + + "Yi.*" + { + solver PBiCGStab; + preconditioner DILU; + tolerance 1e-12; + relTol 0; + residualAlpha 1e-8; + } + + "U.*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-5; + relTol 0; + minIter 1; + } + + yPsi + { + solver PCG; + preconditioner DIC; + tolerance 1e-10; + relTol 0; + } + +} + +PIMPLE +{ + nOuterCorrectors 3; + nCorrectors 1; + nNonOrthogonalCorrectors 0; + +} + +relaxationFactors +{ + equations + { + ".*" 1; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json new file mode 100644 index 00000000..2b53da73 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json @@ -0,0 +1,28 @@ +{ + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0}, + "y" : {"nblocks": 11, "size_per_block": 1.0}, + "z" : {"nblocks": 5, "size_per_block": 1.0} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + }, + "inlets": [ + {"branch_id": 0, "type": "circle", "frac_space": 0.2222222222222222, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "bottom"}, + {"branch_id": 0, "type": "circle", "frac_space": 0.5, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"}, + {"branch_id": 0, "type": "circle", "frac_space": 0.7777777777777778, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"} + ], + "outlets": [ + {"branch_id": 6, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"}, + {"branch_id": 4, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"} + ] +} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json new file mode 100644 index 00000000..29841d7e --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json @@ -0,0 +1,26 @@ +{ + "Meshing": { + "Blockwise": { + "x" : 10, + "y" : 10, + "z" : 10 + } + }, + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0}, + "y" : {"nblocks": 11, "size_per_block": 1.0}, + "z" : {"nblocks": 5, "size_per_block": 1.0} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + } +} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json new file mode 100644 index 00000000..b6224fb7 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json @@ -0,0 +1,29 @@ +{ + "Meshing": { + "Blockwise": { + "x" : 10, + "y" : 10, + "z" : 10 + } + }, + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0, "rescale": 2.7615275385627096}, + "y" : {"nblocks": 11, "size_per_block": 1.0, "rescale": 2.7615275385627096}, + "z" : {"nblocks": 5, "size_per_block": 1.0, "rescale": 2.7615275385627096} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + }, + "mixers": [ + {"branch_id": 2, "frac_space": 0.5, "start_time": 4, "power": 1500, "sign": "+"} + ] +} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict new file mode 100644 index 00000000..89a797b9 --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict @@ -0,0 +1,37 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object setFieldsDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +defaultFieldValues +( + volScalarFieldValue alpha.gas 0.99 + volScalarFieldValue alpha.liquid 0.01 +); + +regions +( + boxToCell + { + box (-1.0 -1.0 -1.0) (552.3 11.046 552.3); + fieldValues + ( + volScalarFieldValue alpha.gas 0.01 + volScalarFieldValue alpha.liquid 0.99 + ); + } +); + + +// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py new file mode 100644 index 00000000..0594eccc --- /dev/null +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py @@ -0,0 +1,47 @@ +import os + +import numpy as np + +from bird.utilities.ofio import * + + +def writeGvars(inletA, liqVol): + filename_tmp = os.path.join("constant", "globalVars_temp") + with open(filename_tmp, "r+") as f: + lines = f.readlines() + filename = os.path.join("constant", "globalVars") + with open(filename, "w+") as f: + for line in lines: + if line.startswith("inletA"): + f.write(f"inletA\t{inletA:g};\n") + elif line.startswith("liqVol"): + f.write(f"liqVol\t{liqVol:g};\n") + else: + f.write(line) + + +def readInletArea(): + filename = os.path.join( + "postProcessing", + "patchIntegrate(patch=inlet,field=alpha.gas)", + "0", + "surfaceFieldValue.dat", + ) + with open(filename, "r+") as f: + lines = f.readlines() + return float(lines[4].split()[-1]) + + +def getLiqVol(): + cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + alpha_field = readOFScal( + os.path.join("0", "alpha.liquid"), len(cellCentres) + ) + return np.sum(volume_field * alpha_field) + + +if __name__ == "__main__": + A = readInletArea() + V = getLiqVol() + writeGvars(A, V) From eb12b72e0e755b926ea0a761a1878bbb25bf4f08 Mon Sep 17 00:00:00 2001 From: Malik Hassanaly Date: Wed, 21 May 2025 09:45:45 -0600 Subject: [PATCH 02/86] fix presteps --- .../presteps.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh index 6d419bf9..77fa6d6f 100644 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh @@ -1,21 +1,20 @@ # Clean case -module load anaconda3/2023 -conda activate /projects/gas2fuels/conda_env/bird +module load conda +conda activate /projects/gas2fuels/conda_env/bird_wf source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc ./Allclean +BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` + echo PRESTEP 1 # Generate blockmeshDict -python /projects/gas2fuels/BioReactorDesign/applications/write_block_rect_mesh.py -i system/mesh.json -o system -#python ../../../applications/write_block_rect_mesh.py -i system/mesh.json -o system +python ${BIRD_HOME}/../applications/write_block_rect_mesh.py -i system/mesh.json -o system # Generate boundary stl -python /projects/gas2fuels/BioReactorDesign/applications/write_stl_patch.py -i system/inlets_outlets.json -#python ../../../applications/write_stl_patch.py -i system/inlets_outlets.json +python ${BIRD_HOME}/../applications/write_stl_patch.py -i system/inlets_outlets.json # Generate mixers -python /projects/gas2fuels/BioReactorDesign/applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant -#python ../../../applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant +python ${BIRD_HOME}/../applications/write_dynMix_fvModels.py -fs -i system/mixers.json -o constant echo PRESTEP 2 # Mesh gen From ff271083d80d23fe320fd7f33269c41c321e4b54 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 22 May 2025 11:45:38 -0600 Subject: [PATCH 03/86] get rid of all multiphaseEulerFoam ref --- experimental_cases/deckwer17/system/controlDict | 2 +- experimental_cases/deckwer19/system/controlDict | 2 +- .../disengagement/bubble_column_pbe_20L/system/controlDict | 2 +- papers/co2_model/cases/breakup_17_mesh1_opt/system/controlDict | 2 +- papers/co2_model/cases/breakup_19_mesh1_opt/system/controlDict | 2 +- tutorial_cases/airlift_40m/submitjob | 2 +- tutorial_cases/bubble_column_20L/system/controlDict | 2 +- tutorial_cases/loop_reactor_mixing/script | 2 +- tutorial_cases/loop_reactor_mixing/system/controlDict | 2 +- .../loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh | 2 +- .../loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script | 2 +- .../system/controlDict | 2 +- tutorial_cases/loop_reactor_reacting/system/controlDict | 2 +- tutorial_cases/side_sparger/system/controlDict | 2 +- tutorial_cases/side_sparger/system/controlDict.first | 2 +- tutorial_cases/side_sparger/system/controlDict.second | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/experimental_cases/deckwer17/system/controlDict b/experimental_cases/deckwer17/system/controlDict index 61296ae9..6942e6d0 100644 --- a/experimental_cases/deckwer17/system/controlDict +++ b/experimental_cases/deckwer17/system/controlDict @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/experimental_cases/deckwer19/system/controlDict b/experimental_cases/deckwer19/system/controlDict index 61296ae9..6942e6d0 100644 --- a/experimental_cases/deckwer19/system/controlDict +++ b/experimental_cases/deckwer19/system/controlDict @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/experimental_cases/disengagement/bubble_column_pbe_20L/system/controlDict b/experimental_cases/disengagement/bubble_column_pbe_20L/system/controlDict index f46a636b..0f3246f2 100644 --- a/experimental_cases/disengagement/bubble_column_pbe_20L/system/controlDict +++ b/experimental_cases/disengagement/bubble_column_pbe_20L/system/controlDict @@ -14,7 +14,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime;//startTime; diff --git a/papers/co2_model/cases/breakup_17_mesh1_opt/system/controlDict b/papers/co2_model/cases/breakup_17_mesh1_opt/system/controlDict index f62759d0..fce220d3 100644 --- a/papers/co2_model/cases/breakup_17_mesh1_opt/system/controlDict +++ b/papers/co2_model/cases/breakup_17_mesh1_opt/system/controlDict @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/papers/co2_model/cases/breakup_19_mesh1_opt/system/controlDict b/papers/co2_model/cases/breakup_19_mesh1_opt/system/controlDict index f62759d0..fce220d3 100644 --- a/papers/co2_model/cases/breakup_19_mesh1_opt/system/controlDict +++ b/papers/co2_model/cases/breakup_19_mesh1_opt/system/controlDict @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/tutorial_cases/airlift_40m/submitjob b/tutorial_cases/airlift_40m/submitjob index 9f34f963..9dffb4d0 100644 --- a/tutorial_cases/airlift_40m/submitjob +++ b/tutorial_cases/airlift_40m/submitjob @@ -17,4 +17,4 @@ module load openmpi/1.10.7/gcc-7.3.0 module load gcc source /projects/bpms/openfoam/OpenFOAM-dev/etc/bashrc . ./presteps.sh -srun -n 144 multiphaseEulerFoam -parallel +srun -n 144 birdmultiphaseEulerFoam -parallel diff --git a/tutorial_cases/bubble_column_20L/system/controlDict b/tutorial_cases/bubble_column_20L/system/controlDict index 61d4e04a..ef313841 100644 --- a/tutorial_cases/bubble_column_20L/system/controlDict +++ b/tutorial_cases/bubble_column_20L/system/controlDict @@ -14,7 +14,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime;//startTime; diff --git a/tutorial_cases/loop_reactor_mixing/script b/tutorial_cases/loop_reactor_mixing/script index 090e5c05..efe675ff 100755 --- a/tutorial_cases/loop_reactor_mixing/script +++ b/tutorial_cases/loop_reactor_mixing/script @@ -10,5 +10,5 @@ bash presteps.sh source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc decomposePar -fileHandler collated -srun -n 16 multiphaseEulerFoam -parallel -fileHandler collated +srun -n 16 birdmultiphaseEulerFoam -parallel -fileHandler collated reconstructPar -newTimes diff --git a/tutorial_cases/loop_reactor_mixing/system/controlDict b/tutorial_cases/loop_reactor_mixing/system/controlDict index d8c42afd..204a8cbe 100644 --- a/tutorial_cases/loop_reactor_mixing/system/controlDict +++ b/tutorial_cases/loop_reactor_mixing/system/controlDict @@ -14,7 +14,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime;//startTime; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh index 6b7eb516..99da4760 100644 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh @@ -1,4 +1,4 @@ -multiphaseEulerFoam +birdmultiphaseEulerFoam diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script index 090e5c05..efe675ff 100755 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script @@ -10,5 +10,5 @@ bash presteps.sh source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc decomposePar -fileHandler collated -srun -n 16 multiphaseEulerFoam -parallel -fileHandler collated +srun -n 16 birdmultiphaseEulerFoam -parallel -fileHandler collated reconstructPar -newTimes diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict index 7f457c4b..6f803fd2 100644 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict @@ -14,7 +14,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime;//startTime; diff --git a/tutorial_cases/loop_reactor_reacting/system/controlDict b/tutorial_cases/loop_reactor_reacting/system/controlDict index 5e782597..99054d1c 100644 --- a/tutorial_cases/loop_reactor_reacting/system/controlDict +++ b/tutorial_cases/loop_reactor_reacting/system/controlDict @@ -14,7 +14,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime;//startTime; diff --git a/tutorial_cases/side_sparger/system/controlDict b/tutorial_cases/side_sparger/system/controlDict index 812bdde3..9cf2ac1b 100644 --- a/tutorial_cases/side_sparger/system/controlDict +++ b/tutorial_cases/side_sparger/system/controlDict @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/tutorial_cases/side_sparger/system/controlDict.first b/tutorial_cases/side_sparger/system/controlDict.first index 48186072..ff0bf772 100644 --- a/tutorial_cases/side_sparger/system/controlDict.first +++ b/tutorial_cases/side_sparger/system/controlDict.first @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; diff --git a/tutorial_cases/side_sparger/system/controlDict.second b/tutorial_cases/side_sparger/system/controlDict.second index eee3a5d6..9e0d2eeb 100644 --- a/tutorial_cases/side_sparger/system/controlDict.second +++ b/tutorial_cases/side_sparger/system/controlDict.second @@ -13,7 +13,7 @@ FoamFile } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -application multiphaseEulerFoam; +application birdmultiphaseEulerFoam; startFrom latestTime; From 45fa2cd36971a1e05c92ae1c728a576af1b2f394 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 22 May 2025 11:51:53 -0600 Subject: [PATCH 04/86] add a troubleshoot page to help users --- docs/source/index.rst | 1 + docs/source/troubleshoot.rst | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 docs/source/troubleshoot.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 7f315c91..0537e923 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -30,4 +30,5 @@ We provide a solver ``birdmultiphaseEulerFoam`` that contains custom models adde tutorials contribute references + troubleshoot acknowledgments diff --git a/docs/source/troubleshoot.rst b/docs/source/troubleshoot.rst new file mode 100644 index 00000000..be438d96 --- /dev/null +++ b/docs/source/troubleshoot.rst @@ -0,0 +1,17 @@ +Troubleshoot +===== + + +Unrecognized drag and mass transfer model +------------ + +.. code-block:: console + + [6] --> FOAM FATAL ERROR: + [6] Unknown dragModelType type Grace + + +This may mean that you are using ``multiphaseEulerFoam`` instead of ``birdmultiphaseEulerFoam`` + + + From 7c14a979534d56dfcb231070cc7ea7cace578d39 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 22 May 2025 12:01:26 -0600 Subject: [PATCH 05/86] discuss the bdofoam problem --- docs/source/troubleshoot.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/troubleshoot.rst b/docs/source/troubleshoot.rst index be438d96..760c0ca6 100644 --- a/docs/source/troubleshoot.rst +++ b/docs/source/troubleshoot.rst @@ -15,3 +15,11 @@ This may mean that you are using ``multiphaseEulerFoam`` instead of ``birdmultip +BDOFoam does not compile +------------ + +``birdmultiphaseEulerFoam`` requires OpenFOAM 9 but ``bdoFoam`` requires OpenFOAM 6. +Compiling ``bdoFoam`` also requires a little more work that ``birdmultiphaseEulerFoam``. +Detailed step are discussed `here `_ + +In the future, we will make sure that ``bdoFoam`` works with OpenFOAM 9. From 63df543751f2f0c2e6ec1fdd6322c318e9409d2b Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 22 May 2025 16:58:16 -0600 Subject: [PATCH 06/86] update v since pypi package will change --- bird/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/version.py b/bird/version.py index 75d8e719..6f41219c 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.19" +__version__ = "0.0.20" From 8ca45853c44a1bccf0779116b3cbe3baa969a4c7 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 08:34:04 -0600 Subject: [PATCH 07/86] add test for case generation --- tests/preprocess/test_case_gen.py | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/preprocess/test_case_gen.py diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py new file mode 100644 index 00000000..95319925 --- /dev/null +++ b/tests/preprocess/test_case_gen.py @@ -0,0 +1,88 @@ + +import os +import pickle +import shutil + +import numpy as np + +from bird.preprocess.json_gen.design_io import * +from bird.preprocess.json_gen.generate_designs import * + + +def test_continuous_loop(): + + generate_single_scaledup_reactor_sparger_cases( + sparger_locs=[0.3, 0.5, 1.4], + sim_id=0, + vvm=0.4, + study_folder=".", + ) + +def test_discrete_loop(): + + def optimization_setup(): + # spots on the branches where we can place sparger or mixers + branchcom_spots = {} + branchcom_spots[0] = np.linspace(0.2, 0.8, 4) + branchcom_spots[1] = np.linspace(0.2, 0.8, 3) + branchcom_spots[2] = np.linspace(0.2, 0.8, 4) + # branches where the sparger and mixers are placed + branches_com = [0, 1, 2] + return branchcom_spots, branches_com + + + def random_sample(branches_com, branchcom_spots, config_dict={}): + config = {} + # choices = ["mix", "sparger", "none"] + choices_com = [0, 1, 2] + for branch in branches_com: + config[branch] = np.random.choice( + choices_com, size=len(branchcom_spots[branch]) + ) + + existing = False + new_config_key = 0 + for old_key_conf in config_dict: + if compare_config(config_dict[old_key_conf], config): + existing = True + print("FOUND SAME CONFIG") + return config_dict + new_config_key = old_key_conf + 1 + + if check_config(config): + config_dict[new_config_key] = config + + return config_dict + + + branchcom_spots, branches_com = optimization_setup() + n_sim = 20 + config_dict = {} + for i in range(n_sim): + config_dict = random_sample( + branches_com, branchcom_spots, config_dict=config_dict + ) + + vvm_l = [0.1, 0.4] + pow_l = [3000,6000] + + for vvm_v in vvm_l: + vvm_str = str(vvm_v).replace(".", "_") + for pow_v in pow_l: + study_folder = f"study_scaleup_{vvm_str}vvm_{pow_v}W" + generate_scaledup_reactor_cases( + config_dict, + branchcom_spots, + vvm=vvm_v, + power=pow_v, + constantD=True, + study_folder=study_folder, + ) + write_script_start(f"{study_folder}/many_scripts_start", n_sim) + write_script_post(f"{study_folder}/many_scripts_post", n_sim) + write_prep(f"{study_folder}/prep.sh", n_sim) + save_config_dict(f"{study_folder}/configs.pkl", config_dict) + save_config_dict( + f"{study_folder}/branchcom_spots.pkl", branchcom_spots + ) + From 980ec071948bc1e8fa5980ab9ce6b482da805a0f Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 08:34:46 -0600 Subject: [PATCH 08/86] fix format --- tests/preprocess/test_case_gen.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 95319925..056a271b 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -1,4 +1,3 @@ - import os import pickle import shutil @@ -18,8 +17,9 @@ def test_continuous_loop(): study_folder=".", ) + def test_discrete_loop(): - + def optimization_setup(): # spots on the branches where we can place sparger or mixers branchcom_spots = {} @@ -29,8 +29,7 @@ def optimization_setup(): # branches where the sparger and mixers are placed branches_com = [0, 1, 2] return branchcom_spots, branches_com - - + def random_sample(branches_com, branchcom_spots, config_dict={}): config = {} # choices = ["mix", "sparger", "none"] @@ -39,7 +38,7 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): config[branch] = np.random.choice( choices_com, size=len(branchcom_spots[branch]) ) - + existing = False new_config_key = 0 for old_key_conf in config_dict: @@ -48,13 +47,12 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): print("FOUND SAME CONFIG") return config_dict new_config_key = old_key_conf + 1 - + if check_config(config): config_dict[new_config_key] = config - + return config_dict - branchcom_spots, branches_com = optimization_setup() n_sim = 20 config_dict = {} @@ -64,7 +62,7 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): ) vvm_l = [0.1, 0.4] - pow_l = [3000,6000] + pow_l = [3000, 6000] for vvm_v in vvm_l: vvm_str = str(vvm_v).replace(".", "_") @@ -84,5 +82,4 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): save_config_dict(f"{study_folder}/configs.pkl", config_dict) save_config_dict( f"{study_folder}/branchcom_spots.pkl", branchcom_spots - ) - + ) From d947ba0f18fa8bb17f392bc5446acd5d6fb9bd6d Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 10:50:20 -0600 Subject: [PATCH 09/86] make sure test passes --- bird/__init__.py | 1 + .../.vim/.netrwhist | 3 + .../0.orig/CO2.gas | 47 ++ .../0.orig/CO2.liquid | 42 + .../0.orig/H2.gas | 47 ++ .../0.orig/H2.liquid | 42 + .../0.orig/N2.gas | 47 ++ .../0.orig/T.gas | 44 ++ .../0.orig/T.liquid | 43 + .../0.orig/U.gas | 47 ++ .../0.orig/U.liquid | 46 ++ .../0.orig/Ydefault.gas | 42 + .../0.orig/Ydefault.liquid | 42 + .../0.orig/alpha.gas | 43 + .../0.orig/alpha.liquid | 40 + .../0.orig/alphat.gas | 46 ++ .../0.orig/alphat.liquid | 44 ++ .../0.orig/epsilon.gas | 48 ++ .../0.orig/epsilon.liquid | 43 + .../0.orig/f.gas | 41 + .../0.orig/k.gas | 43 + .../0.orig/k.liquid | 44 ++ .../0.orig/nut.gas | 48 ++ .../0.orig/nut.liquid | 43 + .../0.orig/p | 39 + .../0.orig/p_rgh | 43 + .../Allclean | 18 + .../computeQOI.sh | 13 + .../constant/dynamicMix_util.H | 37 + .../constant/fvModels | 128 +++ .../constant/g | 21 + .../constant/globalVars | 83 ++ .../constant/globalVars_temp | 83 ++ .../constant/momentumTransport.gas | 26 + .../constant/momentumTransport.liquid | 27 + .../constant/phaseProperties | 295 +++++++ .../constant/phaseProperties_constantd | 261 ++++++ .../constant/phaseProperties_pbe | 295 +++++++ .../constant/thermophysicalProperties.gas | 142 ++++ .../constant/thermophysicalProperties.liquid | 108 +++ .../get_qoi.py | 183 +++++ .../presteps.sh | 70 ++ .../read_history.py | 239 ++++++ .../run.sh | 5 + .../script | 14 + .../script_post | 10 + .../system/blockMeshDict | 746 ++++++++++++++++++ .../system/controlDict | 67 ++ .../system/decomposeParDict | 30 + .../system/fvConstraints | 56 ++ .../system/fvSchemes | 70 ++ .../system/fvSolution | 120 +++ .../system/inlets_outlets.json | 28 + .../system/mesh.json | 26 + .../system/mixers.json | 29 + .../system/setFieldsDict | 37 + .../writeGlobalVars.py | 47 ++ bird/preprocess/json_gen/generate_designs.py | 57 +- tests/preprocess/test_case_gen.py | 9 + 59 files changed, 4422 insertions(+), 16 deletions(-) create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh create mode 100755 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh create mode 100755 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script create mode 100755 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict create mode 100755 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict create mode 100644 bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py diff --git a/bird/__init__.py b/bird/__init__.py index 38b9be87..986a535f 100644 --- a/bird/__init__.py +++ b/bird/__init__.py @@ -35,4 +35,5 @@ ) BIRD_EARLY_PRED_DATA_DIR = os.path.join(BIRD_POST_DIR, "data_early") BIRD_KLA_DATA_DIR = os.path.join(BIRD_POST_DIR, "data_kla") +BIRD_CASEGEN_DATA_DIR = os.path.join(BIRD_PRE_DIR, "data") BIRD_INV_DIR = os.path.join(BIRD_DIR, "inverse_modeling") diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist new file mode 100644 index 00000000..b9691c3b --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist @@ -0,0 +1,3 @@ +let g:netrw_dirhistmax =10 +let g:netrw_dirhistcnt =1 +let g:netrw_dirhist_1='/home/openfoam/postProcessing/patchIntegrate(patch=inlet,field=alpha.gas)/0' diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas new file mode 100644 index 00000000..e4165b1a --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object CO2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 0; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_CO2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_CO2; + //value $f_CO2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid new file mode 100644 index 00000000..4b8ea6a0 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object CO2.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type zeroGradient; + //type fixedValue; + //value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas new file mode 100644 index 00000000..9f66b2d2 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object H2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 0; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_H2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_H2; + //value $f_H2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid new file mode 100644 index 00000000..65ae8d34 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object H2.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type zeroGradient; + //type fixedValue; + //value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas new file mode 100644 index 00000000..c1d7225f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object N2.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 1; + + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $f_N2; + } + + outlet + { + //type inletOutlet; + //phi phi.gas; + //inletValue $f_N2; + //value $f_N2; + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas new file mode 100644 index 00000000..bf0199a0 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 300; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.gas; + inletValue $internalField; + value $internalField; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid new file mode 100644 index 00000000..7101ea31 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 300; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue $internalField; + value $internalField; + } + inlet + { + type fixedValue; + value $internalField; + } + defaultFaces + { + type zeroGradient; + } + +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas new file mode 100644 index 00000000..e696566f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volVectorField; + object U.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +internalField uniform (0 0.0 0); + +#include "${FOAM_CASE}/constant/globalVars" + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + //type flowRateInletVelocity; + //massFlowRate $mflowRateGas; + //rho thermo:rho.gas; + //value $internalField; + type fixedValue; + value uniform (0 $uGasPhase 0); + } + outlet + { + type pressureInletOutletVelocity; + phi phi.gas; + value $internalField; + } + defaultFaces + { + type slip; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid new file mode 100644 index 00000000..1879e020 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volVectorField; + object U.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform (0 0 0); + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + //type flowRateInletVelocity; + //massFlowRate $mflowRateLiq; + //rho thermo:rho.liquid; + //value $internalField; + type fixedValue; + value uniform (0 0 0); + } + outlet + { + type noSlip; + } + defaultFaces + { + type noSlip; + } + +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas new file mode 100644 index 00000000..fba2945d --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object Ydefault.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 0.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid new file mode 100644 index 00000000..a5108564 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid @@ -0,0 +1,42 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object Ydefault.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 1.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 1.0; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas new file mode 100644 index 00000000..1e303fbe --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + location "0"; + object alpha.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $alphaGas; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $alphaGas; + } + outlet + { + type inletOutlet; + phi phi.gas; + inletValue uniform 1; + value uniform 1; + } + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid new file mode 100644 index 00000000..5c92070b --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alpha.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform 1; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $alphaLiq; + } + outlet + { + type fixedValue; + value uniform 0; + } + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas new file mode 100644 index 00000000..b867958f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type calculated; + value $internalField; + //type compressible::alphatWallFunction; + //Prt 0.85; + //value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid new file mode 100644 index 00000000..2569c3ee --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type compressible::alphatWallFunction; + Prt 0.85; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas new file mode 100644 index 00000000..707a1cda --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $eps_inlet_gas; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $eps_inlet_gas; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + //type epsilonWallFunction; + //value $internalField; + } + + // defaultFaces + // { + // type empty; + // } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid new file mode 100644 index 00000000..0a4236fd --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $eps_inlet_liq; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform $eps_inlet_liq; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type epsilonWallFunction; + value $internalField; + } + +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas new file mode 100644 index 00000000..76ee77a9 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas @@ -0,0 +1,41 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object f.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 1.0; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform 1.0; //$internalField; // + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas new file mode 100644 index 00000000..4a3d44ca --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $k_inlet_gas; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $k_inlet_gas; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid new file mode 100644 index 00000000..cde8f6c1 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +#include "${FOAM_CASE}/constant/globalVars" + +internalField uniform $k_inlet_liq; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type fixedValue; + value uniform $k_inlet_liq; + } + + outlet + { + type zeroGradient; + } + + defaultFaces + { + type kqRWallFunction; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas new file mode 100644 index 00000000..ba16dd4c --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-8; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + //type nutkWallFunction; + //value $internalField; + type calculated; + value $internalField; + } + + // defaultFaces + // { + // type empty; + // } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid new file mode 100644 index 00000000..1442e07f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-4; + +boundaryField +{ + #includeEtc "caseDicts/setConstraintTypes" + + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + defaultFaces + { + type nutkWallFunction; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p new file mode 100644 index 00000000..b3a295fb --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p @@ -0,0 +1,39 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 101325; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + outlet + { + type calculated; + value $internalField; + } + defaultFaces + { + type calculated; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh new file mode 100644 index 00000000..88ee7d80 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh @@ -0,0 +1,43 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p_rgh; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 101325; + +boundaryField +{ + inlet + { + type fixedFluxPressure; + value $internalField; + } + outlet + { + type prghTotalPressure; + p0 $internalField; + U U.gas; + phi phi.gas; + rho thermo:rho.gas; + value $internalField; + } + defaultFaces + { + type fixedFluxPressure; + value $internalField; + } +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean new file mode 100755 index 00000000..f55e0ec9 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean @@ -0,0 +1,18 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +# Source tutorial clean functions +. $WM_PROJECT_DIR/bin/tools/CleanFunctions + +# Remove surface, features and solution +#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 +#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 +#rm -rf constant/polyMesh > /dev/null 2>&1 +#rm -rf processor* > /dev/null 2>&1 +rm -rf 0 +cleanCase + +#rm *.obj +#rm *.stl + +#------------------------------------------------------------------------------ diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh new file mode 100644 index 00000000..3756ed7f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh @@ -0,0 +1,13 @@ +if [ ! -f qoi.txt ]; then + # Reconstruct if needed + source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc + reconstructPar -newTimes + module load anaconda3/2023 + conda activate /projects/gas2fuels/conda_env/bird + python read_history.py -cr .. -cn local -df data + python get_qoi.py + conda deactivate +else + echo "WARNING: QOI already computed" +fi + diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H new file mode 100644 index 00000000..8fa7daf7 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H @@ -0,0 +1,37 @@ +#include + +double gradfunMix(double V1, double V2){ + return 3.0*V2*V2 + 2.0*V1*V2 - V1*V1; +} + +double funMix(double V1, double V2, double P, double rhoL, double A){ + return V2*V2*V2 + V1*V2*V2 - V2*V1*V1 - V1*V1*V1 - 4.0*P/(rhoL * A); +} + +double findV2(double P, double rhoL, double A, double V1) { + int newton_iter = 100; + double V2 = 2*V1; + double V2_old; + double V2_new; + if (std::abs(V1) < 1e-12) { + V2=std::pow((4.0*P/rhoL/A),0.333333); + V2_new = V2; + V2_old = V2; + } else { + for (int i=0; i("thermo:rho.liquid"); + const volScalarField& alphaL = + mesh().lookupObject("alpha.liquid"); + const volVectorField& UL = + mesh().lookupObject("U.liquid"); + double pi=3.141592654; + double source_pt_x=13.807637692813548; + double source_pt_y=1.3807637692813548; + double source_pt_z=12.426873923532193; + double disk_rad=0.9665346384969483; + double disk_area=pi*disk_rad*disk_rad; + double power=7626.034346240216; + double smear_factor=3.0; + const scalar startTime = 1; + if (time.value() > startTime) + { + // Get V1 + double source_sign_factor = 1.0; + double V1 = 0; + double V2 = 0; + double rhoV; + double dist_tol = disk_rad*5; + + double dist_n; + double upV = 0; + double uprhoV = 0; + double upVvol = 0; + double downV = 0; + double downrhoV = 0; + double downVvol = 0; + double dist2; + forAll(C,i) + { + dist2 = (C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); + dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); + dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); + + dist_n = (C[i].x()-source_pt_x); + + if (dist2 < dist_tol*dist_tol && dist_n < -dist_tol/2) { + upVvol += V[i] * alphaL[i]; + upV += V[i] * alphaL[i] * UL[i][0]; + uprhoV += V[i] * alphaL[i] * rhoL[i]; + } + if (dist2 < dist_tol*dist_tol && dist_n > dist_tol/2) { + downVvol += V[i] * alphaL[i]; + downV += V[i] * alphaL[i] * UL[i][0]; + downrhoV += V[i] * alphaL[i] * rhoL[i]; + } + } + + reduce(uprhoV, sumOp()); + reduce(downrhoV, sumOp()); + reduce(upV, sumOp()); + reduce(downV, sumOp()); + reduce(downVvol, sumOp()); + reduce(upVvol, sumOp()); + + downV /= downVvol; + upV /= upVvol; + downrhoV /= downVvol; + uprhoV /= upVvol; + + if (upV <= 0 && downV <= 0) { + source_sign_factor = -1.0; + V1 = std::abs(upV); + rhoV = uprhoV; + } else if (upV >= 0 && downV >= 0) { + source_sign_factor = 1.0; + V1 = std::abs(downV); + rhoV = downrhoV; + } else { + V1 = 0.0; + source_sign_factor = -1.0; + rhoV = uprhoV; + Foam::Info << "[BIRD:DYNMIX WARN] " << "upV = " << upV << " downV = " << downV << " for source at " << source_pt_x << ", " << source_pt_y << ", " << source_pt_z << endl; + } + Foam::Info << "[BIRD:DYNMIX INFO] V1 = " << V1 << endl; + + // Get V2 + V2 = findV2(power, rhoV, disk_area, V1); + + forAll(C,i) + { + double Thrust=0.5*rhoL[i]*(V2*V2 - V1*V1)*disk_area; + double dist2=(C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); + dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); + dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); + double epsilon=pow(V[i],0.33333)*smear_factor; + double sourceterm=alphaL[i]*(Thrust/pow(pi,1.5)/pow(epsilon,3.0))* + exp(-dist2/(epsilon*epsilon)); + Usource[i][0] -= source_sign_factor*sourceterm*V[i]; + } + } + #}; +}; diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g new file mode 100644 index 00000000..770a5619 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g @@ -0,0 +1,21 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class uniformDimensionedVectorField; + location "constant"; + object g; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -2 0 0 0 0]; +value (0 -9.81 0); + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars new file mode 100644 index 00000000..1a0ce724 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars @@ -0,0 +1,83 @@ +T0 300; //initial T(K) which stays constant +VVM 1.6; +//****water Liquid properties************** +CpMixLiq 4181; +muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) +kThermLiq 0.62; // W/m-K +rho0MixLiq 1000; // kg/m^3 +sigmaLiq 0.07; //surface tension N/m +//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) +WC_psi 2.6; +WC_M 18; // kg/kmol +WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) +WC_V_H2 14.3e-3; +WC_V_CO2 34e-3; +WC_V_CO 30.7e-3; +WC_V_N2 31.2e-3; +WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) +//****** diffusion coeff *********** +D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; +D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; +D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; +D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; +D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; +//****** Henry coeff *************** +H_O2_298 0.032; +DH_O2 1700; +H_CO2_298 0.83; +DH_CO2 2400; +H_CO_298 0.023; +DH_CO 1300; +H_H2_298 0.019; +DH_H2 500; +H_CH4_298 0.032; +DH_CH4 1900; +H_N2_298 0.015; +DH_N2 1300; +He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; +He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; +He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; +He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; +He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; +//*******inlet gas frac************* +f_H2 0.1; +f_CO2 0.9; +f_N2 0.0; +//*******inlet gas frac************* +inletA 11.8966; +liqVol 606.514; +alphaGas 1; +alphaLiq 0; +uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; +//********************************* +LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; +LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; +LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 +LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; +LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; +LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; +PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; +//********************************* +kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; + +kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; + +kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; + +kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; + +kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; +//********************************* +l_scale 0.5; +intensity 0.05; +k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; +eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; +omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; +omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp new file mode 100644 index 00000000..fcb076a7 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp @@ -0,0 +1,83 @@ +T0 300; //initial T(K) which stays constant +VVM 0.2; +//****water Liquid properties************** +CpMixLiq 4181; +muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) +kThermLiq 0.62; // W/m-K +rho0MixLiq 1000; // kg/m^3 +sigmaLiq 0.07; //surface tension N/m +//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) +WC_psi 2.6; +WC_M 18; // kg/kmol +WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) +WC_V_H2 14.3e-3; +WC_V_CO2 34e-3; +WC_V_CO 30.7e-3; +WC_V_N2 31.2e-3; +WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) +//****** diffusion coeff *********** +D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; +D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; +D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; +D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; +D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; +//****** Henry coeff *************** +H_O2_298 0.032; +DH_O2 1700; +H_CO2_298 0.83; +DH_CO2 2400; +H_CO_298 0.023; +DH_CO 1300; +H_H2_298 0.019; +DH_H2 500; +H_CH4_298 0.032; +DH_CH4 1900; +H_N2_298 0.015; +DH_N2 1300; +He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; +He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; +He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; +He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; +He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; +//*******inlet gas frac************* +f_H2 0.1; +f_CO2 0.9; +f_N2 0.0; +//*******inlet gas frac************* +inletA ; +liqVol ; +alphaGas 1; +alphaLiq 0; +uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; +//********************************* +LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; +LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; +LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 +LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; +LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; +LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; +PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; +//********************************* +kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; + +kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; + +kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; + +kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; + +kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; +PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; +//********************************* +l_scale 0.5; +intensity 0.05; +k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; +eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; +eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; +omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; +omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas new file mode 100644 index 00000000..ca916714 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas @@ -0,0 +1,26 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +//simulationType laminar; +simulationType RAS; +RAS +{ + model mixtureKEpsilon; + turbulence on; + printCoeff on; +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid new file mode 100644 index 00000000..2063de0d --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid @@ -0,0 +1,27 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +//simulationType laminar; +simulationType RAS; + +RAS +{ + model mixtureKEpsilon; + turbulence on; + printCoeffs on; +} + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties new file mode 100644 index 00000000..a3c90f5a --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties @@ -0,0 +1,295 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; + +phases (gas liquid); + +populationBalances (bubbles); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel velocityGroup; + + velocityGroupCoeffs + { + populationBalance bubbles; + + shapeModel spherical; + + sizeGroups + ( + f1 {dSph 1.4e-3; value 0.0;} + f2 {dSph 1.8e-3; value 0.0;} + f3 {dSph 2.2e-3; value 0.0;} + f4 {dSph 2.6e-3; value 0.0;} + f5 {dSph 3e-3; value 1.0;} + f6 {dSph 3.4e-3; value 0.0;} + f7 {dSph 3.8e-3; value 0.0;} + f8 {dSph 4.2e-3; value 0.0;} + f9 {dSph 4.6e-3; value 0.0;} + f10 {dSph 5.0e-3; value 0.0;} + ); + } + + residualAlpha 1e-6; + + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + ( + LehrMilliesMewes{ + efficiency 4.695; + uCrit 0.08; + alphaMax 0.6; + } + ); + + binaryBreakupModels + (); + + breakupModels + ( + Laakkonen { + efficiency 13.83; + daughterSizeDistributionModel Laakkonen; + } + + ); + + driftModels + ( + densityChange{} + ); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd new file mode 100644 index 00000000..e029df99 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd @@ -0,0 +1,261 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangeMultiphaseSystem; + +phases (gas liquid); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 3e-3; + } + residualAlpha 1e-6; + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + (); + + binaryBreakupModels + (); + + breakupModels + (); + + driftModels + (); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe new file mode 100644 index 00000000..a3c90f5a --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe @@ -0,0 +1,295 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; + +phases (gas liquid); + +populationBalances (bubbles); + +gas +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel velocityGroup; + + velocityGroupCoeffs + { + populationBalance bubbles; + + shapeModel spherical; + + sizeGroups + ( + f1 {dSph 1.4e-3; value 0.0;} + f2 {dSph 1.8e-3; value 0.0;} + f3 {dSph 2.2e-3; value 0.0;} + f4 {dSph 2.6e-3; value 0.0;} + f5 {dSph 3e-3; value 1.0;} + f6 {dSph 3.4e-3; value 0.0;} + f7 {dSph 3.8e-3; value 0.0;} + f8 {dSph 4.2e-3; value 0.0;} + f9 {dSph 4.6e-3; value 0.0;} + f10 {dSph 5.0e-3; value 0.0;} + ); + } + + residualAlpha 1e-6; + + Sc 0.7; +} + +liquid +{ + type multiComponentPhaseModel;//pureIsothermalPhaseModel; + + diameterModel constant; + + constantCoeffs + { + d 1e-4; + } + Sc #codeStream + { + code + #{ + os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); + #}; + }; + + residualAlpha 1e-6; +} + +populationBalanceCoeffs +{ + bubbles + { + continuousPhase liquid; + + coalescenceModels + ( + LehrMilliesMewes{ + efficiency 4.695; + uCrit 0.08; + alphaMax 0.6; + } + ); + + binaryBreakupModels + (); + + breakupModels + ( + Laakkonen { + efficiency 13.83; + daughterSizeDistributionModel Laakkonen; + } + + ); + + driftModels + ( + densityChange{} + ); + + nucleationModels + (); + } +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + heatTransfer + { + type linear; + minFullyContinuousAlpha.gas 1; + minPartlyContinuousAlpha.gas 0; + minFullyContinuousAlpha.liquid 1; + minPartlyContinuousAlpha.liquid 0; + } + massTransfer + { + $heatTransfer; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma $sigmaLiq; + } +); + +interfaceCompression +(); + +aspectRatio +( + (gas in liquid) + { + type Wellek; + } +); + + +drag +( + (gas in liquid) + { + type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type IshiiZuber; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } +); + +// heatTransfer +// (); + +heatTransfer.gas +( + (gas in liquid) + { + type spherical; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +heatTransfer.liquid +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type spherical; + residualAlpha 1e-4; + } +); + +interfaceComposition.gas +(); + +interfaceComposition.liquid +( + (liquid and gas) + { + type Henry; + species ( CO2 H2 ); + k ( $He_CO2 $He_H2 ); + Le $LeLiqMix; + } +); + +diffusiveMassTransfer.gas +(); + +diffusiveMassTransfer.liquid +( + (gas in liquid) + { + type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign + //type Frossling; + Le $LeLiqMix; + } + + (liquid in gas) + { + type spherical; + Le 1.0; //not used for spherical + } +); + +phaseTransfer +(); + +lift +( + (gas in liquid) + { + type wallDamped; + + wallDamping + { + type cosine; + Cd 3.0; + } + + lift + { + type Tomiyama; + + swarmCorrection + { + type none; + } + } + } + +); + +wallLubrication +( + (gas in liquid) + { + type Antal; + Cw1 -0.01; + Cw2 0.05; + } +); + +turbulentDispersion +( + (gas in liquid) + { + type Burns; + sigma 0.9; + } +); + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas new file mode 100644 index 00000000..11b1c4b9 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas @@ -0,0 +1,142 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +thermoType +{ + type heRhoThermo; + mixture multiComponentMixture; + transport sutherland; + thermo janaf; + equationOfState perfectGas; + specie specie; + energy sensibleInternalEnergy; + //energy sensibleEnthalpy; +} + + +species +( + H2 + CO2 + N2 +); + +defaultSpecie N2; + +CO2 +{ + specie + { + molWeight 44.00995; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.85746029 0.00441437026 -2.21481404e-06 5.23490188e-10 -4.72084164e-14 -48759.166 2.27163806 ); + lowCpCoeffs ( 2.35677352 0.00898459677 -7.12356269e-06 2.45919022e-09 -1.43699548e-13 -48371.9697 9.90105222 ); + } + transport + { + As 1.572e-06; + Ts 240; + } + elements + { + C 1; + O 2; + } +} + +water +{ + specie + { + molWeight 18.01534; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.03399249 0.00217691804 -1.64072518e-07 -9.7041987e-11 1.68200992e-14 -30004.2971 4.9667701 ); + lowCpCoeffs ( 4.19864056 -0.0020364341 6.52040211e-06 -5.48797062e-09 1.77197817e-12 -30293.7267 -0.849032208 ); + } + transport + { + As 1.512e-06; + Ts 120; + } + elements + { + H 2; + O 1; + } +} + +N2 +{ + specie + { + molWeight 28.0134; + } + thermodynamics + { + Tlow 250; + Thigh 5000; + Tcommon 1000; + highCpCoeffs ( 2.92664 0.0014879768 -5.68476e-07 1.0097038e-10 -6.753351e-15 -922.7977 5.980528 ); + lowCpCoeffs ( 3.298677 0.0014082404 -3.963222e-06 5.641515e-09 -2.444854e-12 -1020.8999 3.950372 ); + } + transport + { + As 1.512e-06; + Ts 120; + } + elements + { + N 2; + } +} + +H2 +{ + specie + { + molWeight 2.01594; + } + thermodynamics + { + Tlow 200; + Thigh 3500; + Tcommon 1000; + highCpCoeffs ( 3.3372792 -4.94024731e-05 4.99456778e-07 -1.79566394e-10 2.00255376e-14 -950.158922 -3.20502331 ); + lowCpCoeffs ( 2.34433112 0.00798052075 -1.9478151e-05 2.01572094e-08 -7.37611761e-12 -917.935173 0.683010238 ); + } + transport + { + As 6.362e-07; + Ts 72; + } + elements + { + H 2; + } +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid new file mode 100644 index 00000000..d324ec51 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid @@ -0,0 +1,108 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "$FOAM_CASE/constant/globalVars" + +thermoType +{ + type heRhoThermo; + mixture multiComponentMixture; + transport const; + thermo hConst; + equationOfState rhoConst;//rPolynomial; + specie specie; + energy sensibleInternalEnergy; + //energy sensibleEnthalpy; +} + +species +( + CO2 + water + H2 +); + +inertSpecie water; + +water +{ + specie + { + molWeight 18.0153; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07; + } + transport + { + mu $muMixLiq; + Pr $PrMixLiq; + } +} + +CO2 +{ + specie + { + molWeight 44.00995; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07; + } + transport + { + mu $muMixLiq; + Pr $PrCO2; + } +} + +H2 +{ + specie + { + molWeight 2.01594; + } + equationOfState + { + rho $rho0MixLiq; + } + thermodynamics + { + Cp $CpMixLiq; + Hf -1.5879e+07;//-9402451; + } + transport + { + mu $muMixLiq; + Pr $PrH2; + } +} + + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py new file mode 100644 index 00000000..9562cc65 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py @@ -0,0 +1,183 @@ +import json +import os +import pickle as pkl + +import matplotlib as mpl +import numpy as np +from prettyPlot.plotting import * +from scipy.optimize import curve_fit + + +def get_sim_folds(path): + folds = os.listdir(path) + sim_folds = [] + for fold in folds: + if fold.startswith("loop"): + sim_folds.append(fold) + return sim_folds + + +def func(t, cstar, kla): + t = t + t0 = 0 + c0 = 0 + return (cstar - c0) * (1 - np.exp(-kla * (t - t0))) + c0 + + +def get_vl(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("liqVol"): + vol = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read liqVol = {vol}m3") + return vol + + +def get_vvm(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("VVM"): + vvm = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read VVM = {vvm} [-]") + return vvm + + +def get_As(verb=False): + filename = os.path.join("constant", "globalVars") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if line.startswith("inletA"): + As = float(line.split()[-1][:-1]) + break + if verb: + print(f"Read As = {As}m2") + return As + + +def get_pmix(verb=False): + with open("system/mixers.json", "r+") as f: + data = json.load(f) + mixer_list = data["mixers"] + pmix = 0 + for mix in mixer_list: + pmix += mix["power"] / 1000 + if verb: + print(f"Read Mixing power = {pmix}kW") + return pmix + + +def get_lh(verb=False): + filename = os.path.join("system", "setFieldsDict") + with open(filename, "r+") as f: + lines = f.readlines() + for line in lines: + if "box (-1.0 -1.0 -1.0)" in line: + height = float(line.split("(")[2].split()[1]) + break + if verb: + print(f"Read Height = {height}m") + return height + + +def get_pinj(vvm, Vl, As, lh): + rhog = 1.25 # kg /m3 + Vg = Vl * vvm / (60 * As * 1) # m/s + Ptank = 101325 # Pa + # Ptank = 0 # Pa + rhoL = 1000 # kg / m3 + Pl = 101325 + rhoL * 9.8 * lh # Pa + # W + P1 = rhog * As * Vg**3 + # W + P2 = (Pl - Ptank) * As * Vg + # kg /s + MF = rhog * Vg * As + # kwh / kg + e_m = (P1 + P2) / (3600 * 1000 * MF) + + # returns kW + return (P1 + P2) * 1e-3 + + +def get_qoi(kla_co2, cs_co2, kla_h2, cs_h2, verb=False): + vvm = get_vvm(verb) + As = get_As(verb) + V_l = get_vl(verb) + liqh = get_lh(verb) + P_inj = get_pinj(vvm, V_l, As, liqh) + P_mix = get_pmix(verb) + + qoi_co2 = kla_co2 * cs_co2 * V_l * 0.04401 / (P_mix / 3600 + P_inj / 3600) + qoi_h2 = kla_h2 * cs_h2 * V_l * 0.002016 / (P_mix / 3600 + P_inj / 3600) + return qoi_co2 * qoi_h2 + + +def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): + qoi = [] + for i in range(len(kla_co2)): + if i == 0: + verb = True + else: + verb = False + qoi.append(get_qoi(kla_co2[i], cs_co2[i], kla_h2[i], cs_h2[i], verb)) + qoi = np.array(qoi) + return np.mean(qoi), np.std(qoi) + + +os.makedirs("Figures", exist_ok=True) + +dataFolder = "data" +fold = "local" + +nuq = 100 +mean_cstar_co2 = np.random.uniform(12.6, 13.3, nuq) +mean_cstar_h2 = np.random.uniform(0.902, 0.96, nuq) + + +tmp_cs_h2 = [] +tmp_cs_co2 = [] +tmp_kla_h2 = [] +tmp_kla_co2 = [] +cs_co2 = mean_cstar_co2 +cs_h2 = mean_cstar_h2 + +a = np.load(os.path.join(dataFolder, fold, "conv.npz")) +endindex = -1 +if ( + "c_h2" in a + and "c_co2" in a + and len(a["time"][:endindex] > 0) + and (a["time"][:endindex][-1] > 95) +): + for i in range(nuq): + fitparamsH2, _ = curve_fit( + func, + np.array(a["time"][:endindex]), + np.array(a["c_h2"][:endindex]), + bounds=[(cs_h2[i] - 1e-6, 0), (cs_h2[i] + 1e-6, 1)], + ) + fitparamsCO2, _ = curve_fit( + func, + np.array(a["time"][:endindex]), + np.array(a["c_co2"][:endindex]), + bounds=[(cs_co2[i] - 1e-6, 0), (cs_co2[i] + 1e-6, 1)], + ) + tmp_kla_co2.append(fitparamsCO2[1]) + tmp_kla_h2.append(fitparamsH2[1]) + tmp_cs_h2.append(cs_h2[i]) + tmp_cs_co2.append(cs_co2[i]) + +qoi_m, qoi_s = get_qoi_uq(tmp_kla_co2, tmp_cs_co2, tmp_kla_h2, tmp_cs_h2) + + +with open("qoi.txt", "w+") as f: + f.write(f"{qoi_m},{qoi_s}\n") diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh new file mode 100644 index 00000000..77fa6d6f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh @@ -0,0 +1,70 @@ +# Clean case +module load conda +conda activate /projects/gas2fuels/conda_env/bird_wf +source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc +./Allclean + +BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` + +echo PRESTEP 1 +# Generate blockmeshDict +python ${BIRD_HOME}/../applications/write_block_rect_mesh.py -i system/mesh.json -o system + +# Generate boundary stl +python ${BIRD_HOME}/../applications/write_stl_patch.py -i system/inlets_outlets.json + +# Generate mixers +python ${BIRD_HOME}/../applications/write_dynMix_fvModels.py -fs -i system/mixers.json -o constant + +echo PRESTEP 2 +# Mesh gen +blockMesh -dict system/blockMeshDict + +# Inlet BC +surfaceToPatch -tol 1e-3 inlets.stl +export newmeshdir=$(foamListTimes -latestTime) +rm -rf constant/polyMesh/ +cp -r $newmeshdir/polyMesh ./constant +rm -rf $newmeshdir +cp constant/polyMesh/boundary /tmp +sed -i -e 's/inlets\.stl/inlet/g' /tmp/boundary +cat /tmp/boundary > constant/polyMesh/boundary + +# Outlet BC +surfaceToPatch -tol 1e-3 outlets.stl +export newmeshdir=$(foamListTimes -latestTime) +rm -rf constant/polyMesh/ +cp -r $newmeshdir/polyMesh ./constant +rm -rf $newmeshdir +cp constant/polyMesh/boundary /tmp +sed -i -e 's/outlets\.stl/outlet/g' /tmp/boundary +cat /tmp/boundary > constant/polyMesh/boundary + + +# Scale +transformPoints "scale=(2.7615275385627096 2.7615275385627096 2.7615275385627096)" + + +# setup IC +cp -r 0.orig 0 +setFields + +# Setup mass flow rate +# Get inlet area +postProcess -func 'patchIntegrate(patch="inlet", field="alpha.gas")' +postProcess -func writeCellVolumes +writeMeshObj + +echo PRESTEP 3 +python writeGlobalVars.py +cp constant/phaseProperties_constantd constant/phaseProperties + +conda deactivate + +if [ -f qoi.txt ]; then + rm qoi.txt +fi +if [ -f data/local/conv.npz ]; then + rm data/local/conv.npz +fi + diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py new file mode 100644 index 00000000..264711f8 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -0,0 +1,239 @@ +import argparse +import os +import sys + +import numpy as np +from prettyPlot.plotting import plt, pretty_labels + +from bird.utilities.ofio import * + + +def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + alpha_liq = field_dict["alpha_liq"] + volume = field_dict["volume"] + holdup = np.sum((1 - alpha_liq) * volume) / np.sum(volume) + return holdup, field_dict + + +def co2liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: + co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") + co2_liq = readOFScal(co2_liq_file, nCells) + # print("computing co2 liq") + field_dict["co2_liq"] = co2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + co2_liq = field_dict["co2_liq"] + met = np.sum( + alpha_liq[indliq] * co2_liq[indliq] * volume[indliq] + ) / np.sum(volume[indliq]) + return met, field_dict + + +def cliq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: + rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") + rho_liq = readOFScal(rho_liq_file, nCells) + field_dict["rho_liq"] = rho_liq + if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: + co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") + co2_liq = readOFScal(co2_liq_file, nCells) + # print("computing co2 liq") + field_dict["co2_liq"] = co2_liq + if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: + h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") + h2_liq = readOFScal(h2_liq_file, nCells) + # print("computing h2 liq") + field_dict["h2_liq"] = h2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + co2_liq = field_dict["co2_liq"] + h2_liq = field_dict["h2_liq"] + rho_liq = field_dict["rho_liq"] + + # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 + # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + + c_h2 = 1000 * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 + c_co2 = 1000 * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + + c_h2 = np.sum(c_h2 * volume[indliq]) / np.sum(volume[indliq]) + c_co2 = np.sum(c_co2 * volume[indliq]) / np.sum(volume[indliq]) + + return c_co2, c_h2, field_dict + + +def h2liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: + h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") + h2_liq = readOFScal(h2_liq_file, nCells) + # print("computing h2 liq") + field_dict["h2_liq"] = h2_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + if not ("indliq" in field_dict) or field_dict["indliq"] is None: + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.5) + # print("computing indliq") + field_dict["indliq"] = indliq + volume = field_dict["volume"] + indliq = field_dict["indliq"] + alpha_liq = field_dict["alpha_liq"] + h2_liq = field_dict["h2_liq"] + met = np.sum(alpha_liq[indliq] * h2_liq[indliq] * volume[indliq]) / np.sum( + volume[indliq] + ) + return met, field_dict + + +def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): + if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: + alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") + alpha_liq = readOFScal(alpha_liq_file, nCells) + # print("reading alpha_liq") + field_dict["alpha_liq"] = alpha_liq + if not ("volume" in field_dict) or field_dict["volume"] is None: + volume_file = os.path.join(caseFolder, "0", "V") + volume = readOFScal(volume_file, nCells) + # print("reading Volume") + field_dict["volume"] = volume + volume = field_dict["volume"] + alpha_liq = field_dict["alpha_liq"] + indliq = np.argwhere(alpha_liq > 0.0) + liqvol = np.sum(alpha_liq[indliq] * volume[indliq]) / np.sum( + volume[indliq] + ) + return liqvol, field_dict + + +parser = argparse.ArgumentParser(description="Convergence of GH") +parser.add_argument( + "-cn", + "--case_name", + type=str, + metavar="", + required=True, + help="Case name", +) +parser.add_argument( + "-df", + "--data_folder", + type=str, + metavar="", + required=False, + help="data folder name", + default="data", +) + +args, unknown = parser.parse_known_args() + + +case_root = "." # "../" +case_name = args.case_name # "12_hole_sparger_snappyRefine_700rpm_opt_coeff" +case_path = "." +dataFolder = args.data_folder + +if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): + sys.exit("WARNING: History already created, Skipping") + +time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) +cellCentres = readMesh(os.path.join(case_path, f"meshCellCentres_0.obj")) +nCells = len(cellCentres) + + +co2_history = np.zeros(len(time_str_sorted)) +c_co2_history = np.zeros(len(time_str_sorted)) +h2_history = np.zeros(len(time_str_sorted)) +c_h2_history = np.zeros(len(time_str_sorted)) +gh_history = np.zeros(len(time_str_sorted)) +liqvol_history = np.zeros(len(time_str_sorted)) +print(f"case_path = {case_path}") +field_dict = {} +for itime, time in enumerate(time_float_sorted): + time_folder = time_str_sorted[itime] + print(f"\tTime : {time_folder}") + if not field_dict == {}: + new_field_dict = {} + if "volume" in field_dict: + new_field_dict["volume"] = field_dict["volume"] + field_dict = new_field_dict + gh_history[itime], field_dict = compute_gas_holdup( + case_path, time_str_sorted[itime], nCells, field_dict + ) + co2_history[itime], field_dict = co2liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + h2_history[itime], field_dict = h2liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + liqvol_history[itime], field_dict = vol_liq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + c_co2_history[itime], c_h2_history[itime], field_dict = cliq( + case_path, time_str_sorted[itime], nCells, field_dict + ) + + +os.makedirs(dataFolder, exist_ok=True) +os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) +np.savez( + os.path.join(dataFolder, case_name, "conv.npz"), + time=np.array(time_float_sorted), + gh=gh_history, + co2=co2_history, + h2=h2_history, + vol_liq=liqvol_history, + c_h2=c_h2_history, + c_co2=c_co2_history, +) diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh new file mode 100644 index 00000000..99da4760 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh @@ -0,0 +1,5 @@ +birdmultiphaseEulerFoam + + + + diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script new file mode 100755 index 00000000..efe675ff --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script @@ -0,0 +1,14 @@ +#!/bin/bash +#SBATCH --qos=high +#SBATCH --job-name=val2 +##SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=16 +#SBATCH --time=07:59:00 +#SBATCH --account=co2snow + +bash presteps.sh +source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc +decomposePar -fileHandler collated +srun -n 16 birdmultiphaseEulerFoam -parallel -fileHandler collated +reconstructPar -newTimes diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post new file mode 100755 index 00000000..aabbc33e --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post @@ -0,0 +1,10 @@ +#!/bin/bash +#SBATCH --qos=high +#SBATCH --job-name=val2 +##SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=16 +#SBATCH --time=00:59:00 +#SBATCH --account=co2snow + +bash computeQOI.sh diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict new file mode 100644 index 00000000..1183d262 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict @@ -0,0 +1,746 @@ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object blockMeshDict; +} + +convertToMeters 1.0; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +vertices +( +( 0.0 0.0 0.0) +( 1.0 0.0 0.0) +( 2.0 0.0 0.0) +( 3.0 0.0 0.0) +( 4.0 0.0 0.0) +( 5.0 0.0 0.0) +( 6.0 0.0 0.0) +( 7.0 0.0 0.0) +( 8.0 0.0 0.0) +( 9.0 0.0 0.0) +( 10.0 0.0 0.0) +( 0.0 1.0 0.0) +( 1.0 1.0 0.0) +( 2.0 1.0 0.0) +( 3.0 1.0 0.0) +( 4.0 1.0 0.0) +( 5.0 1.0 0.0) +( 6.0 1.0 0.0) +( 7.0 1.0 0.0) +( 8.0 1.0 0.0) +( 9.0 1.0 0.0) +( 10.0 1.0 0.0) +( 0.0 2.0 0.0) +( 1.0 2.0 0.0) +( 2.0 2.0 0.0) +( 3.0 2.0 0.0) +( 4.0 2.0 0.0) +( 5.0 2.0 0.0) +( 6.0 2.0 0.0) +( 7.0 2.0 0.0) +( 8.0 2.0 0.0) +( 9.0 2.0 0.0) +( 10.0 2.0 0.0) +( 0.0 3.0 0.0) +( 1.0 3.0 0.0) +( 2.0 3.0 0.0) +( 3.0 3.0 0.0) +( 4.0 3.0 0.0) +( 5.0 3.0 0.0) +( 6.0 3.0 0.0) +( 7.0 3.0 0.0) +( 8.0 3.0 0.0) +( 9.0 3.0 0.0) +( 10.0 3.0 0.0) +( 0.0 4.0 0.0) +( 1.0 4.0 0.0) +( 2.0 4.0 0.0) +( 3.0 4.0 0.0) +( 4.0 4.0 0.0) +( 5.0 4.0 0.0) +( 6.0 4.0 0.0) +( 7.0 4.0 0.0) +( 8.0 4.0 0.0) +( 9.0 4.0 0.0) +( 10.0 4.0 0.0) +( 0.0 5.0 0.0) +( 1.0 5.0 0.0) +( 2.0 5.0 0.0) +( 3.0 5.0 0.0) +( 4.0 5.0 0.0) +( 5.0 5.0 0.0) +( 6.0 5.0 0.0) +( 7.0 5.0 0.0) +( 8.0 5.0 0.0) +( 9.0 5.0 0.0) +( 10.0 5.0 0.0) +( 0.0 6.0 0.0) +( 1.0 6.0 0.0) +( 2.0 6.0 0.0) +( 3.0 6.0 0.0) +( 4.0 6.0 0.0) +( 5.0 6.0 0.0) +( 6.0 6.0 0.0) +( 7.0 6.0 0.0) +( 8.0 6.0 0.0) +( 9.0 6.0 0.0) +( 10.0 6.0 0.0) +( 0.0 7.0 0.0) +( 1.0 7.0 0.0) +( 2.0 7.0 0.0) +( 3.0 7.0 0.0) +( 4.0 7.0 0.0) +( 5.0 7.0 0.0) +( 6.0 7.0 0.0) +( 7.0 7.0 0.0) +( 8.0 7.0 0.0) +( 9.0 7.0 0.0) +( 10.0 7.0 0.0) +( 0.0 0.0 1.0) +( 1.0 0.0 1.0) +( 2.0 0.0 1.0) +( 3.0 0.0 1.0) +( 4.0 0.0 1.0) +( 5.0 0.0 1.0) +( 6.0 0.0 1.0) +( 7.0 0.0 1.0) +( 8.0 0.0 1.0) +( 9.0 0.0 1.0) +( 10.0 0.0 1.0) +( 0.0 1.0 1.0) +( 1.0 1.0 1.0) +( 2.0 1.0 1.0) +( 3.0 1.0 1.0) +( 4.0 1.0 1.0) +( 5.0 1.0 1.0) +( 6.0 1.0 1.0) +( 7.0 1.0 1.0) +( 8.0 1.0 1.0) +( 9.0 1.0 1.0) +( 10.0 1.0 1.0) +( 0.0 2.0 1.0) +( 1.0 2.0 1.0) +( 2.0 2.0 1.0) +( 3.0 2.0 1.0) +( 4.0 2.0 1.0) +( 5.0 2.0 1.0) +( 6.0 2.0 1.0) +( 7.0 2.0 1.0) +( 8.0 2.0 1.0) +( 9.0 2.0 1.0) +( 10.0 2.0 1.0) +( 0.0 3.0 1.0) +( 1.0 3.0 1.0) +( 2.0 3.0 1.0) +( 3.0 3.0 1.0) +( 4.0 3.0 1.0) +( 5.0 3.0 1.0) +( 6.0 3.0 1.0) +( 7.0 3.0 1.0) +( 8.0 3.0 1.0) +( 9.0 3.0 1.0) +( 10.0 3.0 1.0) +( 0.0 4.0 1.0) +( 1.0 4.0 1.0) +( 2.0 4.0 1.0) +( 3.0 4.0 1.0) +( 4.0 4.0 1.0) +( 5.0 4.0 1.0) +( 6.0 4.0 1.0) +( 7.0 4.0 1.0) +( 8.0 4.0 1.0) +( 9.0 4.0 1.0) +( 10.0 4.0 1.0) +( 0.0 5.0 1.0) +( 1.0 5.0 1.0) +( 2.0 5.0 1.0) +( 3.0 5.0 1.0) +( 4.0 5.0 1.0) +( 5.0 5.0 1.0) +( 6.0 5.0 1.0) +( 7.0 5.0 1.0) +( 8.0 5.0 1.0) +( 9.0 5.0 1.0) +( 10.0 5.0 1.0) +( 0.0 6.0 1.0) +( 1.0 6.0 1.0) +( 2.0 6.0 1.0) +( 3.0 6.0 1.0) +( 4.0 6.0 1.0) +( 5.0 6.0 1.0) +( 6.0 6.0 1.0) +( 7.0 6.0 1.0) +( 8.0 6.0 1.0) +( 9.0 6.0 1.0) +( 10.0 6.0 1.0) +( 0.0 7.0 1.0) +( 1.0 7.0 1.0) +( 2.0 7.0 1.0) +( 3.0 7.0 1.0) +( 4.0 7.0 1.0) +( 5.0 7.0 1.0) +( 6.0 7.0 1.0) +( 7.0 7.0 1.0) +( 8.0 7.0 1.0) +( 9.0 7.0 1.0) +( 10.0 7.0 1.0) +( 0.0 0.0 2.0) +( 1.0 0.0 2.0) +( 2.0 0.0 2.0) +( 3.0 0.0 2.0) +( 4.0 0.0 2.0) +( 5.0 0.0 2.0) +( 6.0 0.0 2.0) +( 7.0 0.0 2.0) +( 8.0 0.0 2.0) +( 9.0 0.0 2.0) +( 10.0 0.0 2.0) +( 0.0 1.0 2.0) +( 1.0 1.0 2.0) +( 2.0 1.0 2.0) +( 3.0 1.0 2.0) +( 4.0 1.0 2.0) +( 5.0 1.0 2.0) +( 6.0 1.0 2.0) +( 7.0 1.0 2.0) +( 8.0 1.0 2.0) +( 9.0 1.0 2.0) +( 10.0 1.0 2.0) +( 0.0 2.0 2.0) +( 1.0 2.0 2.0) +( 2.0 2.0 2.0) +( 3.0 2.0 2.0) +( 4.0 2.0 2.0) +( 5.0 2.0 2.0) +( 6.0 2.0 2.0) +( 7.0 2.0 2.0) +( 8.0 2.0 2.0) +( 9.0 2.0 2.0) +( 10.0 2.0 2.0) +( 0.0 3.0 2.0) +( 1.0 3.0 2.0) +( 2.0 3.0 2.0) +( 3.0 3.0 2.0) +( 4.0 3.0 2.0) +( 5.0 3.0 2.0) +( 6.0 3.0 2.0) +( 7.0 3.0 2.0) +( 8.0 3.0 2.0) +( 9.0 3.0 2.0) +( 10.0 3.0 2.0) +( 0.0 4.0 2.0) +( 1.0 4.0 2.0) +( 2.0 4.0 2.0) +( 3.0 4.0 2.0) +( 4.0 4.0 2.0) +( 5.0 4.0 2.0) +( 6.0 4.0 2.0) +( 7.0 4.0 2.0) +( 8.0 4.0 2.0) +( 9.0 4.0 2.0) +( 10.0 4.0 2.0) +( 0.0 5.0 2.0) +( 1.0 5.0 2.0) +( 2.0 5.0 2.0) +( 3.0 5.0 2.0) +( 4.0 5.0 2.0) +( 5.0 5.0 2.0) +( 6.0 5.0 2.0) +( 7.0 5.0 2.0) +( 8.0 5.0 2.0) +( 9.0 5.0 2.0) +( 10.0 5.0 2.0) +( 0.0 6.0 2.0) +( 1.0 6.0 2.0) +( 2.0 6.0 2.0) +( 3.0 6.0 2.0) +( 4.0 6.0 2.0) +( 5.0 6.0 2.0) +( 6.0 6.0 2.0) +( 7.0 6.0 2.0) +( 8.0 6.0 2.0) +( 9.0 6.0 2.0) +( 10.0 6.0 2.0) +( 0.0 7.0 2.0) +( 1.0 7.0 2.0) +( 2.0 7.0 2.0) +( 3.0 7.0 2.0) +( 4.0 7.0 2.0) +( 5.0 7.0 2.0) +( 6.0 7.0 2.0) +( 7.0 7.0 2.0) +( 8.0 7.0 2.0) +( 9.0 7.0 2.0) +( 10.0 7.0 2.0) +( 0.0 0.0 3.0) +( 1.0 0.0 3.0) +( 2.0 0.0 3.0) +( 3.0 0.0 3.0) +( 4.0 0.0 3.0) +( 5.0 0.0 3.0) +( 6.0 0.0 3.0) +( 7.0 0.0 3.0) +( 8.0 0.0 3.0) +( 9.0 0.0 3.0) +( 10.0 0.0 3.0) +( 0.0 1.0 3.0) +( 1.0 1.0 3.0) +( 2.0 1.0 3.0) +( 3.0 1.0 3.0) +( 4.0 1.0 3.0) +( 5.0 1.0 3.0) +( 6.0 1.0 3.0) +( 7.0 1.0 3.0) +( 8.0 1.0 3.0) +( 9.0 1.0 3.0) +( 10.0 1.0 3.0) +( 0.0 2.0 3.0) +( 1.0 2.0 3.0) +( 2.0 2.0 3.0) +( 3.0 2.0 3.0) +( 4.0 2.0 3.0) +( 5.0 2.0 3.0) +( 6.0 2.0 3.0) +( 7.0 2.0 3.0) +( 8.0 2.0 3.0) +( 9.0 2.0 3.0) +( 10.0 2.0 3.0) +( 0.0 3.0 3.0) +( 1.0 3.0 3.0) +( 2.0 3.0 3.0) +( 3.0 3.0 3.0) +( 4.0 3.0 3.0) +( 5.0 3.0 3.0) +( 6.0 3.0 3.0) +( 7.0 3.0 3.0) +( 8.0 3.0 3.0) +( 9.0 3.0 3.0) +( 10.0 3.0 3.0) +( 0.0 4.0 3.0) +( 1.0 4.0 3.0) +( 2.0 4.0 3.0) +( 3.0 4.0 3.0) +( 4.0 4.0 3.0) +( 5.0 4.0 3.0) +( 6.0 4.0 3.0) +( 7.0 4.0 3.0) +( 8.0 4.0 3.0) +( 9.0 4.0 3.0) +( 10.0 4.0 3.0) +( 0.0 5.0 3.0) +( 1.0 5.0 3.0) +( 2.0 5.0 3.0) +( 3.0 5.0 3.0) +( 4.0 5.0 3.0) +( 5.0 5.0 3.0) +( 6.0 5.0 3.0) +( 7.0 5.0 3.0) +( 8.0 5.0 3.0) +( 9.0 5.0 3.0) +( 10.0 5.0 3.0) +( 0.0 6.0 3.0) +( 1.0 6.0 3.0) +( 2.0 6.0 3.0) +( 3.0 6.0 3.0) +( 4.0 6.0 3.0) +( 5.0 6.0 3.0) +( 6.0 6.0 3.0) +( 7.0 6.0 3.0) +( 8.0 6.0 3.0) +( 9.0 6.0 3.0) +( 10.0 6.0 3.0) +( 0.0 7.0 3.0) +( 1.0 7.0 3.0) +( 2.0 7.0 3.0) +( 3.0 7.0 3.0) +( 4.0 7.0 3.0) +( 5.0 7.0 3.0) +( 6.0 7.0 3.0) +( 7.0 7.0 3.0) +( 8.0 7.0 3.0) +( 9.0 7.0 3.0) +( 10.0 7.0 3.0) +( 0.0 0.0 4.0) +( 1.0 0.0 4.0) +( 2.0 0.0 4.0) +( 3.0 0.0 4.0) +( 4.0 0.0 4.0) +( 5.0 0.0 4.0) +( 6.0 0.0 4.0) +( 7.0 0.0 4.0) +( 8.0 0.0 4.0) +( 9.0 0.0 4.0) +( 10.0 0.0 4.0) +( 0.0 1.0 4.0) +( 1.0 1.0 4.0) +( 2.0 1.0 4.0) +( 3.0 1.0 4.0) +( 4.0 1.0 4.0) +( 5.0 1.0 4.0) +( 6.0 1.0 4.0) +( 7.0 1.0 4.0) +( 8.0 1.0 4.0) +( 9.0 1.0 4.0) +( 10.0 1.0 4.0) +( 0.0 2.0 4.0) +( 1.0 2.0 4.0) +( 2.0 2.0 4.0) +( 3.0 2.0 4.0) +( 4.0 2.0 4.0) +( 5.0 2.0 4.0) +( 6.0 2.0 4.0) +( 7.0 2.0 4.0) +( 8.0 2.0 4.0) +( 9.0 2.0 4.0) +( 10.0 2.0 4.0) +( 0.0 3.0 4.0) +( 1.0 3.0 4.0) +( 2.0 3.0 4.0) +( 3.0 3.0 4.0) +( 4.0 3.0 4.0) +( 5.0 3.0 4.0) +( 6.0 3.0 4.0) +( 7.0 3.0 4.0) +( 8.0 3.0 4.0) +( 9.0 3.0 4.0) +( 10.0 3.0 4.0) +( 0.0 4.0 4.0) +( 1.0 4.0 4.0) +( 2.0 4.0 4.0) +( 3.0 4.0 4.0) +( 4.0 4.0 4.0) +( 5.0 4.0 4.0) +( 6.0 4.0 4.0) +( 7.0 4.0 4.0) +( 8.0 4.0 4.0) +( 9.0 4.0 4.0) +( 10.0 4.0 4.0) +( 0.0 5.0 4.0) +( 1.0 5.0 4.0) +( 2.0 5.0 4.0) +( 3.0 5.0 4.0) +( 4.0 5.0 4.0) +( 5.0 5.0 4.0) +( 6.0 5.0 4.0) +( 7.0 5.0 4.0) +( 8.0 5.0 4.0) +( 9.0 5.0 4.0) +( 10.0 5.0 4.0) +( 0.0 6.0 4.0) +( 1.0 6.0 4.0) +( 2.0 6.0 4.0) +( 3.0 6.0 4.0) +( 4.0 6.0 4.0) +( 5.0 6.0 4.0) +( 6.0 6.0 4.0) +( 7.0 6.0 4.0) +( 8.0 6.0 4.0) +( 9.0 6.0 4.0) +( 10.0 6.0 4.0) +( 0.0 7.0 4.0) +( 1.0 7.0 4.0) +( 2.0 7.0 4.0) +( 3.0 7.0 4.0) +( 4.0 7.0 4.0) +( 5.0 7.0 4.0) +( 6.0 7.0 4.0) +( 7.0 7.0 4.0) +( 8.0 7.0 4.0) +( 9.0 7.0 4.0) +( 10.0 7.0 4.0) +( 0.0 0.0 5.0) +( 1.0 0.0 5.0) +( 2.0 0.0 5.0) +( 3.0 0.0 5.0) +( 4.0 0.0 5.0) +( 5.0 0.0 5.0) +( 6.0 0.0 5.0) +( 7.0 0.0 5.0) +( 8.0 0.0 5.0) +( 9.0 0.0 5.0) +( 10.0 0.0 5.0) +( 0.0 1.0 5.0) +( 1.0 1.0 5.0) +( 2.0 1.0 5.0) +( 3.0 1.0 5.0) +( 4.0 1.0 5.0) +( 5.0 1.0 5.0) +( 6.0 1.0 5.0) +( 7.0 1.0 5.0) +( 8.0 1.0 5.0) +( 9.0 1.0 5.0) +( 10.0 1.0 5.0) +( 0.0 2.0 5.0) +( 1.0 2.0 5.0) +( 2.0 2.0 5.0) +( 3.0 2.0 5.0) +( 4.0 2.0 5.0) +( 5.0 2.0 5.0) +( 6.0 2.0 5.0) +( 7.0 2.0 5.0) +( 8.0 2.0 5.0) +( 9.0 2.0 5.0) +( 10.0 2.0 5.0) +( 0.0 3.0 5.0) +( 1.0 3.0 5.0) +( 2.0 3.0 5.0) +( 3.0 3.0 5.0) +( 4.0 3.0 5.0) +( 5.0 3.0 5.0) +( 6.0 3.0 5.0) +( 7.0 3.0 5.0) +( 8.0 3.0 5.0) +( 9.0 3.0 5.0) +( 10.0 3.0 5.0) +( 0.0 4.0 5.0) +( 1.0 4.0 5.0) +( 2.0 4.0 5.0) +( 3.0 4.0 5.0) +( 4.0 4.0 5.0) +( 5.0 4.0 5.0) +( 6.0 4.0 5.0) +( 7.0 4.0 5.0) +( 8.0 4.0 5.0) +( 9.0 4.0 5.0) +( 10.0 4.0 5.0) +( 0.0 5.0 5.0) +( 1.0 5.0 5.0) +( 2.0 5.0 5.0) +( 3.0 5.0 5.0) +( 4.0 5.0 5.0) +( 5.0 5.0 5.0) +( 6.0 5.0 5.0) +( 7.0 5.0 5.0) +( 8.0 5.0 5.0) +( 9.0 5.0 5.0) +( 10.0 5.0 5.0) +( 0.0 6.0 5.0) +( 1.0 6.0 5.0) +( 2.0 6.0 5.0) +( 3.0 6.0 5.0) +( 4.0 6.0 5.0) +( 5.0 6.0 5.0) +( 6.0 6.0 5.0) +( 7.0 6.0 5.0) +( 8.0 6.0 5.0) +( 9.0 6.0 5.0) +( 10.0 6.0 5.0) +( 0.0 7.0 5.0) +( 1.0 7.0 5.0) +( 2.0 7.0 5.0) +( 3.0 7.0 5.0) +( 4.0 7.0 5.0) +( 5.0 7.0 5.0) +( 6.0 7.0 5.0) +( 7.0 7.0 5.0) +( 8.0 7.0 5.0) +( 9.0 7.0 5.0) +( 10.0 7.0 5.0) +); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +blocks +( + + //block 0 +hex (0 1 12 11 88 89 100 99 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 1 +hex (1 2 13 12 89 90 101 100 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 2 +hex (2 3 14 13 90 91 102 101 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 3 +hex (3 4 15 14 91 92 103 102 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 4 +hex (4 5 16 15 92 93 104 103 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 5 +hex (5 6 17 16 93 94 105 104 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 6 +hex (6 7 18 17 94 95 106 105 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 7 +hex (7 8 19 18 95 96 107 106 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 8 +hex (8 9 20 19 96 97 108 107 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 9 +hex (9 10 21 20 97 98 109 108 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 10 +hex (97 98 109 108 185 186 197 196 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 11 +hex (185 186 197 196 273 274 285 284 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 12 +hex (273 274 285 284 361 362 373 372 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 13 +hex (361 362 373 372 449 450 461 460 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 14 +hex (360 361 372 371 448 449 460 459 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 15 +hex (359 360 371 370 447 448 459 458 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 16 +hex (358 359 370 369 446 447 458 457 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 17 +hex (357 358 369 368 445 446 457 456 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 18 +hex (356 357 368 367 444 445 456 455 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 19 +hex (355 356 367 366 443 444 455 454 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 20 +hex (354 355 366 365 442 443 454 453 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 21 +hex (353 354 365 364 441 442 453 452 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 22 +hex (352 353 364 363 440 441 452 451 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 23 +hex (363 364 375 374 451 452 463 462 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 24 +hex (374 375 386 385 462 463 474 473 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 25 +hex (385 386 397 396 473 474 485 484 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 26 +hex (396 397 408 407 484 485 496 495 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 27 +hex (407 408 419 418 495 496 507 506 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 28 +hex (418 419 430 429 506 507 518 517 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 29 +hex (308 309 320 319 396 397 408 407 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 30 +hex (220 221 232 231 308 309 320 319 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 31 +hex (132 133 144 143 220 221 232 231 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 32 +hex (44 45 56 55 132 133 144 143 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 33 +hex (55 56 67 66 143 144 155 154 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 34 +hex (66 67 78 77 154 155 166 165 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 35 +hex (33 34 45 44 121 122 133 132 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 36 +hex (22 23 34 33 110 111 122 121 ) +( 10 10 10 ) +SimpleGrading (1 1 1) + + //block 37 +hex (11 12 23 22 99 100 111 110 ) +( 10 10 10 ) +SimpleGrading (1 1 1) +); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +defaultPatch +{ type wall;} + +patches +( +); diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict new file mode 100644 index 00000000..6f803fd2 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict @@ -0,0 +1,67 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object controlDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +application birdmultiphaseEulerFoam; + +startFrom latestTime;//startTime; + +startTime 0; + +stopAt endTime; + +endTime 200; + +deltaT 0.0001; + +writeControl adjustableRunTime; + +writeInterval 2; + +purgeWrite 0; + +writeFormat ascii; + +writePrecision 6; + +writeCompression off; + +timeFormat general; + +timePrecision 6; + +runTimeModifiable yes; + +adjustTimeStep yes; + +maxCo 0.5; + +maxDeltaT 0.01; + + +functions +{ + + #includeFunc writeObjects(d.gas) + #includeFunc writeObjects(thermo:rho.gas) + #includeFunc writeObjects(thermo:rho.liquid) +} +//functions +//{ +// #includeFunc fieldAverage(U.air, U.water, alpha.air, p) +//} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict new file mode 100755 index 00000000..f8397e73 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict @@ -0,0 +1,30 @@ +/*--------------------------------*- C++ -*----------------------------------*\ +| ========= | | +| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | +| \\ / O peration | Version: 3.0.x | +| \\ / A nd | Web: www.OpenFOAM.org | +| \\/ M anipulation | | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object decomposeParDict; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +numberOfSubdomains 16; + +method scotch; + +hierarchicalCoeffs +{ + n (4 4 1); + delta 0.001; + order xyz; +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints new file mode 100644 index 00000000..334f1c8f --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints @@ -0,0 +1,56 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object fvConstraints; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +limitp +{ + type limitPressure; + + min 1e4; +} +limitUliq +{ + type limitVelocity; + active yes; + U U.liquid; + selectionMode all; + max 1e1; +} +limitUgas +{ + type limitVelocity; + active yes; + U U.gas; + selectionMode all; + max 2e1; +} +limitTgas +{ + type limitTemperature; + selectionMode all; + min 290; + max 310; + phase gas; +} +limitTliq +{ + type limitTemperature; + selectionMode all; + min 290; + max 310; + phase liquid; +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes new file mode 100644 index 00000000..52e6e13a --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes @@ -0,0 +1,70 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSchemes; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +ddtSchemes +{ + default Euler; +} + +gradSchemes +{ + default Gauss linear; + limited cellLimited Gauss linear 1; +} + +divSchemes +{ + default none; + + "div\(phi,alpha.*\)" Gauss vanLeer; + + "div\(phir,alpha.*,alpha.*\)" Gauss vanLeer; + + "div\(alphaRhoPhi.*,U.*\)" Gauss limitedLinearV 1; + "div\(phi.*,U.*\)" Gauss limitedLinearV 1; + "div\(alphaRhoPhi.*,Yi\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,(h|e).*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,(K|k|epsilon|omega).*\)" Gauss limitedLinear 1; + "div\(alphaPhi.*,f.*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,\(p\|thermo:rho.*\)\)" Gauss limitedLinear 1; + + "div\(phim,(k|epsilon)m\)" Gauss upwind; + "div\(\(\(\(alpha.*\*thermo:rho.*\)*nuEff.*\)*dev2\(T\(grad\(U.*\)\)\)\)\)" Gauss linear; +} + +laplacianSchemes +{ + default Gauss linear corrected; +} + +interpolationSchemes +{ + default linear; +} + +snGradSchemes +{ + default uncorrected; +} + +wallDist +{ + method Poisson; + nRequired true; +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution new file mode 100644 index 00000000..2e69fdfa --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution @@ -0,0 +1,120 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSolution; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +solvers +{ + "alpha.*" + { + nAlphaCorr 2; + nAlphaSubCycles 5; + } + + bubbles + { + nCorr 1; + tolerance 1e-4; + scale true; + solveOnFinalIterOnly true; + sourceUpdateInterval 1; + } + + p_rgh + { + solver GAMG; + smoother DIC; + tolerance 1e-7; + relTol 0; + } + + p_rghFinal + { + $p_rgh; + relTol 0; + } + + "(k|omega).*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-7; + relTol 0; + minIter 1; + } + + "(e|h).*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-8; + relTol 0; + minIter 0; + maxIter 3; + } + + "f.*" + { + solver PBiCGStab; + preconditioner DILU; + tolerance 1e-6; + relTol 0; + } + + "Yi.*" + { + solver PBiCGStab; + preconditioner DILU; + tolerance 1e-12; + relTol 0; + residualAlpha 1e-8; + } + + "U.*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-5; + relTol 0; + minIter 1; + } + + yPsi + { + solver PCG; + preconditioner DIC; + tolerance 1e-10; + relTol 0; + } + +} + +PIMPLE +{ + nOuterCorrectors 3; + nCorrectors 1; + nNonOrthogonalCorrectors 0; + +} + +relaxationFactors +{ + equations + { + ".*" 1; + } +} + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json new file mode 100644 index 00000000..2b53da73 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json @@ -0,0 +1,28 @@ +{ + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0}, + "y" : {"nblocks": 11, "size_per_block": 1.0}, + "z" : {"nblocks": 5, "size_per_block": 1.0} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + }, + "inlets": [ + {"branch_id": 0, "type": "circle", "frac_space": 0.2222222222222222, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "bottom"}, + {"branch_id": 0, "type": "circle", "frac_space": 0.5, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"}, + {"branch_id": 0, "type": "circle", "frac_space": 0.7777777777777778, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"} + ], + "outlets": [ + {"branch_id": 6, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"}, + {"branch_id": 4, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"} + ] +} diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json new file mode 100644 index 00000000..29841d7e --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json @@ -0,0 +1,26 @@ +{ + "Meshing": { + "Blockwise": { + "x" : 10, + "y" : 10, + "z" : 10 + } + }, + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0}, + "y" : {"nblocks": 11, "size_per_block": 1.0}, + "z" : {"nblocks": 5, "size_per_block": 1.0} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + } +} diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json new file mode 100644 index 00000000..b6224fb7 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json @@ -0,0 +1,29 @@ +{ + "Meshing": { + "Blockwise": { + "x" : 10, + "y" : 10, + "z" : 10 + } + }, + "Geometry": { + "OverallDomain": { + "x" : {"nblocks": 10, "size_per_block": 1.0, "rescale": 2.7615275385627096}, + "y" : {"nblocks": 11, "size_per_block": 1.0, "rescale": 2.7615275385627096}, + "z" : {"nblocks": 5, "size_per_block": 1.0, "rescale": 2.7615275385627096} + }, + "Fluids": [ + [ [0,0,0], [9,0,0] ], + [ [9,0,0], [9,0,4] ], + [ [9,0,4], [0,0,4] ], + [ [0,1,4], [0,4,4] ], + [ [0,4,4], [0,10,4] ], + [ [0,4,4], [0,4,0] ], + [ [0,4,0], [0,10,0] ], + [ [0,4,0], [0,1,0] ] + ] + }, + "mixers": [ + {"branch_id": 2, "frac_space": 0.5, "start_time": 4, "power": 1500, "sign": "+"} + ] +} diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict new file mode 100644 index 00000000..89a797b9 --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict @@ -0,0 +1,37 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: dev + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object setFieldsDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +defaultFieldValues +( + volScalarFieldValue alpha.gas 0.99 + volScalarFieldValue alpha.liquid 0.01 +); + +regions +( + boxToCell + { + box (-1.0 -1.0 -1.0) (552.3 11.046 552.3); + fieldValues + ( + volScalarFieldValue alpha.gas 0.01 + volScalarFieldValue alpha.liquid 0.99 + ); + } +); + + +// ************************************************************************* // diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py new file mode 100644 index 00000000..0594eccc --- /dev/null +++ b/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py @@ -0,0 +1,47 @@ +import os + +import numpy as np + +from bird.utilities.ofio import * + + +def writeGvars(inletA, liqVol): + filename_tmp = os.path.join("constant", "globalVars_temp") + with open(filename_tmp, "r+") as f: + lines = f.readlines() + filename = os.path.join("constant", "globalVars") + with open(filename, "w+") as f: + for line in lines: + if line.startswith("inletA"): + f.write(f"inletA\t{inletA:g};\n") + elif line.startswith("liqVol"): + f.write(f"liqVol\t{liqVol:g};\n") + else: + f.write(line) + + +def readInletArea(): + filename = os.path.join( + "postProcessing", + "patchIntegrate(patch=inlet,field=alpha.gas)", + "0", + "surfaceFieldValue.dat", + ) + with open(filename, "r+") as f: + lines = f.readlines() + return float(lines[4].split()[-1]) + + +def getLiqVol(): + cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + alpha_field = readOFScal( + os.path.join("0", "alpha.liquid"), len(cellCentres) + ) + return np.sum(volume_field * alpha_field) + + +if __name__ == "__main__": + A = readInletArea() + V = getLiqVol() + writeGvars(A, V) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index 354cabd7..aa992026 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -131,12 +131,22 @@ def overwrite_bubble_size_model(case_folder, constantD=False): def generate_small_reactor_cases( - config_dict, branchcom_spots, vvm, power, constantD, study_folder + config_dict, + branchcom_spots, + vvm, + power, + constantD, + study_folder, + template_folder="loop_reactor_pbe_dynmix_nonstat_headbranch", ): - templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch" + if not os.path.isabs(template_folder): + + template_folder = os.path.join( + f"{BIRD_CASE_DIR}", f"{template_folder}" + ) geom_dict = make_default_geom_dict_from_file( - f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json", + os.path.join(f"{template_folder}", "system", "mesh.json"), rescale=0.05, ) try: @@ -146,8 +156,8 @@ def generate_small_reactor_cases( os.makedirs(study_folder) for simid in config_dict: shutil.copytree( - f"{BIRD_CASE_DIR}/{templateFolder}", - f"{study_folder}/Sim_{simid}", + f"{template_folder}", + os.path.join(f"{study_folder}", "Sim_{simid}"), ) bc_dict = {} bc_dict["inlets"] = [] @@ -235,18 +245,28 @@ def generate_small_reactor_cases( ) geom_dict = make_default_geom_dict_from_file( - f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json", + os.path.join(f"{template_folder}", "system", "mesh.json"), rescale=0.05, ) def generate_scaledup_reactor_cases( - config_dict, branchcom_spots, vvm, power, constantD, study_folder + config_dict, + branchcom_spots, + vvm, + power, + constantD, + study_folder, + template_folder="loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", ): - templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup" + if not os.path.isabs(template_folder): + template_folder = os.path.join( + f"{BIRD_CASE_DIR}", f"{template_folder}" + ) + geom_dict = make_default_geom_dict_from_file( - f"{BIRD_CASE_DIR}/{templateFolder}/system/mesh.json" + os.path.join(f"{template_folder}", "system", "mesh.json") ) try: shutil.rmtree(study_folder) @@ -255,8 +275,8 @@ def generate_scaledup_reactor_cases( os.makedirs(study_folder) for simid in config_dict: shutil.copytree( - f"{BIRD_CASE_DIR}/{templateFolder}", - f"{study_folder}/Sim_{simid}", + f"{template_folder}", + os.path.join(f"{study_folder}", f"Sim_{simid}"), ) bc_dict = {} bc_dict["inlets"] = [] @@ -416,6 +436,7 @@ def generate_single_scaledup_reactor_sparger_cases( constantD: bool = True, vvm: float = 0.4, study_folder: str = ".", + template_folder: str = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", ): """ Generates loop reactor case with desired sparger placement configuration @@ -441,6 +462,8 @@ def generate_single_scaledup_reactor_sparger_cases( VVM value [-] study_folder : str Where to generate the case + template_folder: str + The case template to start from """ # Sanity checks @@ -456,16 +479,18 @@ def generate_single_scaledup_reactor_sparger_cases( branch_id = [int(entry) for entry in sparger_locs] # Case generation - templateFolder = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup" - geom_dict = make_default_geom_dict_from_file( - os.path.join( - f"{BIRD_CASE_DIR}", f"{templateFolder}", "system", "mesh.json" + if not os.path.isabs(template_folder): + + template_folder = os.path.join( + f"{BIRD_CASE_DIR}", f"{template_folder}" ) + geom_dict = make_default_geom_dict_from_file( + os.path.join(f"{template_folder}", "system", "mesh.json") ) # Start from template shutil.copytree( - os.path.join(f"{BIRD_CASE_DIR}", f"{templateFolder}"), + f"{template_folder}", os.path.join(f"{study_folder}", f"Sim_{sim_id}"), ) diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 056a271b..8f2cc03d 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -4,6 +4,7 @@ import numpy as np +from bird import BIRD_CASEGEN_DATA_DIR from bird.preprocess.json_gen.design_io import * from bird.preprocess.json_gen.generate_designs import * @@ -15,6 +16,10 @@ def test_continuous_loop(): sim_id=0, vvm=0.4, study_folder=".", + template_folder=os.path.join( + BIRD_CASEGEN_DATA_DIR, + "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", + ), ) @@ -75,6 +80,10 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): power=pow_v, constantD=True, study_folder=study_folder, + template_folder=os.path.join( + BIRD_CASEGEN_DATA_DIR, + "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", + ), ) write_script_start(f"{study_folder}/many_scripts_start", n_sim) write_script_post(f"{study_folder}/many_scripts_post", n_sim) From 4f75b72f25ed2a82e5007304c17a321b2797a23c Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 10:54:00 -0600 Subject: [PATCH 10/86] missing module --- bird/preprocess/json_gen/generate_designs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index aa992026..099959f5 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -1,6 +1,7 @@ import os import pickle import shutil +import sys import numpy as np From af9b55fc86869c985639dd20c2f5d3f7b8996edd Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:35:04 -0600 Subject: [PATCH 11/86] include case gen data to package --- bird/__init__.py | 2 +- .../.vim/.netrwhist | 0 .../0.orig/CO2.gas | 0 .../0.orig/CO2.liquid | 0 .../0.orig/H2.gas | 0 .../0.orig/H2.liquid | 0 .../0.orig/N2.gas | 0 .../0.orig/T.gas | 0 .../0.orig/T.liquid | 0 .../0.orig/U.gas | 0 .../0.orig/U.liquid | 0 .../0.orig/Ydefault.gas | 0 .../0.orig/Ydefault.liquid | 0 .../0.orig/alpha.gas | 0 .../0.orig/alpha.liquid | 0 .../0.orig/alphat.gas | 0 .../0.orig/alphat.liquid | 0 .../0.orig/epsilon.gas | 0 .../0.orig/epsilon.liquid | 0 .../0.orig/f.gas | 0 .../0.orig/k.gas | 0 .../0.orig/k.liquid | 0 .../0.orig/nut.gas | 0 .../0.orig/nut.liquid | 0 .../0.orig/p | 0 .../0.orig/p_rgh | 0 .../Allclean | 0 .../computeQOI.sh | 0 .../constant/dynamicMix_util.H | 0 .../constant/fvModels | 0 .../constant/g | 0 .../constant/globalVars | 0 .../constant/globalVars_temp | 0 .../constant/momentumTransport.gas | 0 .../constant/momentumTransport.liquid | 0 .../constant/phaseProperties | 0 .../constant/phaseProperties_constantd | 0 .../constant/phaseProperties_pbe | 0 .../constant/thermophysicalProperties.gas | 0 .../constant/thermophysicalProperties.liquid | 0 .../get_qoi.py | 0 .../presteps.sh | 0 .../read_history.py | 0 .../run.sh | 0 .../script | 0 .../script_post | 0 .../system/blockMeshDict | 0 .../system/controlDict | 0 .../system/decomposeParDict | 0 .../system/fvConstraints | 0 .../system/fvSchemes | 0 .../system/fvSolution | 0 .../system/inlets_outlets.json | 0 .../system/mesh.json | 0 .../system/mixers.json | 0 .../system/setFieldsDict | 0 .../writeGlobalVars.py | 0 setup.py | 1 + tests/__init__.py | 0 tests/preprocess/test_case_gen.py | 7 +++---- 60 files changed, 5 insertions(+), 5 deletions(-) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict (100%) rename bird/preprocess/{data => data_case_gen}/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py (100%) create mode 100644 tests/__init__.py diff --git a/bird/__init__.py b/bird/__init__.py index 986a535f..6eb11de8 100644 --- a/bird/__init__.py +++ b/bird/__init__.py @@ -35,5 +35,5 @@ ) BIRD_EARLY_PRED_DATA_DIR = os.path.join(BIRD_POST_DIR, "data_early") BIRD_KLA_DATA_DIR = os.path.join(BIRD_POST_DIR, "data_kla") -BIRD_CASEGEN_DATA_DIR = os.path.join(BIRD_PRE_DIR, "data") +BIRD_CASE_GEN_DATA_DIR = os.path.join(BIRD_PRE_DIR, "data_case_gen") BIRD_INV_DIR = os.path.join(BIRD_DIR, "inverse_modeling") diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict diff --git a/bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py similarity index 100% rename from bird/preprocess/data/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py rename to bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py diff --git a/setup.py b/setup.py index 4eaa7bd2..740f8673 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ "*.dat", "data_conditional_mean", "data_kla", + "data_case_gen", ] }, extras_require={ diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 8f2cc03d..488ba263 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -4,10 +4,9 @@ import numpy as np -from bird import BIRD_CASEGEN_DATA_DIR from bird.preprocess.json_gen.design_io import * from bird.preprocess.json_gen.generate_designs import * - +from bird import BIRD_CASE_GEN_DATA_DIR def test_continuous_loop(): @@ -17,7 +16,7 @@ def test_continuous_loop(): vvm=0.4, study_folder=".", template_folder=os.path.join( - BIRD_CASEGEN_DATA_DIR, + BIRD_CASE_GEN_DATA_DIR, "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", ), ) @@ -81,7 +80,7 @@ def random_sample(branches_com, branchcom_spots, config_dict={}): constantD=True, study_folder=study_folder, template_folder=os.path.join( - BIRD_CASEGEN_DATA_DIR, + BIRD_CASE_GEN_DATA_DIR, "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", ), ) From aa1d8b97ecfc6941948ce50f44bf4dd9eff460a3 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:36:27 -0600 Subject: [PATCH 12/86] trigger ci --- .github/workflows/ci.yml | 2 +- tests/preprocess/test_case_gen.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 852dc071..8a2d9cb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - 'docs/**' pull_request: - branches: [main] + branches: [main, movesparg] paths-ignore: - '*.md' - '*.rst' diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 488ba263..563a6ba3 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -4,9 +4,10 @@ import numpy as np +from bird import BIRD_CASE_GEN_DATA_DIR from bird.preprocess.json_gen.design_io import * from bird.preprocess.json_gen.generate_designs import * -from bird import BIRD_CASE_GEN_DATA_DIR + def test_continuous_loop(): From 29fb8a1a158d1e6176847662c0868d3d3480147e Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:46:27 -0600 Subject: [PATCH 13/86] explicit inclusion of constant --- .github/workflows/ci.yml | 199 ++++++++++++++++++++------------------- setup.py | 2 + 2 files changed, 102 insertions(+), 99 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a2d9cb2..ff939736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10'] + #python-version: ['3.10', '3.11', '3.12'] os: ['ubuntu-latest', 'macos-latest'] defaults: run: @@ -77,103 +78,103 @@ jobs: run: | pytest tests/postprocess - Test-pypi-Bird: - name: Test-pypi-BiRD (${{ matrix.python-version }}, ${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: ['3.10'] - os: ['ubuntu-latest'] - defaults: - run: - working-directory: ${{github.workspace}} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{matrix.python-version}} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install nrel-bird - pip install pytest - - name: Test - run: pytest . + #Test-pypi-Bird: + # name: Test-pypi-BiRD (${{ matrix.python-version }}, ${{ matrix.os }}) + # runs-on: ${{ matrix.os }} + # strategy: + # fail-fast: false + # matrix: + # python-version: ['3.10'] + # os: ['ubuntu-latest'] + # defaults: + # run: + # working-directory: ${{github.workspace}} + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/setup-python@v5 + # with: + # python-version: ${{matrix.python-version}} + # - name: Install dependencies + # run: | + # pip install --upgrade pip + # pip install nrel-bird + # pip install pytest + # - name: Test + # run: pytest . - Test-OF: - name: Test-OF (${{ matrix.python-version }}, ${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: ['3.10'] - os: ['ubuntu-22.04'] - defaults: - run: - working-directory: ${{github.workspace}} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{matrix.python-version}} - - uses: gerlero/setup-openfoam@v1 - with: - openfoam-version: 9 - - name: Install dependencies - run: | - pip install --upgrade pip - pip install . - - name: Compile solver - run: | - cd OFsolvers/birdmultiphaseEulerFoam - export WM_COMPILE_OPTION=Debug - ./Allwmake - cd ../../ - - name: Run deckwer17 PBE - run: | - cd experimental_cases/deckwer17 - bash run.sh - cd ../../ - - name: Run deckwer17 constantD - run: | - cd experimental_cases/deckwer17 - cp constant/phaseProperties_constantd constant/phaseProperties - bash run.sh - cd ../../ - - name: Run deckwer19 PBE - run: | - cd experimental_cases/deckwer19 - bash run.sh - cd ../../ - - name: Run side sparger tutorial - run: | - cd tutorial_cases/side_sparger - bash run.sh - cd ../../ - - name: Run bubble column tutorial - run: | - cd tutorial_cases/bubble_column_20L - bash run.sh - cd ../../ - - name: Run stirred-tank tutorial - run: | - cd tutorial_cases/stirred_tank - bash run.sh - cd ../../ - - name: Run reactive loop reactor tutorial - run: | - cd tutorial_cases/loop_reactor_reacting - bash run.sh - cd ../../ - - name: Run mixing loop reactor tutorial - run: | - cd tutorial_cases/loop_reactor_mixing - bash run.sh - cd ../../ - - name: Run airlift reactor tutorial - run: | - cd tutorial_cases/airlift_40m - bash run.sh - cd ../../ + #Test-OF: + # name: Test-OF (${{ matrix.python-version }}, ${{ matrix.os }}) + # runs-on: ${{ matrix.os }} + # strategy: + # fail-fast: false + # matrix: + # python-version: ['3.10'] + # os: ['ubuntu-22.04'] + # defaults: + # run: + # working-directory: ${{github.workspace}} + # steps: + # - uses: actions/checkout@v4 + # - uses: actions/setup-python@v5 + # with: + # python-version: ${{matrix.python-version}} + # - uses: gerlero/setup-openfoam@v1 + # with: + # openfoam-version: 9 + # - name: Install dependencies + # run: | + # pip install --upgrade pip + # pip install . + # - name: Compile solver + # run: | + # cd OFsolvers/birdmultiphaseEulerFoam + # export WM_COMPILE_OPTION=Debug + # ./Allwmake + # cd ../../ + # - name: Run deckwer17 PBE + # run: | + # cd experimental_cases/deckwer17 + # bash run.sh + # cd ../../ + # - name: Run deckwer17 constantD + # run: | + # cd experimental_cases/deckwer17 + # cp constant/phaseProperties_constantd constant/phaseProperties + # bash run.sh + # cd ../../ + # - name: Run deckwer19 PBE + # run: | + # cd experimental_cases/deckwer19 + # bash run.sh + # cd ../../ + # - name: Run side sparger tutorial + # run: | + # cd tutorial_cases/side_sparger + # bash run.sh + # cd ../../ + # - name: Run bubble column tutorial + # run: | + # cd tutorial_cases/bubble_column_20L + # bash run.sh + # cd ../../ + # - name: Run stirred-tank tutorial + # run: | + # cd tutorial_cases/stirred_tank + # bash run.sh + # cd ../../ + # - name: Run reactive loop reactor tutorial + # run: | + # cd tutorial_cases/loop_reactor_reacting + # bash run.sh + # cd ../../ + # - name: Run mixing loop reactor tutorial + # run: | + # cd tutorial_cases/loop_reactor_mixing + # bash run.sh + # cd ../../ + # - name: Run airlift reactor tutorial + # run: | + # cd tutorial_cases/airlift_40m + # bash run.sh + # cd ../../ diff --git a/setup.py b/setup.py index 740f8673..e299df8c 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,8 @@ "data_conditional_mean", "data_kla", "data_case_gen", + "constant", + "0.orig" ] }, extras_require={ From 5b53e0dd8a3c512fd59c3be723e31c0f341c41c2 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:53:06 -0600 Subject: [PATCH 14/86] print what github action actually copied --- bird/preprocess/json_gen/generate_designs.py | 1 + setup.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index 099959f5..956accfd 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -494,6 +494,7 @@ def generate_single_scaledup_reactor_sparger_cases( f"{template_folder}", os.path.join(f"{study_folder}", f"Sim_{sim_id}"), ) + print("Sim folder contents:", os.listdir(os.path.join(study_folder, f"Sim_{sim_id}"))) bc_dict = {} bc_dict["inlets"] = [] diff --git a/setup.py b/setup.py index e299df8c..1c6dbe38 100644 --- a/setup.py +++ b/setup.py @@ -41,9 +41,7 @@ "*.dat", "data_conditional_mean", "data_kla", - "data_case_gen", - "constant", - "0.orig" + "data_case_gen/**/*", ] }, extras_require={ From ff0185eb3b585c0fd565120bcad2bcc38abdec44 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:56:52 -0600 Subject: [PATCH 15/86] revert log --- bird/preprocess/json_gen/generate_designs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index 956accfd..099959f5 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -494,7 +494,6 @@ def generate_single_scaledup_reactor_sparger_cases( f"{template_folder}", os.path.join(f"{study_folder}", f"Sim_{sim_id}"), ) - print("Sim folder contents:", os.listdir(os.path.join(study_folder, f"Sim_{sim_id}"))) bc_dict = {} bc_dict["inlets"] = [] From db722925328a19bbcba136f15949da33f100370e Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 11:58:37 -0600 Subject: [PATCH 16/86] all tests --- .github/workflows/ci.yml | 199 +++++++++++++++++++-------------------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff939736..8a2d9cb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,8 +52,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10'] - #python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12'] os: ['ubuntu-latest', 'macos-latest'] defaults: run: @@ -78,103 +77,103 @@ jobs: run: | pytest tests/postprocess - #Test-pypi-Bird: - # name: Test-pypi-BiRD (${{ matrix.python-version }}, ${{ matrix.os }}) - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # python-version: ['3.10'] - # os: ['ubuntu-latest'] - # defaults: - # run: - # working-directory: ${{github.workspace}} - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-python@v5 - # with: - # python-version: ${{matrix.python-version}} - # - name: Install dependencies - # run: | - # pip install --upgrade pip - # pip install nrel-bird - # pip install pytest - # - name: Test - # run: pytest . + Test-pypi-Bird: + name: Test-pypi-BiRD (${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ['3.10'] + os: ['ubuntu-latest'] + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{matrix.python-version}} + - name: Install dependencies + run: | + pip install --upgrade pip + pip install nrel-bird + pip install pytest + - name: Test + run: pytest . - #Test-OF: - # name: Test-OF (${{ matrix.python-version }}, ${{ matrix.os }}) - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # python-version: ['3.10'] - # os: ['ubuntu-22.04'] - # defaults: - # run: - # working-directory: ${{github.workspace}} - # steps: - # - uses: actions/checkout@v4 - # - uses: actions/setup-python@v5 - # with: - # python-version: ${{matrix.python-version}} - # - uses: gerlero/setup-openfoam@v1 - # with: - # openfoam-version: 9 - # - name: Install dependencies - # run: | - # pip install --upgrade pip - # pip install . - # - name: Compile solver - # run: | - # cd OFsolvers/birdmultiphaseEulerFoam - # export WM_COMPILE_OPTION=Debug - # ./Allwmake - # cd ../../ - # - name: Run deckwer17 PBE - # run: | - # cd experimental_cases/deckwer17 - # bash run.sh - # cd ../../ - # - name: Run deckwer17 constantD - # run: | - # cd experimental_cases/deckwer17 - # cp constant/phaseProperties_constantd constant/phaseProperties - # bash run.sh - # cd ../../ - # - name: Run deckwer19 PBE - # run: | - # cd experimental_cases/deckwer19 - # bash run.sh - # cd ../../ - # - name: Run side sparger tutorial - # run: | - # cd tutorial_cases/side_sparger - # bash run.sh - # cd ../../ - # - name: Run bubble column tutorial - # run: | - # cd tutorial_cases/bubble_column_20L - # bash run.sh - # cd ../../ - # - name: Run stirred-tank tutorial - # run: | - # cd tutorial_cases/stirred_tank - # bash run.sh - # cd ../../ - # - name: Run reactive loop reactor tutorial - # run: | - # cd tutorial_cases/loop_reactor_reacting - # bash run.sh - # cd ../../ - # - name: Run mixing loop reactor tutorial - # run: | - # cd tutorial_cases/loop_reactor_mixing - # bash run.sh - # cd ../../ - # - name: Run airlift reactor tutorial - # run: | - # cd tutorial_cases/airlift_40m - # bash run.sh - # cd ../../ + Test-OF: + name: Test-OF (${{ matrix.python-version }}, ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ['3.10'] + os: ['ubuntu-22.04'] + defaults: + run: + working-directory: ${{github.workspace}} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{matrix.python-version}} + - uses: gerlero/setup-openfoam@v1 + with: + openfoam-version: 9 + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + - name: Compile solver + run: | + cd OFsolvers/birdmultiphaseEulerFoam + export WM_COMPILE_OPTION=Debug + ./Allwmake + cd ../../ + - name: Run deckwer17 PBE + run: | + cd experimental_cases/deckwer17 + bash run.sh + cd ../../ + - name: Run deckwer17 constantD + run: | + cd experimental_cases/deckwer17 + cp constant/phaseProperties_constantd constant/phaseProperties + bash run.sh + cd ../../ + - name: Run deckwer19 PBE + run: | + cd experimental_cases/deckwer19 + bash run.sh + cd ../../ + - name: Run side sparger tutorial + run: | + cd tutorial_cases/side_sparger + bash run.sh + cd ../../ + - name: Run bubble column tutorial + run: | + cd tutorial_cases/bubble_column_20L + bash run.sh + cd ../../ + - name: Run stirred-tank tutorial + run: | + cd tutorial_cases/stirred_tank + bash run.sh + cd ../../ + - name: Run reactive loop reactor tutorial + run: | + cd tutorial_cases/loop_reactor_reacting + bash run.sh + cd ../../ + - name: Run mixing loop reactor tutorial + run: | + cd tutorial_cases/loop_reactor_mixing + bash run.sh + cd ../../ + - name: Run airlift reactor tutorial + run: | + cd tutorial_cases/airlift_40m + bash run.sh + cd ../../ From 8e761f8f0003b5f5ec2472101931b243084b913b Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 23 May 2025 12:03:18 -0600 Subject: [PATCH 17/86] prepare for merge --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a2d9cb2..852dc071 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - 'docs/**' pull_request: - branches: [main, movesparg] + branches: [main] paths-ignore: - '*.md' - '*.rst' From a56282d01c51a33bb2e25b6334430b69c313f1b7 Mon Sep 17 00:00:00 2001 From: jainprana Date: Fri, 30 May 2025 00:12:44 -0400 Subject: [PATCH 18/86] Create temp --- bird/postprocess/SA_optimization/temp | 1 + 1 file changed, 1 insertion(+) create mode 100644 bird/postprocess/SA_optimization/temp diff --git a/bird/postprocess/SA_optimization/temp b/bird/postprocess/SA_optimization/temp new file mode 100644 index 00000000..78981922 --- /dev/null +++ b/bird/postprocess/SA_optimization/temp @@ -0,0 +1 @@ +a From 65e172e98e5a531427db18d319841329bc7a262d Mon Sep 17 00:00:00 2001 From: jainprana Date: Fri, 30 May 2025 00:14:25 -0400 Subject: [PATCH 19/86] Add files via upload --- bird/postprocess/SA_optimization/README.md | 47 +++ .../Xdata_study_scaleup_0_4vvm_3000W.csv | 185 ++++++++++++ bird/postprocess/SA_optimization/get_csv.py | 39 +++ .../SA_optimization/get_optimal.py | 282 ++++++++++++++++++ .../get_optimal_with_constraint.py | 263 ++++++++++++++++ bird/postprocess/SA_optimization/results.pkl | Bin 0 -> 5208 bytes .../ydata_study_scaleup_0_4vvm_3000W.csv | 185 ++++++++++++ 7 files changed, 1001 insertions(+) create mode 100644 bird/postprocess/SA_optimization/README.md create mode 100644 bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv create mode 100644 bird/postprocess/SA_optimization/get_csv.py create mode 100644 bird/postprocess/SA_optimization/get_optimal.py create mode 100644 bird/postprocess/SA_optimization/get_optimal_with_constraint.py create mode 100644 bird/postprocess/SA_optimization/results.pkl create mode 100644 bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv diff --git a/bird/postprocess/SA_optimization/README.md b/bird/postprocess/SA_optimization/README.md new file mode 100644 index 00000000..37309e9b --- /dev/null +++ b/bird/postprocess/SA_optimization/README.md @@ -0,0 +1,47 @@ +# Surrogate-Based Optimization with Simulated Annealing + +This project implements a complete framework for surrogate-based optimization using Simulated Annealing (SA). It supports three surrogate models: + - Radial Basis Function Interpolator ('rbf') + - Random Forest ('rf') + - Neural Network ('nn') + +The SA optimizer operates on discrete feature values (0,1,2) with an option to restrict the number of spargers (1) to a fixed value (max_saprgers). + +- Preprocessing the data ('get_csv.py') + - reads the 'configs.pkl' and 'results.pkl' files from a study + - saves the configuration in 'Xdata_{study_name}.csv' file + - save the qoi and qoi_error in 'ydata_{study_name}.csv' file + +- Surrogate modeling and optimization ('get_optimal.py'/'get_optimal_with_constraints.py') + - run_optimization(...) function sets up the surrogate-based optimization: + - Inputs: + - X: read from 'Xdata_{study_name}.csv' file + - y: read from 'ydata_{study_name}.csv' file + - model_type: type of surrogate model (default = 'rbf') + - model_type = 'rbf': Radial Basis Function + - model_type = 'rf': Random Forest + - model_type = 'nn': Neural Network + - max_spargers: maximum number of spargers (only in 'get_optimal_with_constraints.py') (default = 8) + - n_runs: number of bootstrap runs (default = 10) + - max_iters: maximum number of iterations of SA (default = 1000) + - bootstrap_size: sample size of each boostrap (default = 100) + - For each bootstrap run, the model hyperparameters are tuned using 5-fold cross validation. + - The simulated_annealing_surrogate(...) function runs the optimization: + - If SA is too slow or fails to converge, you can change the following parameters: + - temp: maximum temperature of SA. Controls exploration. + - alpha: the rate at which temperature changes every iteration. + - It returen the optimal solutions along with the optimization logs which are used to make plots in postprocessing. + - Once the optimization is done the best solution is saved in a csv file and the following plots are made: + - Mean-CI plot of the objective function (qoi) + - Mean-CI plot of the distance of an iterate from the optimal solution (this is not done in 'get_optimal_with_constraints.py'). + +Python dependencies: +- numpy +- pandas +- scikit-learn +- scipy +- optuna +- matplotlib +- warnings +- random +- tensorflow diff --git a/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv b/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv new file mode 100644 index 00000000..771f8ccf --- /dev/null +++ b/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv @@ -0,0 +1,185 @@ +0,1,0,1,0,0,2,2,2,2,0 +0,2,2,2,2,2,0,1,0,0,0 +2,1,0,1,2,0,0,1,1,0,1 +0,1,1,2,0,1,1,2,1,1,0 +0,2,2,2,2,1,1,1,0,2,0 +0,1,2,2,1,1,0,2,2,1,0 +2,0,2,1,0,2,1,1,1,2,2 +0,2,1,1,0,0,2,0,0,1,0 +0,0,0,1,1,1,1,0,2,2,2 +0,2,0,2,1,0,2,2,1,2,1 +2,2,1,1,0,2,2,2,0,1,1 +0,1,0,1,2,0,2,1,1,1,1 +2,1,1,0,0,1,2,1,1,2,1 +1,2,0,1,1,2,2,2,2,1,0 +1,1,0,0,0,2,2,0,2,1,2 +1,1,0,1,0,0,2,0,2,0,1 +0,1,2,2,2,0,1,2,2,1,0 +2,1,2,0,1,1,2,0,1,0,0 +0,1,1,0,0,0,2,2,2,2,1 +2,2,0,1,0,2,2,2,1,0,1 +0,2,0,2,1,2,2,0,0,1,2 +0,1,2,1,1,2,0,2,0,0,2 +1,1,0,1,0,2,1,2,1,1,0 +2,2,2,1,1,0,2,2,1,2,0 +2,2,1,1,1,2,1,2,1,1,0 +1,1,2,1,1,1,1,0,2,1,1 +0,0,2,1,2,0,1,0,0,0,1 +0,0,2,0,1,0,2,0,0,1,2 +0,1,0,1,1,0,2,1,0,1,0 +1,2,2,1,0,2,2,1,0,1,2 +0,0,1,2,1,1,1,2,2,1,2 +0,0,1,1,2,0,1,0,2,2,1 +2,0,0,2,1,0,2,0,0,2,1 +0,2,0,1,0,1,1,2,0,2,1 +0,1,2,2,1,0,1,0,2,2,1 +2,1,2,2,1,1,0,1,0,2,0 +1,2,2,2,1,2,2,0,2,1,0 +0,0,0,2,1,0,0,2,2,1,1 +1,0,0,0,1,0,0,2,2,1,1 +2,2,0,0,0,0,2,2,2,2,1 +2,1,2,1,2,2,1,0,1,2,1 +2,2,1,2,2,0,1,1,0,2,2 +2,1,0,2,2,2,1,1,0,1,1 +1,0,0,1,2,2,1,1,2,0,0 +1,2,2,0,2,0,1,1,0,0,2 +1,1,0,2,1,0,2,0,0,1,2 +1,2,0,1,1,0,1,1,0,2,0 +1,2,0,1,1,0,2,2,2,2,2 +1,2,1,2,2,1,1,2,2,0,1 +0,1,1,0,0,1,0,1,1,2,2 +0,0,0,1,0,0,0,0,1,0,2 +0,1,1,1,0,2,2,2,0,1,0 +1,0,1,0,1,1,2,0,2,0,2 +0,1,0,1,2,0,1,1,2,2,0 +2,0,2,1,2,1,0,2,2,0,2 +0,2,1,1,2,1,2,2,2,2,0 +0,1,0,2,2,0,0,0,2,2,2 +2,0,1,1,0,2,1,1,1,1,2 +2,2,1,1,1,2,1,1,2,2,0 +0,0,0,0,0,1,0,2,2,0,0 +0,0,0,2,2,1,0,2,0,1,1 +1,2,0,0,2,2,2,2,0,1,0 +1,1,2,0,2,2,1,0,1,0,0 +1,0,0,2,0,1,0,0,2,0,0 +2,1,2,1,0,1,2,1,1,2,2 +1,0,2,0,2,1,1,2,0,1,2 +1,0,2,0,2,0,0,0,2,2,1 +2,0,2,1,1,2,0,0,2,1,0 +1,0,1,0,2,0,2,2,1,0,2 +0,1,2,1,0,0,0,2,1,1,2 +0,0,1,0,2,1,0,0,1,0,2 +1,0,0,0,1,2,1,2,0,2,1 +0,2,1,0,0,1,1,1,0,0,0 +2,0,2,1,0,0,0,0,0,1,1 +1,0,0,1,1,2,2,1,2,1,0 +2,2,1,2,2,0,2,1,1,2,2 +0,2,2,1,2,0,1,1,2,0,1 +1,0,2,1,1,2,0,0,1,0,0 +1,0,1,1,0,1,1,2,1,1,1 +1,1,0,1,1,2,2,0,0,2,0 +1,0,2,2,2,1,1,1,1,2,1 +1,1,2,1,0,0,1,1,2,2,1 +0,1,0,0,2,1,0,1,2,0,2 +0,0,0,2,2,1,2,0,2,1,1 +2,0,2,1,1,1,0,0,0,0,1 +2,0,0,1,2,1,1,1,0,1,1 +1,0,1,0,0,0,0,1,1,0,0 +0,1,2,1,2,0,2,0,1,2,1 +0,0,2,1,0,0,2,1,1,2,1 +0,2,1,1,1,0,2,0,2,0,2 +2,1,1,2,0,0,0,1,1,0,0 +2,1,1,0,2,1,0,0,0,1,0 +0,2,1,0,0,1,2,2,2,0,2 +1,0,2,0,1,0,2,0,2,2,0 +1,1,1,1,2,2,1,0,1,0,0 +1,0,1,0,2,2,1,2,0,1,0 +2,0,2,2,1,2,1,0,2,0,2 +2,1,1,1,0,0,0,1,0,1,0 +1,1,1,1,2,1,2,1,0,0,2 +2,0,2,2,2,1,1,2,0,1,1 +0,1,1,2,2,1,2,2,1,0,0 +0,0,1,1,2,2,0,1,1,0,2 +2,2,2,1,0,0,1,1,2,2,1 +2,0,1,2,0,1,0,1,1,2,2 +0,1,0,2,2,1,2,1,1,1,0 +1,1,2,1,1,0,2,0,1,0,2 +2,1,0,0,1,2,2,2,2,1,0 +2,2,0,0,1,1,1,1,1,0,0 +2,0,1,1,1,1,1,0,1,2,0 +1,2,2,1,2,1,2,2,2,0,1 +1,0,2,0,1,0,1,1,0,0,0 +2,1,0,1,0,1,0,1,2,2,2 +0,1,2,2,0,0,1,1,2,2,1 +1,2,1,1,1,1,1,2,0,1,0 +2,2,1,1,2,2,1,2,0,2,2 +2,2,0,2,0,1,1,0,0,0,2 +1,2,2,0,0,2,0,0,1,1,2 +2,1,2,2,1,0,0,2,1,0,1 +1,1,2,0,0,2,2,0,0,0,2 +0,2,0,0,2,0,2,0,1,2,2 +0,1,0,0,0,1,2,1,0,0,2 +1,2,0,2,2,0,2,2,1,1,1 +2,0,1,0,2,1,1,1,2,2,2 +0,0,1,0,2,0,0,1,2,1,2 +0,1,0,1,2,1,2,2,0,0,2 +2,2,2,2,0,1,1,1,1,0,2 +2,2,2,2,0,1,1,2,0,1,0 +1,2,1,1,0,1,0,0,2,0,2 +0,1,0,0,1,0,2,1,0,0,0 +2,2,1,1,2,2,2,2,0,2,2 +0,2,2,0,1,2,2,0,2,0,0 +0,1,0,1,2,1,1,1,2,2,2 +0,1,2,2,1,2,0,1,0,0,2 +1,0,1,0,0,2,1,1,0,2,1 +2,0,1,0,0,0,1,1,0,0,0 +2,2,2,1,0,2,1,2,0,0,2 +2,0,1,0,2,2,0,2,0,2,2 +0,0,0,1,2,2,2,0,0,1,2 +0,2,0,1,2,0,1,1,0,2,1 +0,1,2,0,2,0,1,1,2,0,1 +1,1,1,0,0,1,2,2,1,1,1 +2,1,2,0,0,1,0,0,0,2,2 +2,1,2,2,2,1,2,0,0,2,2 +0,0,0,2,0,1,1,1,0,0,1 +0,2,0,2,2,2,2,2,0,1,1 +2,2,2,0,1,1,1,0,1,0,0 +1,0,1,1,2,2,2,1,0,0,0 +2,1,2,2,1,1,0,0,1,1,2 +0,1,2,1,2,1,0,0,1,1,2 +1,2,2,0,1,2,2,0,1,1,0 +0,2,2,0,0,1,0,2,2,2,0 +1,2,2,2,1,2,2,2,0,1,0 +1,0,2,1,2,2,2,2,1,1,2 +2,0,2,0,1,1,1,2,1,2,1 +0,1,0,1,0,1,1,2,1,2,1 +2,2,0,1,2,2,2,2,0,2,2 +1,0,2,2,0,1,1,0,0,0,1 +0,0,0,1,2,0,2,1,2,0,2 +0,0,0,1,2,2,2,0,1,1,0 +0,1,0,1,1,0,0,1,1,0,1 +2,0,2,0,1,0,0,2,1,2,2 +1,0,1,2,2,1,0,0,1,2,0 +2,0,2,0,1,0,0,1,2,1,0 +1,0,1,0,0,1,0,0,2,1,0 +2,1,2,2,0,1,1,2,2,1,2 +2,1,2,1,1,0,1,0,1,2,2 +1,0,0,2,2,0,1,1,1,1,0 +1,0,2,1,2,0,2,2,2,2,2 +1,0,0,0,2,1,2,0,0,0,2 +1,0,0,2,2,2,0,1,2,0,0 +0,1,1,0,2,2,1,2,1,1,0 +1,2,2,2,2,0,1,0,2,2,2 +1,0,1,1,2,1,2,0,1,1,1 +1,1,0,2,2,1,1,1,2,2,1 +2,2,1,0,2,2,2,0,1,2,1 +1,0,0,0,1,2,0,1,2,0,1 +0,1,2,0,2,2,1,1,0,1,2 +1,2,1,2,2,0,2,0,0,0,2 +0,0,0,1,2,2,1,0,1,0,1 +2,0,1,0,1,2,0,0,2,1,1 +1,0,0,2,2,0,2,1,0,0,1 +1,0,1,1,0,2,2,2,1,2,0 +0,0,1,1,0,2,1,1,0,1,0 +2,2,1,0,2,0,0,1,0,1,1 +1,1,0,1,2,0,1,2,2,0,0 diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py new file mode 100644 index 00000000..cc76fc14 --- /dev/null +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -0,0 +1,39 @@ +import pickle5 as pkl +import os +import numpy as np +import csv + +def get_config_result(config_fold): + with open(os.path.join(config_fold, "results.pkl"), "rb") as f: + results = pkl.load(f) + with open(os.path.join(config_fold, "configs.pkl"), "rb") as f: + configs = pkl.load(f) + Xdata = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], np.int64) + count = 0 + # Save data into CSV files + xfname = "Xdata_"+config_fold+".csv" + yfname = "ydata_"+config_fold+".csv" + with open(xfname,'w',newline='') as csvfile: + writer = csv.writer(csvfile) + for sims in results: + b0 = configs[sims][0] + b1 = configs[sims][1] + b2 = configs[sims][2] + raw_data = np.concatenate((b0,b1,b2), axis = None) + writer.writerow(raw_data) + + with open(yfname,'w',newline='') as csvfile: + writer = csv.writer(csvfile) + for sims in results: + q0 = results[sims]["qoi"] + q1 = results[sims]["qoi_err"] + y_data = np.concatenate((q0,q1), axis = None) + writer.writerow(y_data) + +studies = { + "study_scaleup_0_4vvm_3000W" : r"0.0036$m^3$ 0.4vvm 0W" + } + + +for study in studies: + get_config_result(study) diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py new file mode 100644 index 00000000..ca960fd8 --- /dev/null +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -0,0 +1,282 @@ +import numpy as np +import pandas as pd +import random +import optuna +from sklearn.ensemble import RandomForestRegressor +from sklearn.preprocessing import OneHotEncoder +from sklearn.model_selection import KFold, cross_val_score +from sklearn.metrics import mean_squared_error +from scipy.interpolate import RBFInterpolator +from tensorflow.keras.models import Sequential +from tensorflow.keras.layers import Dense +import matplotlib.pyplot as plt +import warnings + +warningsl.filterwarnings("ignore") + +def tune_rbf(X,y): + # Tune the RBFInterpolator + # kernel - "multiquadric" (Can try other kernels) + def objective(trial): + epsilon = trial.suggest_float("epsilon",0.1,10.0, log=False) + try: + kf = KFold(n_splits=5,shuffle=True,random_state=42) + return np.mean([ + mean_squared_error( + y[test], + RBFInterpolator(X[train],y[train],epsilon=epsilon,kernel="multiquadric")(X[test]) + ) + for train, test in kf.split(X) + ]) + except: + return float("inf") + study = optuna.create_study(direction="minimize") + study.optimize(objective,n_trials=20) + return study.best_params + +def tune_rf(X,y): + # Tune the Random Forest + def objective(trial): + model = RandomForestRegressor( + n_estimators=trial.suggest_int("n_estimators",50,200), + max_depth = trial.suggest_int("max_depth",3,10), + min_samples_leaf = trial.suggest_int("min_samples_leaf",1,10), + random_state = 42 + ) + scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') + return -np.mean(scores) + study = optuna.create_study(direction="minimize") + study.optimize(objective,n_trials=20) + return study.best_params + +def tune_nn(X_encoded,y): + # Tune the Neural Network + def objective(trial): + units = trial.suggest_int("n_units",16,128) + layers = trial.suggest_int("n_layers",1,3) + kf = KFold(n_splits=5,shuffle=True,random_state=42) + mses=[] + for train, test in kf.split(X_encoded): + model = Sequential() + model.add(Dense(units, activation='relu', input_shape=(X_encoded.shape[1],))) + for _ in range(layers-1): + model.add(Dense(units,activation='relu')) + model.add(Dense(1)) + model.compile(optimizer='adam',loss='mse') + model.fit(X_encoded[train],y[train],epochs=100,batch_size=16,verbose=0) + mses.append(mean_squared_error(y[test],model.predict(X_encoded[test]).flatten())) + return np.mean(mses) + study = optuna.create_study(direction="minimize") + study.optimize(objective, n_trials=20) + return study.best_params + + +class Surrogatewrapper: + # Wrapper that builds the surrogate model and predicts the QOI value for given X + def __init__(self,model_type,X,y,params): + # Inputs: + # model_type = 'rbf' for RBFInterpolator + # = 'rf' for Random Forest + # = 'nn' for Neural Network + # X,y = the raw data + # params = tune parameter of the surrogate model + + self.model_type = model_type + self.encoder = None + + if model_type == 'rbf': + self.model = RBFInterpolator(X,y,kernel='multiquadric',epsilon=params["epsilon"]) + elif model_type == 'rf': + self.model = RandomForestRegressor(**params, random_state=42) + self.model.fit(X,y) + elif model_type == 'nn': + self.encoder = OneHotEncoder(sparse_output=False) + X_encoded = self.encoder.fit_transform(X) + self.model = self._build_nn(X_encoded.shape[1],params["n_units"],params["n_layers"]) + self.model.fit(X_encoded,y,epochs=100,batch_size=16,verbose=0) + + + def _build_nn(self,input_dim, units, layers): + # Builds the neural network + model = Sequential() + model.add(Dense(units, activation='relu',input_shape=(input_dim,))) + for _ in range(layers-1): + model.add(Dense(units,activation='relu')) + model.add(Dense(1)) + model.compile(optimizer='adam', loss='mse') + return model + + def predict(self,X): + X = X.reshape(1,-1) + if self.model_type == 'nn': + X = self.encoder.transform(X) + return float(self.model.predict(X)[0]) + elif self.model_type == 'rf': + return float(self.model.predict(X)[0]) + else: + return float(self.model(X)[0]) + +def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,temp=10.0,alpha=0.95): + # Runs the Simulated Annealing (SA) and reports the resuts + # Inputs: + # surrogate = tuned surrogate model + # dim = dimension of the problem + # max_iters = maximum number of iterations for SA + # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) + # = max temperature. It controls the exploration rate of SA + # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) + # = cooling rate. It determines how quickly temp decays. + + # Outputs: + # x_best = optimal solution + # y_best = optimal objective function value + # trace = optimization log. Updates at the end of each iteration. + # trace_x = tracks how X changes during each iteration. + + # generate random starting point + x_curr = np.random.randint(0,3,size=dim) + + y_curr = surrogate.predict(x_curr) + x_best, y_best = x_curr.copy(), y_curr + + trace = [(0,y_best)] + trace_x = [x_curr.copy()] + + for i in range(max_iters): + # Optimization loop + + x_new = x_curr.copy() + idx = random.randint(0,dim-1) + # perturb a random dimension to get new x + x_new[idx] = (x_new[idx] + random.choice([-1,1])) % 3 + y_new = surrogate.predict(x_new) + + delta = y_new - y_curr + if delta < 0 or np.random.rand() < np.exp(-delta / temp): + x_curr, y_curr = x_new, y_new + if y_curr < y_best: + x_best, y_best = x_new.copy(), y_new + + trace.append((i,y_best)) + trace_x.append(x_curr.copy()) + temp *= alpha + + return x_best, y_best, trace, trace_x + +def run_optimization(X,y,model_type = 'rbf',n_runs=10,max_iters = 1000, bootstrap_size=100): + # Bootstraps data, runs optimization and postprocesses the resutls. + # Inputs: + # X,y = the raw data + # model_type = 'rbf' for RBFInterpolator + # = 'rf' for Random Forest + # = 'nn' for Neural Network + # n_runs = number of bootstraps + # max_iters = maximum number of SA iterations + # bootstrap_size = size of bootstrap samples + + all_x = [] + all_y = [] + all_traces = np.zeros((n_runs, max_iters + 1)) + all_trace_x = [] + rng = np.random.default_rng(42) + # Bootstrap the data + bootstrap_idxs = [rng.choice(len(X), size = bootstrap_size, replace = False) for _ in range(n_runs)] + + i = 0 + for idxs in bootstrap_idxs: + # Tune the model for each bootstrap + X_sub, y_sub = X[idxs], y[idxs] + if model_type == "rbf": + params = tune_rbf(X_sub,y_sub) + elif model_type == "rf": + params = tune_rf(X_sub,y_sub) + elif model_type == "nn": + encoder = OneHotEncoder(sparse_output=False) + X_encoded = encoder.fit_transform(X_sub) + params = tune_nn(X_encoded,y_sub) + else: + raise ValueError("Invalid model_type") + + # build the surrogate model + surrogate = Surrogatewrapper(model_type,X_sub,y_sub,params) + + # run optimization + x_best, y_best, trace, trace_x = simulated_annealing_surrogate(surrogate, dim=X.shape[1], max_iters=max_iters) + + trace_y = [y for _, y in trace] + all_traces[i,:] = trace_y + all_x.append(x_best) + all_y.append(y_best) + all_trace_x.append(trace_x) + i = i + 1 + + # Compute the best y across all the bootstraps + best_index = np.argmin(all_y) + best_x = all_x[best_index] + best_y = all_y[best_index] + # Save the best solution + df = pd.DataFrame([{ + **{f"x{i}": best_x[i] for i in range(len(best_x))}, + "best_y": best_y + }]) + df.to_csv(f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}.csv", index=False) + + + # Make the mean-CI plot for the objective function and save it + mean_trace = np.mean(-1*all_traces,axis=0) + std_trace = np.std(all_traces,axis=0) + lower_bound = mean_trace - 1.96* std_trace / np.sqrt(n_runs) + upper_bound = mean_trace + 1.96* std_trace / np.sqrt(n_runs) + iterations = np.arange(max_iters + 1) + + plt.figure(figsize=(8,4)) + plt.plot(iterations,mean_trace,label=f"Mean Convergence {model_type.upper()}", color = "blue") + plt.fill_between(iterations, lower_bound, upper_bound, color = "blue", alpha = 0.3, label = "95% CI") + plt.xlabel("Iteration") + plt.ylabel("Best Surrogate-Predicted Objective") + plt.title(f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})") + plt.grid(True) + plt.legend() + plt.savefig(f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png",dpi=300) + plt.show() + + # Compute the distance of intermediate x from the optimal solution + optimal_x = np.ones(X.shape[1],dtype=int) + l1_traces = [] + for t in all_trace_x: + distances = [np.sum(np.abs(np.array(x) - optimal_x)) for x in t] + l1_traces.append(distances) + + ''' compute and make the mean-CI plot for the distance between the intermdeiate + solution and the optimal solution ''' + l1_traces = np.array(l1_traces) + mean_l1 = np.mean(l1_traces,axis=0) + std_l1 = np.std(l1_traces,axis=0) + lower = mean_l1 - 1.96 * std_l1 / np.sqrt(len(trace_x)) + upper = mean_l1 + 1.96 * std_l1 / np.sqrt(len(trace_x)) + iterations = np.arange(len(mean_l1)) + plt.figure(figsize=(8,4)) + plt.plot(iterations,mean_l1,label="Mean L1 Distance from Optimal",color = "darkred") + plt.fill_between(iterations,lower,upper,alpha=0.3,color="darkred", label="95% CI") + plt.xlabel("Iteration") + plt.ylabel("L1 Distance from Optimal") + plt.title("Convergence Toward Optimal Solution (L1 Norm)") + plt.grid(True) + plt.legend() + plt.tight_layout() + plt.savefig(f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png",dpi=300) + plt.show() + + + + +# Read data from the csv file. +X_raw_data = pd.read_csv('Xdata_study_scaleup_0_4vvm_3000W.csv') +y_raw_data = pd.read_csv('ydata_study_scaleup_0_4vvm_3000W.csv') + +X = X_raw_data.values +y = y_raw_data.iloc[:,:-1].values +y = y*-1 + +# The function will build, tune, and optimize the surrogate model and postprocess the results. +run_optimization(X,y,model_type="rbf",n_runs = 10,max_iters = 1000, bootstrap_size=150) \ No newline at end of file diff --git a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py new file mode 100644 index 00000000..03279036 --- /dev/null +++ b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py @@ -0,0 +1,263 @@ +import numpy as np +import pandas as pd +import random +import optuna +from sklearn.ensemble import RandomForestRegressor +from sklearn.preprocessing import OneHotEncoder +from sklearn.model_selection import KFold, cross_val_score +from sklearn.metrics import mean_squared_error +from scipy.interpolate import RBFInterpolator +from tensorflow.keras.models import Sequential +from tensorflow.keras.layers import Dense +import matplotlib.pyplot as plt + +def tune_rbf(X,y): + def objective(trial): + # Tune the RBFInterpolator + # kernel - "multiquadric" (Can try other kernels) + epsilon = trial.suggest_float("epsilon",0.1,10.0, log=False) + try: + kf = KFold(n_splits=5,shuffle=True,random_state=42) + return np.mean([ + mean_squared_error( + y[test], + RBFInterpolator(X[train],y[train],epsilon=epsilon,kernel="multiquadric")(X[test]) + ) + for train, test in kf.split(X) + ]) + except: + return float("inf") + study = optuna.create_study(direction="minimize") + study.optimize(objective,n_trials=20) + return study.best_params + +def tune_rf(X,y): + # Tune the Random Forest + def objective(trial): + model = RandomForestRegressor( + n_estimators=trial.suggest_int("n_estimators",50,200), + max_depth = trial.suggest_int("max_depth",3,10), + min_samples_leaf = trial.suggest_int("min_samples_leaf",1,10), + random_state = 42 + ) + scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') + return -np.mean(scores) + study = optuna.create_study(direction="minimize") + study.optimize(objective,n_trials=20) + return study.best_params + +def tune_nn(X_encoded,y): + # Tune the Neural Network + def objective(trial): + units = trial.suggest_int("n_units",16,128) + layers = trial.suggest_int("n_layers",1,3) + kf = KFold(n_splits=5,shuffle=True,random_state=42) + mses=[] + for train, test in kf.split(X_encoded): + model = Sequential() + model.add(Dense(units, activation='relu', input_shape=(X_encoded.shape[1],))) + for _ in range(layers-1): + model.add(Dense(units,activation='relu')) + model.add(Dense(1)) + model.compile(optimizer='adam',loss='mse') + model.fit(X_encoded[train],y[train],epochs=100,batch_size=16,verbose=0) + mses.append(mean_squared_error(y[test],model.predict(X_encoded[test]).flatten())) + return np.mean(mses) + study = optuna.create_study(direction="minimize") + study.optimize(objective, n_trials=20) + return study.best_params + + +class Surrogatewrapper: + # Wrapper that builds the surrogate model and predicts the QOI value for given X + def __init__(self,model_type,X,y,params): + # Inputs: + # model_type = 'rbf' for RBFInterpolator + # = 'rf' for Random Forest + # = 'nn' for Neural Network + # X,y = the raw data + # params = tune parameter of the surrogate model + + self.model_type = model_type + self.encoder = None + + if model_type == 'rbf': + self.model = RBFInterpolator(X,y,kernel='multiquadric',epsilon=params["epsilon"]) + elif model_type == 'rf': + self.model = RandomForestRegressor(**params, random_state=42) + self.model.fit(X,y) + elif model_type == 'nn': + self.encoder = OneHotEncoder(sparse_output=False) + X_encoded = self.encoder.fit_transform(X) + self.model = self._build_nn(X_encoded.shape[1],params["n_units"],params["n_layers"]) + self.model.fit(X_encoded,y,epochs=100,batch_size=16,verbose=0) + + + def _build_nn(self,input_dim, units, layers): + # Builds the neural network + model = Sequential() + model.add(Dense(units, activation='relu',input_shape=(input_dim,))) + for _ in range(layers-1): + model.add(Dense(units,activation='relu')) + model.add(Dense(1)) + model.compile(optimizer='adam', loss='mse') + return model + + def predict(self,X): + X = X.reshape(1,-1) + if self.model_type == 'nn': + X = self.encoder.transform(X) + return float(self.model.predict(X)[0]) + elif self.model_type == 'rf': + return float(self.model.predict(X)[0]) + else: + return float(self.model(X)[0]) + +def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,max_spargers=8,temp=10.0,alpha=0.95): + # Runs the Simulated Annealing (SA) and reports the resuts + # Inputs: + # surrogate = tuned surrogate model + # dim = dimension of the problem + # max_spargers = maximum number of spargers allowed + # max_iters = maximum number of iterations for SA + # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) + # = max temperature. It controls the exploration rate of SA + # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) + # = cooling rate. It determines how quickly temp decays. + + # Outputs: + # x_best = optimal solution + # y_best = optimal objective function value + # trace = optimization log. Updates at the end of each iteration. + # trace_x = tracks how X changes during each iteration. + + def is_valid(x): + # Checks if the number of spargers <= max_spargers + return np.sum(x== 1) <= max_spargers + + while True: + # generate random starting point + x_curr = np.random.randint(0,3,size=dim) + if is_valid(x_curr): + break + + y_curr = surrogate.predict(x_curr) + x_best, y_best = x_curr.copy(), y_curr + + trace = [(0,y_best)] + + for i in range(max_iters): + # Optimization loop + + x_new = x_curr.copy() + idx = random.randint(0,dim-1) + # perturb a random dimension to get new x + x_new[idx] = (x_new[idx] + random.choice([-1,1])) % 3 + + if not is_valid(x_new): + trace.append((i,y_best)) + temp *= alpha + continue + + y_new = surrogate.predict(x_new) + + delta = y_new - y_curr + if delta < 0 or np.random.rand() < np.exp(-delta / temp): + x_curr, y_curr = x_new, y_new + if y_curr < y_best: + x_best, y_best = x_new.copy(), y_new + + trace.append((i,y_best)) + temp *= alpha + + return x_best, y_best, trace + +def run_optimization(X,y,model_type = 'rbf',max_spargers = 8,n_runs=10,max_iters = 1000, bootstrap_size=100): + # Bootstraps data, runs optimization and postprocesses the resutls. + # Inputs: + # X,y = the raw data + # model_type = 'rbf' for RBFInterpolator + # = 'rf' for Random Forest + # = 'nn' for Neural Network + # n_runs = number of bootstraps + # max_iters = maximum number of SA iterations + # bootstrap_size = size of bootstrap samples + + all_x = [] + all_y = [] + all_traces = np.zeros((n_runs, max_iters + 1)) + rng = np.random.default_rng(42) + # Bootstrap the data + bootstrap_idxs = [rng.choice(len(X), size = bootstrap_size, replace = False) for _ in range(n_runs)] + + i = 0 + for idxs in bootstrap_idxs: + # Tune the model for each bootstrap + X_sub, y_sub = X[idxs], y[idxs] + if model_type == "rbf": + params = tune_rbf(X_sub,y_sub) + elif model_type == "rf": + params = tune_rf(X_sub,y_sub) + elif model_type == "nn": + encoder = OneHotEncoder(sparse_output=False) + X_encoded = encoder.fit_transform(X_sub) + params = tune_nn(X_encoded,y_sub) + else: + raise ValueError("Invalid model_type") + + # build the surrogate model + surrogate = Surrogatewrapper(model_type,X_sub,y_sub,params) + + # run optimization + x_best, y_best, trace = simulated_annealing_surrogate(surrogate, dim=X.shape[1], max_iters=max_iters, max_spargers=max_spargers) + + trace_y = [y for _, y in trace] + all_traces[i,:] = trace_y + all_x.append(x_best) + all_y.append(y_best) + i = i + 1 + + # Compute the best y across all the bootstraps + best_index = np.argmin(all_y) + best_x = all_x[best_index] + best_y = all_y[best_index] + # Save the best solution + df = pd.DataFrame([{ + **{f"x{i}": best_x[i] for i in range(len(best_x))}, + "best_y": best_y + }]) + df.to_csv(f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.csv", index=False) + + print("X = ", x_best) + print("surrogate-predicted y",y_best) + + # Make the mean-CI plot for the objective function and save it + mean_trace = np.mean(-1*all_traces,axis=0) + std_trace = np.std(all_traces,axis=0) + lower_bound = mean_trace - 1.96* std_trace / np.sqrt(n_runs) + upper_bound = mean_trace + 1.96* std_trace / np.sqrt(n_runs) + iterations = np.arange(max_iters + 1) + + plt.figure(figsize=(8,4)) + plt.plot(iterations,mean_trace,label=f"Mean Convergence {model_type.upper()}", color = "blue") + plt.fill_between(iterations, lower_bound, upper_bound, color = "blue", alpha = 0.3, label = "95% CI") + plt.xlabel("Iteration") + plt.ylabel("Best Surrogate-Predicted Objective") + plt.title(f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})") + plt.grid(True) + plt.legend() + plt.savefig(f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png",dpi=300) + plt.show() + + + +# Read data from the csv file. +X_raw_data = pd.read_csv('Xdata_study_scaleup_0_4vvm_3000W.csv') +y_raw_data = pd.read_csv('ydata_study_scaleup_0_4vvm_3000W.csv') + +X = X_raw_data.values +y = y_raw_data.iloc[:,:-1].values +y = y*-1 + +# The function will build, tune, and optimize the surrogate model and postprocess the results. +run_optimization(X,y,model_type="rbf",max_spargers = 8, n_runs = 10,max_iters = 1000, bootstrap_size=100) \ No newline at end of file diff --git a/bird/postprocess/SA_optimization/results.pkl b/bird/postprocess/SA_optimization/results.pkl new file mode 100644 index 0000000000000000000000000000000000000000..10aa5fe88138e206188453a8ec77f9050aaa9765 GIT binary patch literal 5208 zcmXxoc{r3?7yxi7l`Pj*n6VFIhA4X_OR{#-DwRv65|Kn5NpwZlC`7vICav00NwnBv z$}*POZmC;WN=3JgwCLXNeD@uHcplHs`<`>=obP?lH%W4Zz8W(0PhtTF?W2BWkB^Sp zT;S!ZcA&_wr~h#FBZ_V(z`xmiCo{SPO78KYBRoJAAt?tPCUCbo)y*8J@)M#f+U3c&`}(LuzzipoAMEE&M)G#HD+ zjJBJ+incMaqxm})Mp|Hc2`YjCW7;|cKj!D48yHoCF~bY`Udhnb9`V^>jG2Xz0vLNi z*0hEp7Rf0Hl$T>)Qo#sGOv4 z80VMMRZMRVF|qylp)&o282N!2%0#`2OQF}-sTKH@m)?lcA5u$5>6?uQ6(p+O*k?+?2_*J=p*1>*pUB8G-Ny$|ojdo}^4-LGN^Qf=o{7Pz28`3p z8VvgP7%^dvaF=@VN{l?gEZ^F!@;dPkvEGu3TEjEk#$%FV>+ zJQ!o7!Y+duNvu`{{LMwMzYN&zzu``60iqX{udh)y@R zYY|3g!A#}Xw$4^z&~b&L+R|)yjBZP9Qj@Kgz7lPwziSw1e8I>9OsD@|QKvb5&N{pe zoQDoZVx$3PaxAsm`qeKaPSD5sQ8hUkqed|9lRX;0uAtX0u3Zv-FUTCDKxrKB=d;pV z$|SDmXI}B}O)6&$7>BjyYxLNCV&c>V>AQAP=Vd#X_Ry!QdFKuiVRT!0knWy*c3Xi-)Op{LqWF3!fN7|i6WRr&h?`q>oJW&Yz7a}pyTFjkS_^!SEnp4yfGW6moJx`y8&rQ#2Fp8AgzQiHbdtt=72Ieq+Q=)2trPPAi0n4g4 z&~Y99dUUbOX$MAbeE$Ts1L?>bUsD0Z%cK1vC9I zZ8XfpnwZEX@8BTR9r!nib*w&9nxP?w(GoDD9CMY!p8wanSqz_C1&mRU zG>)lx(Cu+IiR&x~ZuUr$!Dts4Yx}k9vPEZz^)R(O7tf$R>u4~hqe)0!`buIC9qNx> z^tg`ETrfuJhtMq*^N6+9`B=P;^}(nLOl96q=UtgIh~0i1f^JOe#wZtzy?ws@`OOky zB1W4)+4LYrzk#vNSuLpdrk^8$-__MAYpAoN3#L_iGx);(1QORW5HZYq%)rP1OmoWw zcH&$ABYVE6b{jpOr`J1PGBwW}FjiWXR<%BTJ_TQ7a%>B=F-ik7{35K&3fxTM zI#xaCo&0?(M&@Ab4-1}Wzw{;6vu{J@@`iI5se+lzd6!z_Ii0pQ8LrVAsLxsk%xq6b zmGweLVq%S1!ebn&&W{5#X4@~=(>g-OjfxvqnU`X;3QW&t0>4JEh*+!0<9=lLPmI=s znJ-(Cuut_dF;PsDpFb}bqi`^zeZ@c9gXlf#QSFScEv3$UveYi`JR8ALpvOBiwB(uE z4vZqejQtG*Om4{&>;7#hY)MuFMgd@!0_E6wHFRI;4VirKaW*y2O<>v?^6u-0J4hUl zyUjgGl82E47-`xEXqdYJZ|K)}Cs&t%y zHG9^&KdD+UMQR@{=QETp(er${p}+8CJ4Q3X7#{=)1qF1SX#FtISno~shs|Kxp1gD= zNiT_esO1=|IC2N0iD0Z7kIJ2WY>0LG#&DC*PQXYW%yh3tYk80rG2z~SE9~=ZG4cn~ zl_~f~DD#n6o6Re;2XiYi`c)dIp74p`mqSdDywr2E-WrT{fEf*Uz8?HJO02tK-eGcU-ISjt4JBE=9n9++@g_>zE=(xcp>>DC4jP8Q5=fvGV z^(Kj!Fnut$=J9Ndwn=lUZR_0as^;)8at1SAzhrHSQy-l(yEufqzyhP)U}mNriNj*L-%9%bGJaY{ zo!vxfoR#CLnjdss>ZxDhJincfQ6-qs)#qZjdb)>q7bdTXnB;yI> z&m1hRn1#_oFwT^mrCo!y#3XiQ@!D^bFe(DmDSu+Q-ETZGz6npkSDCsmEe6x`7dWw8 z8i{pk4llcEL+$NyFr!5VqRw$<#JY?JM!9G&M)lJ1G}A)7OX(gUJY+VbyIBzwS2=4lzObYVWTPsAuJ-G^hRh*V-%oCf0o^ zF6+`ms!p5(({biLy13{8F@BD0cw5>iMpj_VX89d6AB+)e|FTZ(c1<56J23rE)&Vt> zGKh8jYV&#c&VG!hf-yQ(mR8nXC)RqwcewL-HAd=ShTr(nwqJ{g^-THHnv|o2(K@iH zHaV)5GhB%^=Bh8dysR4|Q!u^h(G|C9nuzh!oV<5FqUus180$pT)`b?Bm_Rqipjw~m zW7ENmJ`~0;RVt%xfm@lHc@ai(VABt5uCH2il-MJV`{~GX>dwHC<_zF*6>R9<-#s&x zC#mnkC=^V$`R>NJ>GYZJT)Wi1@1p=CZ7|iWH22ld=^kG9t>K@O;naJ8H<&?8lI@-l z`gv%Jy|R%}XNA!jFrzNb*OId_WG>w$B`rOl_h2+0%rs~Ftz%>K9ZVd-2yEfK!-x%L zIc>@D%Jx`#4VOjBdlk|!+5pCQbYfECfCI6Io=my@%fDlE2Ta!~;#6D3WMXYml2M5_ z^`0{yjG6t5^Xs>C?P=9+egE#%A&la{R8L)-v#OC^-`#jY^2UB@A6&uo^1MeXU5}BR z?fF?H+8WgRLn@fTmv#3__G~1^{}wv7;u}?$Jis_nqHCJ6nZ!gBEj6E2q+m1y%*-L< z%HKzeiAgTjO+1rtgwa}QTybEkrA7!HR~g!zKT4gKWnfHQX85u1|KC5SZ3=Y}QqTJZ zF!rMIzD-8-_Y6Yk$Dz96R1eq-#;tVxu!bR~$II_M-^ZMZksFxa(3a*M8BWCbKNBOw zAzB#agPFGkWJQ!~5_{CcZ?QF|p6M(w4c#$~#k@RXO;gh1(=SpzV5M}t(ya229rQU9 zBrlHbIZ54LFqolGZp-kv|IgWxP1R~ArebswOz+5`Q@O`-dM+;&JF>^$#b}l^XLa`a zn2S}!#6owqh#&qK*?=*^9IZl}=>2O+u`UW0wqkS@%=pmh&wr1n_pc`;v|^5j3r6?A znAhIcv?kk=oE?p|-#6@|-eH+wrUirPu5;U zsZrm}906m`nEBmBfj)!66`MtWL{hcjD421uebi7z6B)0|zpTpO-DHf8gJ~>&x%`0o zWnxYC&s7+M)ZXfV>GaElzWo?ZtkqxslM3e(M!{f){pFn@WjxOyYHvfpO!O9(-qx-pCh$C7lA^gCqcAX| zmAh4BAJ9Fg+i!7Z>Qw69lL5vt8(kCrmhR!g{N0MqCJ!;P2Q!?a{iry>o8;uX-g)M_ bmx?O~)77r4$+|=Lqt2Z=yn#3t#+Ls9#_kjd literal 0 HcmV?d00001 diff --git a/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv b/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv new file mode 100644 index 00000000..e224b58a --- /dev/null +++ b/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv @@ -0,0 +1,185 @@ +4.639249158318668,0.12626038361898534 +4.830205075462752,0.1345759778255486 +11.883263228093135,0.3264360683677917 +13.920765170099399,0.4105486654309187 +6.542159679067755,0.2034214300932224 +12.152375869450877,0.36239501906304267 +8.022225385816165,0.2748815895051835 +9.57833436748858,0.28111820963360595 +7.621157957714864,0.25567306815826935 +6.854083596204951,0.2298569939977962 +11.145584784788815,0.31811738493951314 +11.017594168736228,0.3184159068790033 +13.50973761231716,0.3752779584066442 +12.030553522901826,0.3626718350283127 +8.500853761159016,0.2514870108881235 +8.743216029351826,0.2670107363696459 +11.717721199440591,0.3128707825208673 +11.345565509701485,0.353472952974596 +8.988391479133291,0.2580096856927115 +8.543409441773761,0.262301422633598 +6.560127864354076,0.20837078993418331 +6.4149526976824855,0.19347669879343268 +13.741028936629446,0.37516250222124864 +10.260225187485267,0.30671415538616664 +12.38699282309954,0.33678185294994506 +15.459376105064136,0.447246924226177 +9.09803267916285,0.304227995563132 +6.740910374100037,0.21154083742443155 +13.191334348313264,0.34796851870506956 +13.70507196126807,0.38117142223133704 +11.518308973994909,0.35289543460220707 +11.515168788856977,0.3461668678795173 +7.224363681870076,0.23657610962538503 +10.805708473886197,0.3263647525674359 +12.932602535390258,0.370807747121449 +10.892569560758172,0.31241264469905766 +12.069083972134544,0.36751044492654417 +6.4823749997684805,0.19262760715138516 +11.231649637811778,0.3277463243939802 +4.270684191558357,0.10987581442252059 +13.76528529571703,0.38285304273583004 +10.152583557069674,0.3408235347109574 +10.986707238586106,0.324251636154978 +9.79517497061306,0.3029635961468728 +10.419543443002388,0.3170020475695413 +10.258773239532914,0.27979900920419293 +10.189745733831108,0.3250937746551829 +6.514063372968979,0.2268487917845758 +13.953941108502349,0.37603794998487483 +11.992362749779748,0.34762137147830285 +7.882404941588837,0.23711080920489294 +9.275719183296298,0.26904923614363635 +7.776457060294847,0.25905651438188215 +9.725980401340914,0.2985287367453095 +6.807759844786992,0.2048265466788003 +6.630803695687349,0.21680559865756013 +4.063931442420922,0.11272271250075441 +11.225122173481324,0.3453857489445164 +9.82654794320513,0.31766875974197384 +6.157926107439263,0.18678044797135201 +5.855484586386016,0.18450597017794995 +9.18587411892937,0.2978687770910763 +9.88460112835837,0.2705988868749546 +6.841601401913933,0.2050600340598003 +12.79640726757815,0.3623376347189863 +12.83899374588669,0.3975083213578766 +8.457051563045496,0.26991461104084247 +9.763108036495394,0.3275758151015267 +8.984052674188392,0.23708461040402135 +11.471274422120723,0.3049553719602422 +10.615101031292475,0.311189711506409 +13.247670835151544,0.36060925350524264 +10.074632957437625,0.31206315974184845 +7.549823041704327,0.21747657909403267 +13.660770312425418,0.3507548164008165 +9.258995020455252,0.29796230302465954 +9.338803482609384,0.3070023813054725 +10.168223206620057,0.289058067197555 +15.414172835849321,0.4436665921077268 +6.382847851314592,0.18876460404289166 +12.541321134236878,0.3777538153682838 +14.625672661663916,0.38802297600654273 +9.2131453370491,0.2672227550883449 +6.020912999143897,0.18351282428727747 +9.594482823382547,0.3548268182647394 +9.434406063121779,0.32561538569508414 +11.27709117666853,0.3170389795034779 +11.85440229800048,0.3713104751517777 +8.143103526931212,0.24373125334484186 +5.987413503473131,0.19209801113080932 +10.66860645276813,0.3146498728341653 +10.973009201937053,0.326561903194073 +6.736162134712763,0.21831998403883163 +6.319124326915894,0.20386451019469645 +9.85518408546759,0.2547269254087896 +11.752111783293545,0.3360594969365309 +7.2505681489712845,0.22033395375408343 +11.538062664951463,0.32045394779240677 +9.480109510593579,0.2931065455457869 +7.24841607883414,0.23832775051957591 +10.347675591461396,0.28753868506914715 +10.686337772821627,0.30684839551276677 +9.822044747899417,0.32661245475083844 +9.447079333421648,0.3112984064131052 +11.114202667483596,0.3364211476686481 +9.532358320060213,0.31236106613200026 +11.688464102351732,0.35487546667211 +6.532609784598158,0.22168781874321142 +10.192019448061677,0.3054567325499729 +13.077747284183287,0.38903025246203415 +10.096158138823926,0.30867510872414256 +9.343519904040358,0.290375320672992 +11.40697959771358,0.31308878923916295 +12.020609433055474,0.3782750313181893 +7.172718740262101,0.24334049751097286 +7.584794001842911,0.2514622981514563 +9.152594292536907,0.2678778098535802 +12.045947232357136,0.33710722608802324 +4.379843967798498,0.10762641890440137 +3.9571319937219607,0.113635341004926 +9.604472609306962,0.27473135213632965 +8.418838881573317,0.25553219416942385 +10.210954922063188,0.323263474717489 +9.607306751578054,0.29177678625562137 +7.060706536012455,0.2464907666663334 +6.943395438649984,0.23411188612717468 +7.987496774296126,0.23715829335149538 +6.703187252555086,0.21613478186303836 +8.989863908497496,0.29754347680337007 +5.3003428235059005,0.15283433472600066 +7.54340245910743,0.24628416098103528 +9.813350149916667,0.3142972297214279 +9.471843550104339,0.3170324937170163 +14.278150609504612,0.4087077718493328 +9.677308352105648,0.28421158422409315 +7.4440856645917135,0.25581770286934824 +4.385277812944313,0.13265263272190378 +8.22363437693505,0.2223718632499199 +9.408385570236735,0.29099268450210747 +11.77627353707695,0.36591204701029667 +15.043583831144797,0.4168601817074705 +6.7135010881993855,0.20526445170131055 +7.3099809259113115,0.22216331625583294 +6.8867769079468415,0.2117247511316945 +4.409863387744625,0.11906173881006474 +7.641879896764433,0.23103459137397314 +8.483160007686088,0.24982929105677146 +12.2184349027241,0.34412720042998773 +13.108464684640968,0.3854827942762161 +12.301487123370132,0.3546389998626981 +6.778424897986802,0.20380859414550845 +11.153693300454865,0.33196148938956604 +12.848113577592285,0.383963743119625 +7.7318602570176695,0.24995391622250263 +13.889362645764818,0.41917795613750286 +5.029285733575436,0.1565712068835105 +12.815511254920658,0.32045351604623495 +8.916057019146086,0.2593018338761806 +7.649338687785831,0.2492080696611349 +12.8604460492114,0.3723917179012849 +6.601788303188531,0.18369133369166799 +10.288367893781292,0.30306427295098 +6.925703924819166,0.2403483369335239 +11.790867067900965,0.32527202858137033 +12.810837863572981,0.36388377313805176 +11.092170573659601,0.333338222107799 +10.189868448033742,0.2709142473792956 +5.078027062937434,0.15792477880845202 +7.223634361053522,0.21356319778744356 +8.948019498562504,0.24191457543885958 +13.222659710790236,0.3750710095241255 +8.076671575057484,0.2209566383161624 +15.312111223230433,0.3871211907716363 +14.170973262775446,0.42197025792806736 +9.32461479951292,0.29381079716589753 +13.309929308155027,0.3625732862415955 +11.21961903186075,0.3243213919583699 +4.66559471266868,0.1327459953758173 +8.65681333512524,0.2849376420634901 +10.051156775736247,0.33207149084178794 +10.559178404056045,0.31681027963019587 +9.170860767688787,0.2863959162680961 +12.08392615370928,0.3370654696996921 +8.519728499754038,0.24416325998118774 +7.275149264066791,0.2572700464241981 From b74c87098aa2acbc6554e6e68f0e5f404e53f3bd Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 10:07:05 -0600 Subject: [PATCH 20/86] fix format --- bird/postprocess/SA_optimization/README.md | 2 +- bird/postprocess/SA_optimization/get_csv.py | 23 +- .../SA_optimization/get_optimal.py | 324 +++++++++++------- .../get_optimal_with_constraint.py | 322 ++++++++++------- 4 files changed, 416 insertions(+), 255 deletions(-) diff --git a/bird/postprocess/SA_optimization/README.md b/bird/postprocess/SA_optimization/README.md index 37309e9b..3d79d9db 100644 --- a/bird/postprocess/SA_optimization/README.md +++ b/bird/postprocess/SA_optimization/README.md @@ -24,7 +24,7 @@ The SA optimizer operates on discrete feature values (0,1,2) with an option to r - max_spargers: maximum number of spargers (only in 'get_optimal_with_constraints.py') (default = 8) - n_runs: number of bootstrap runs (default = 10) - max_iters: maximum number of iterations of SA (default = 1000) - - bootstrap_size: sample size of each boostrap (default = 100) + - bootstrap_size: sample size of each bootstrap (default = 100) - For each bootstrap run, the model hyperparameters are tuned using 5-fold cross validation. - The simulated_annealing_surrogate(...) function runs the optimization: - If SA is too slow or fails to converge, you can change the following parameters: diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py index cc76fc14..02dd1191 100644 --- a/bird/postprocess/SA_optimization/get_csv.py +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -1,7 +1,9 @@ -import pickle5 as pkl +import csv import os + import numpy as np -import csv +import pickle5 as pkl + def get_config_result(config_fold): with open(os.path.join(config_fold, "results.pkl"), "rb") as f: @@ -11,28 +13,27 @@ def get_config_result(config_fold): Xdata = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], np.int64) count = 0 # Save data into CSV files - xfname = "Xdata_"+config_fold+".csv" - yfname = "ydata_"+config_fold+".csv" - with open(xfname,'w',newline='') as csvfile: + xfname = "Xdata_" + config_fold + ".csv" + yfname = "ydata_" + config_fold + ".csv" + with open(xfname, "w", newline="") as csvfile: writer = csv.writer(csvfile) for sims in results: b0 = configs[sims][0] b1 = configs[sims][1] b2 = configs[sims][2] - raw_data = np.concatenate((b0,b1,b2), axis = None) + raw_data = np.concatenate((b0, b1, b2), axis=None) writer.writerow(raw_data) - with open(yfname,'w',newline='') as csvfile: + with open(yfname, "w", newline="") as csvfile: writer = csv.writer(csvfile) for sims in results: q0 = results[sims]["qoi"] q1 = results[sims]["qoi_err"] - y_data = np.concatenate((q0,q1), axis = None) + y_data = np.concatenate((q0, q1), axis=None) writer.writerow(y_data) -studies = { - "study_scaleup_0_4vvm_3000W" : r"0.0036$m^3$ 0.4vvm 0W" - } + +studies = {"study_scaleup_0_4vvm_3000W": r"0.0036$m^3$ 0.4vvm 0W"} for study in studies: diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index ca960fd8..43c86bbb 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -1,79 +1,109 @@ -import numpy as np +import random +import warnings + +import matplotlib.pyplot as plt +import numpy as np +import optuna import pandas as pd -import random -import optuna +from scipy.interpolate import RBFInterpolator from sklearn.ensemble import RandomForestRegressor -from sklearn.preprocessing import OneHotEncoder -from sklearn.model_selection import KFold, cross_val_score from sklearn.metrics import mean_squared_error -from scipy.interpolate import RBFInterpolator -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Dense -import matplotlib.pyplot as plt -import warnings +from sklearn.model_selection import KFold, cross_val_score +from sklearn.preprocessing import OneHotEncoder +from tensorflow.keras.layers import Dense +from tensorflow.keras.models import Sequential warningsl.filterwarnings("ignore") -def tune_rbf(X,y): + +def tune_rbf(X, y): # Tune the RBFInterpolator # kernel - "multiquadric" (Can try other kernels) def objective(trial): - epsilon = trial.suggest_float("epsilon",0.1,10.0, log=False) + epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) try: - kf = KFold(n_splits=5,shuffle=True,random_state=42) - return np.mean([ - mean_squared_error( - y[test], - RBFInterpolator(X[train],y[train],epsilon=epsilon,kernel="multiquadric")(X[test]) - ) - for train, test in kf.split(X) - ]) + kf = KFold(n_splits=5, shuffle=True, random_state=42) + return np.mean( + [ + mean_squared_error( + y[test], + RBFInterpolator( + X[train], + y[train], + epsilon=epsilon, + kernel="multiquadric", + )(X[test]), + ) + for train, test in kf.split(X) + ] + ) except: return float("inf") + study = optuna.create_study(direction="minimize") - study.optimize(objective,n_trials=20) + study.optimize(objective, n_trials=20) return study.best_params -def tune_rf(X,y): + +def tune_rf(X, y): # Tune the Random Forest def objective(trial): model = RandomForestRegressor( - n_estimators=trial.suggest_int("n_estimators",50,200), - max_depth = trial.suggest_int("max_depth",3,10), - min_samples_leaf = trial.suggest_int("min_samples_leaf",1,10), - random_state = 42 + n_estimators=trial.suggest_int("n_estimators", 50, 200), + max_depth=trial.suggest_int("max_depth", 3, 10), + min_samples_leaf=trial.suggest_int("min_samples_leaf", 1, 10), + random_state=42, + ) + scores = cross_val_score( + model, X, y, cv=5, scoring="neg_mean_squared_error" ) - scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') return -np.mean(scores) + study = optuna.create_study(direction="minimize") - study.optimize(objective,n_trials=20) + study.optimize(objective, n_trials=20) return study.best_params -def tune_nn(X_encoded,y): + +def tune_nn(X_encoded, y): # Tune the Neural Network def objective(trial): - units = trial.suggest_int("n_units",16,128) - layers = trial.suggest_int("n_layers",1,3) - kf = KFold(n_splits=5,shuffle=True,random_state=42) - mses=[] + units = trial.suggest_int("n_units", 16, 128) + layers = trial.suggest_int("n_layers", 1, 3) + kf = KFold(n_splits=5, shuffle=True, random_state=42) + mses = [] for train, test in kf.split(X_encoded): model = Sequential() - model.add(Dense(units, activation='relu', input_shape=(X_encoded.shape[1],))) - for _ in range(layers-1): - model.add(Dense(units,activation='relu')) + model.add( + Dense( + units, activation="relu", input_shape=(X_encoded.shape[1],) + ) + ) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) model.add(Dense(1)) - model.compile(optimizer='adam',loss='mse') - model.fit(X_encoded[train],y[train],epochs=100,batch_size=16,verbose=0) - mses.append(mean_squared_error(y[test],model.predict(X_encoded[test]).flatten())) + model.compile(optimizer="adam", loss="mse") + model.fit( + X_encoded[train], + y[train], + epochs=100, + batch_size=16, + verbose=0, + ) + mses.append( + mean_squared_error( + y[test], model.predict(X_encoded[test]).flatten() + ) + ) return np.mean(mses) + study = optuna.create_study(direction="minimize") study.optimize(objective, n_trials=20) - return study.best_params + return study.best_params class Surrogatewrapper: # Wrapper that builds the surrogate model and predicts the QOI value for given X - def __init__(self,model_type,X,y,params): + def __init__(self, model_type, X, y, params): # Inputs: # model_type = 'rbf' for RBFInterpolator # = 'rf' for Random Forest @@ -81,51 +111,57 @@ def __init__(self,model_type,X,y,params): # X,y = the raw data # params = tune parameter of the surrogate model - self.model_type = model_type - self.encoder = None + self.model_type = model_type + self.encoder = None - if model_type == 'rbf': - self.model = RBFInterpolator(X,y,kernel='multiquadric',epsilon=params["epsilon"]) - elif model_type == 'rf': + if model_type == "rbf": + self.model = RBFInterpolator( + X, y, kernel="multiquadric", epsilon=params["epsilon"] + ) + elif model_type == "rf": self.model = RandomForestRegressor(**params, random_state=42) - self.model.fit(X,y) - elif model_type == 'nn': + self.model.fit(X, y) + elif model_type == "nn": self.encoder = OneHotEncoder(sparse_output=False) X_encoded = self.encoder.fit_transform(X) - self.model = self._build_nn(X_encoded.shape[1],params["n_units"],params["n_layers"]) - self.model.fit(X_encoded,y,epochs=100,batch_size=16,verbose=0) + self.model = self._build_nn( + X_encoded.shape[1], params["n_units"], params["n_layers"] + ) + self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) - - def _build_nn(self,input_dim, units, layers): + def _build_nn(self, input_dim, units, layers): # Builds the neural network model = Sequential() - model.add(Dense(units, activation='relu',input_shape=(input_dim,))) - for _ in range(layers-1): - model.add(Dense(units,activation='relu')) + model.add(Dense(units, activation="relu", input_shape=(input_dim,))) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) model.add(Dense(1)) - model.compile(optimizer='adam', loss='mse') - return model - - def predict(self,X): - X = X.reshape(1,-1) - if self.model_type == 'nn': + model.compile(optimizer="adam", loss="mse") + return model + + def predict(self, X): + X = X.reshape(1, -1) + if self.model_type == "nn": X = self.encoder.transform(X) return float(self.model.predict(X)[0]) - elif self.model_type == 'rf': + elif self.model_type == "rf": return float(self.model.predict(X)[0]) else: return float(self.model(X)[0]) - -def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,temp=10.0,alpha=0.95): - # Runs the Simulated Annealing (SA) and reports the resuts + + +def simulated_annealing_surrogate( + surrogate, dim=12, max_iters=1000, temp=10.0, alpha=0.95 +): + # Runs the Simulated Annealing (SA) and reports the results # Inputs: - # surrogate = tuned surrogate model + # surrogate = tuned surrogate model # dim = dimension of the problem # max_iters = maximum number of iterations for SA # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) # = max temperature. It controls the exploration rate of SA # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = cooling rate. It determines how quickly temp decays. + # = cooling rate. It determines how quickly temp decays. # Outputs: # x_best = optimal solution @@ -134,37 +170,40 @@ def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,temp=10.0,alph # trace_x = tracks how X changes during each iteration. # generate random starting point - x_curr = np.random.randint(0,3,size=dim) - + x_curr = np.random.randint(0, 3, size=dim) + y_curr = surrogate.predict(x_curr) - x_best, y_best = x_curr.copy(), y_curr + x_best, y_best = x_curr.copy(), y_curr - trace = [(0,y_best)] + trace = [(0, y_best)] trace_x = [x_curr.copy()] for i in range(max_iters): # Optimization loop - + x_new = x_curr.copy() - idx = random.randint(0,dim-1) + idx = random.randint(0, dim - 1) # perturb a random dimension to get new x - x_new[idx] = (x_new[idx] + random.choice([-1,1])) % 3 + x_new[idx] = (x_new[idx] + random.choice([-1, 1])) % 3 y_new = surrogate.predict(x_new) delta = y_new - y_curr if delta < 0 or np.random.rand() < np.exp(-delta / temp): - x_curr, y_curr = x_new, y_new + x_curr, y_curr = x_new, y_new if y_curr < y_best: - x_best, y_best = x_new.copy(), y_new - - trace.append((i,y_best)) + x_best, y_best = x_new.copy(), y_new + + trace.append((i, y_best)) trace_x.append(x_curr.copy()) - temp *= alpha - + temp *= alpha + return x_best, y_best, trace, trace_x -def run_optimization(X,y,model_type = 'rbf',n_runs=10,max_iters = 1000, bootstrap_size=100): - # Bootstraps data, runs optimization and postprocesses the resutls. + +def run_optimization( + X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=100 +): + # Bootstraps data, runs optimization and postprocesses the results. # Inputs: # X,y = the raw data # model_type = 'rbf' for RBFInterpolator @@ -180,31 +219,36 @@ def run_optimization(X,y,model_type = 'rbf',n_runs=10,max_iters = 1000, bootstra all_trace_x = [] rng = np.random.default_rng(42) # Bootstrap the data - bootstrap_idxs = [rng.choice(len(X), size = bootstrap_size, replace = False) for _ in range(n_runs)] + bootstrap_idxs = [ + rng.choice(len(X), size=bootstrap_size, replace=False) + for _ in range(n_runs) + ] i = 0 for idxs in bootstrap_idxs: # Tune the model for each bootstrap X_sub, y_sub = X[idxs], y[idxs] if model_type == "rbf": - params = tune_rbf(X_sub,y_sub) + params = tune_rbf(X_sub, y_sub) elif model_type == "rf": - params = tune_rf(X_sub,y_sub) + params = tune_rf(X_sub, y_sub) elif model_type == "nn": encoder = OneHotEncoder(sparse_output=False) X_encoded = encoder.fit_transform(X_sub) - params = tune_nn(X_encoded,y_sub) + params = tune_nn(X_encoded, y_sub) else: raise ValueError("Invalid model_type") # build the surrogate model - surrogate = Surrogatewrapper(model_type,X_sub,y_sub,params) + surrogate = Surrogatewrapper(model_type, X_sub, y_sub, params) # run optimization - x_best, y_best, trace, trace_x = simulated_annealing_surrogate(surrogate, dim=X.shape[1], max_iters=max_iters) - + x_best, y_best, trace, trace_x = simulated_annealing_surrogate( + surrogate, dim=X.shape[1], max_iters=max_iters + ) + trace_y = [y for _, y in trace] - all_traces[i,:] = trace_y + all_traces[i, :] = trace_y all_x.append(x_best) all_y.append(y_best) all_trace_x.append(trace_x) @@ -214,69 +258,101 @@ def run_optimization(X,y,model_type = 'rbf',n_runs=10,max_iters = 1000, bootstra best_index = np.argmin(all_y) best_x = all_x[best_index] best_y = all_y[best_index] - # Save the best solution - df = pd.DataFrame([{ - **{f"x{i}": best_x[i] for i in range(len(best_x))}, - "best_y": best_y - }]) - df.to_csv(f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}.csv", index=False) - + # Save the best solution + df = pd.DataFrame( + [ + { + **{f"x{i}": best_x[i] for i in range(len(best_x))}, + "best_y": best_y, + } + ] + ) + df.to_csv( + f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}.csv", + index=False, + ) # Make the mean-CI plot for the objective function and save it - mean_trace = np.mean(-1*all_traces,axis=0) - std_trace = np.std(all_traces,axis=0) - lower_bound = mean_trace - 1.96* std_trace / np.sqrt(n_runs) - upper_bound = mean_trace + 1.96* std_trace / np.sqrt(n_runs) + mean_trace = np.mean(-1 * all_traces, axis=0) + std_trace = np.std(all_traces, axis=0) + lower_bound = mean_trace - 1.96 * std_trace / np.sqrt(n_runs) + upper_bound = mean_trace + 1.96 * std_trace / np.sqrt(n_runs) iterations = np.arange(max_iters + 1) - plt.figure(figsize=(8,4)) - plt.plot(iterations,mean_trace,label=f"Mean Convergence {model_type.upper()}", color = "blue") - plt.fill_between(iterations, lower_bound, upper_bound, color = "blue", alpha = 0.3, label = "95% CI") + plt.figure(figsize=(8, 4)) + plt.plot( + iterations, + mean_trace, + label=f"Mean Convergence {model_type.upper()}", + color="blue", + ) + plt.fill_between( + iterations, + lower_bound, + upper_bound, + color="blue", + alpha=0.3, + label="95% CI", + ) plt.xlabel("Iteration") plt.ylabel("Best Surrogate-Predicted Objective") - plt.title(f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})") + plt.title( + f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})" + ) plt.grid(True) - plt.legend() - plt.savefig(f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png",dpi=300) + plt.legend() + plt.savefig( + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png", + dpi=300, + ) plt.show() # Compute the distance of intermediate x from the optimal solution - optimal_x = np.ones(X.shape[1],dtype=int) + optimal_x = np.ones(X.shape[1], dtype=int) l1_traces = [] for t in all_trace_x: distances = [np.sum(np.abs(np.array(x) - optimal_x)) for x in t] l1_traces.append(distances) - - ''' compute and make the mean-CI plot for the distance between the intermdeiate - solution and the optimal solution ''' + + """ compute and make the mean-CI plot for the distance between the intermdeiate + solution and the optimal solution """ l1_traces = np.array(l1_traces) - mean_l1 = np.mean(l1_traces,axis=0) - std_l1 = np.std(l1_traces,axis=0) + mean_l1 = np.mean(l1_traces, axis=0) + std_l1 = np.std(l1_traces, axis=0) lower = mean_l1 - 1.96 * std_l1 / np.sqrt(len(trace_x)) upper = mean_l1 + 1.96 * std_l1 / np.sqrt(len(trace_x)) iterations = np.arange(len(mean_l1)) - plt.figure(figsize=(8,4)) - plt.plot(iterations,mean_l1,label="Mean L1 Distance from Optimal",color = "darkred") - plt.fill_between(iterations,lower,upper,alpha=0.3,color="darkred", label="95% CI") + plt.figure(figsize=(8, 4)) + plt.plot( + iterations, + mean_l1, + label="Mean L1 Distance from Optimal", + color="darkred", + ) + plt.fill_between( + iterations, lower, upper, alpha=0.3, color="darkred", label="95% CI" + ) plt.xlabel("Iteration") plt.ylabel("L1 Distance from Optimal") plt.title("Convergence Toward Optimal Solution (L1 Norm)") plt.grid(True) plt.legend() plt.tight_layout() - plt.savefig(f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png",dpi=300) + plt.savefig( + f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png", dpi=300 + ) plt.show() - - # Read data from the csv file. -X_raw_data = pd.read_csv('Xdata_study_scaleup_0_4vvm_3000W.csv') -y_raw_data = pd.read_csv('ydata_study_scaleup_0_4vvm_3000W.csv') +X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") +y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") X = X_raw_data.values -y = y_raw_data.iloc[:,:-1].values -y = y*-1 +y = y_raw_data.iloc[:, :-1].values +y = y * -1 # The function will build, tune, and optimize the surrogate model and postprocess the results. -run_optimization(X,y,model_type="rbf",n_runs = 10,max_iters = 1000, bootstrap_size=150) \ No newline at end of file +run_optimization( + X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=150 +) diff --git a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py index 03279036..b3371e41 100644 --- a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py +++ b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py @@ -1,129 +1,165 @@ -import numpy as np +import random + +import matplotlib.pyplot as plt +import numpy as np +import optuna import pandas as pd -import random -import optuna +from scipy.interpolate import RBFInterpolator from sklearn.ensemble import RandomForestRegressor -from sklearn.preprocessing import OneHotEncoder -from sklearn.model_selection import KFold, cross_val_score from sklearn.metrics import mean_squared_error -from scipy.interpolate import RBFInterpolator -from tensorflow.keras.models import Sequential -from tensorflow.keras.layers import Dense -import matplotlib.pyplot as plt +from sklearn.model_selection import KFold, cross_val_score +from sklearn.preprocessing import OneHotEncoder +from tensorflow.keras.layers import Dense +from tensorflow.keras.models import Sequential -def tune_rbf(X,y): + +def tune_rbf(X, y): def objective(trial): # Tune the RBFInterpolator # kernel - "multiquadric" (Can try other kernels) - epsilon = trial.suggest_float("epsilon",0.1,10.0, log=False) + epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) try: - kf = KFold(n_splits=5,shuffle=True,random_state=42) - return np.mean([ - mean_squared_error( - y[test], - RBFInterpolator(X[train],y[train],epsilon=epsilon,kernel="multiquadric")(X[test]) - ) - for train, test in kf.split(X) - ]) + kf = KFold(n_splits=5, shuffle=True, random_state=42) + return np.mean( + [ + mean_squared_error( + y[test], + RBFInterpolator( + X[train], + y[train], + epsilon=epsilon, + kernel="multiquadric", + )(X[test]), + ) + for train, test in kf.split(X) + ] + ) except: return float("inf") + study = optuna.create_study(direction="minimize") - study.optimize(objective,n_trials=20) + study.optimize(objective, n_trials=20) return study.best_params -def tune_rf(X,y): + +def tune_rf(X, y): # Tune the Random Forest def objective(trial): model = RandomForestRegressor( - n_estimators=trial.suggest_int("n_estimators",50,200), - max_depth = trial.suggest_int("max_depth",3,10), - min_samples_leaf = trial.suggest_int("min_samples_leaf",1,10), - random_state = 42 + n_estimators=trial.suggest_int("n_estimators", 50, 200), + max_depth=trial.suggest_int("max_depth", 3, 10), + min_samples_leaf=trial.suggest_int("min_samples_leaf", 1, 10), + random_state=42, + ) + scores = cross_val_score( + model, X, y, cv=5, scoring="neg_mean_squared_error" ) - scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error') return -np.mean(scores) + study = optuna.create_study(direction="minimize") - study.optimize(objective,n_trials=20) + study.optimize(objective, n_trials=20) return study.best_params -def tune_nn(X_encoded,y): + +def tune_nn(X_encoded, y): # Tune the Neural Network def objective(trial): - units = trial.suggest_int("n_units",16,128) - layers = trial.suggest_int("n_layers",1,3) - kf = KFold(n_splits=5,shuffle=True,random_state=42) - mses=[] + units = trial.suggest_int("n_units", 16, 128) + layers = trial.suggest_int("n_layers", 1, 3) + kf = KFold(n_splits=5, shuffle=True, random_state=42) + mses = [] for train, test in kf.split(X_encoded): model = Sequential() - model.add(Dense(units, activation='relu', input_shape=(X_encoded.shape[1],))) - for _ in range(layers-1): - model.add(Dense(units,activation='relu')) + model.add( + Dense( + units, activation="relu", input_shape=(X_encoded.shape[1],) + ) + ) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) model.add(Dense(1)) - model.compile(optimizer='adam',loss='mse') - model.fit(X_encoded[train],y[train],epochs=100,batch_size=16,verbose=0) - mses.append(mean_squared_error(y[test],model.predict(X_encoded[test]).flatten())) + model.compile(optimizer="adam", loss="mse") + model.fit( + X_encoded[train], + y[train], + epochs=100, + batch_size=16, + verbose=0, + ) + mses.append( + mean_squared_error( + y[test], model.predict(X_encoded[test]).flatten() + ) + ) return np.mean(mses) + study = optuna.create_study(direction="minimize") study.optimize(objective, n_trials=20) - return study.best_params + return study.best_params class Surrogatewrapper: # Wrapper that builds the surrogate model and predicts the QOI value for given X - def __init__(self,model_type,X,y,params): + def __init__(self, model_type, X, y, params): # Inputs: # model_type = 'rbf' for RBFInterpolator # = 'rf' for Random Forest # = 'nn' for Neural Network # X,y = the raw data # params = tune parameter of the surrogate model - - self.model_type = model_type - self.encoder = None - if model_type == 'rbf': - self.model = RBFInterpolator(X,y,kernel='multiquadric',epsilon=params["epsilon"]) - elif model_type == 'rf': + self.model_type = model_type + self.encoder = None + + if model_type == "rbf": + self.model = RBFInterpolator( + X, y, kernel="multiquadric", epsilon=params["epsilon"] + ) + elif model_type == "rf": self.model = RandomForestRegressor(**params, random_state=42) - self.model.fit(X,y) - elif model_type == 'nn': + self.model.fit(X, y) + elif model_type == "nn": self.encoder = OneHotEncoder(sparse_output=False) X_encoded = self.encoder.fit_transform(X) - self.model = self._build_nn(X_encoded.shape[1],params["n_units"],params["n_layers"]) - self.model.fit(X_encoded,y,epochs=100,batch_size=16,verbose=0) + self.model = self._build_nn( + X_encoded.shape[1], params["n_units"], params["n_layers"] + ) + self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) - - def _build_nn(self,input_dim, units, layers): + def _build_nn(self, input_dim, units, layers): # Builds the neural network model = Sequential() - model.add(Dense(units, activation='relu',input_shape=(input_dim,))) - for _ in range(layers-1): - model.add(Dense(units,activation='relu')) + model.add(Dense(units, activation="relu", input_shape=(input_dim,))) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) model.add(Dense(1)) - model.compile(optimizer='adam', loss='mse') - return model - - def predict(self,X): - X = X.reshape(1,-1) - if self.model_type == 'nn': + model.compile(optimizer="adam", loss="mse") + return model + + def predict(self, X): + X = X.reshape(1, -1) + if self.model_type == "nn": X = self.encoder.transform(X) return float(self.model.predict(X)[0]) - elif self.model_type == 'rf': + elif self.model_type == "rf": return float(self.model.predict(X)[0]) else: return float(self.model(X)[0]) - -def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,max_spargers=8,temp=10.0,alpha=0.95): - # Runs the Simulated Annealing (SA) and reports the resuts + + +def simulated_annealing_surrogate( + surrogate, dim=12, max_iters=1000, max_spargers=8, temp=10.0, alpha=0.95 +): + # Runs the Simulated Annealing (SA) and reports the results # Inputs: - # surrogate = tuned surrogate model + # surrogate = tuned surrogate model # dim = dimension of the problem # max_spargers = maximum number of spargers allowed # max_iters = maximum number of iterations for SA # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) # = max temperature. It controls the exploration rate of SA # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = cooling rate. It determines how quickly temp decays. + # = cooling rate. It determines how quickly temp decays. # Outputs: # x_best = optimal solution @@ -133,47 +169,56 @@ def simulated_annealing_surrogate(surrogate,dim=12,max_iters=1000,max_spargers=8 def is_valid(x): # Checks if the number of spargers <= max_spargers - return np.sum(x== 1) <= max_spargers - + return np.sum(x == 1) <= max_spargers + while True: # generate random starting point - x_curr = np.random.randint(0,3,size=dim) + x_curr = np.random.randint(0, 3, size=dim) if is_valid(x_curr): break y_curr = surrogate.predict(x_curr) - x_best, y_best = x_curr.copy(), y_curr + x_best, y_best = x_curr.copy(), y_curr - trace = [(0,y_best)] + trace = [(0, y_best)] for i in range(max_iters): # Optimization loop - + x_new = x_curr.copy() - idx = random.randint(0,dim-1) + idx = random.randint(0, dim - 1) # perturb a random dimension to get new x - x_new[idx] = (x_new[idx] + random.choice([-1,1])) % 3 - + x_new[idx] = (x_new[idx] + random.choice([-1, 1])) % 3 + if not is_valid(x_new): - trace.append((i,y_best)) + trace.append((i, y_best)) temp *= alpha continue - + y_new = surrogate.predict(x_new) delta = y_new - y_curr if delta < 0 or np.random.rand() < np.exp(-delta / temp): - x_curr, y_curr = x_new, y_new + x_curr, y_curr = x_new, y_new if y_curr < y_best: - x_best, y_best = x_new.copy(), y_new - - trace.append((i,y_best)) - temp *= alpha - + x_best, y_best = x_new.copy(), y_new + + trace.append((i, y_best)) + temp *= alpha + return x_best, y_best, trace -def run_optimization(X,y,model_type = 'rbf',max_spargers = 8,n_runs=10,max_iters = 1000, bootstrap_size=100): - # Bootstraps data, runs optimization and postprocesses the resutls. + +def run_optimization( + X, + y, + model_type="rbf", + max_spargers=8, + n_runs=10, + max_iters=1000, + bootstrap_size=100, +): + # Bootstraps data, runs optimization and postprocesses the results. # Inputs: # X,y = the raw data # model_type = 'rbf' for RBFInterpolator @@ -185,34 +230,42 @@ def run_optimization(X,y,model_type = 'rbf',max_spargers = 8,n_runs=10,max_iters all_x = [] all_y = [] - all_traces = np.zeros((n_runs, max_iters + 1)) + all_traces = np.zeros((n_runs, max_iters + 1)) rng = np.random.default_rng(42) # Bootstrap the data - bootstrap_idxs = [rng.choice(len(X), size = bootstrap_size, replace = False) for _ in range(n_runs)] - + bootstrap_idxs = [ + rng.choice(len(X), size=bootstrap_size, replace=False) + for _ in range(n_runs) + ] + i = 0 for idxs in bootstrap_idxs: # Tune the model for each bootstrap X_sub, y_sub = X[idxs], y[idxs] if model_type == "rbf": - params = tune_rbf(X_sub,y_sub) + params = tune_rbf(X_sub, y_sub) elif model_type == "rf": - params = tune_rf(X_sub,y_sub) + params = tune_rf(X_sub, y_sub) elif model_type == "nn": encoder = OneHotEncoder(sparse_output=False) X_encoded = encoder.fit_transform(X_sub) - params = tune_nn(X_encoded,y_sub) + params = tune_nn(X_encoded, y_sub) else: raise ValueError("Invalid model_type") # build the surrogate model - surrogate = Surrogatewrapper(model_type,X_sub,y_sub,params) + surrogate = Surrogatewrapper(model_type, X_sub, y_sub, params) # run optimization - x_best, y_best, trace = simulated_annealing_surrogate(surrogate, dim=X.shape[1], max_iters=max_iters, max_spargers=max_spargers) - + x_best, y_best, trace = simulated_annealing_surrogate( + surrogate, + dim=X.shape[1], + max_iters=max_iters, + max_spargers=max_spargers, + ) + trace_y = [y for _, y in trace] - all_traces[i,:] = trace_y + all_traces[i, :] = trace_y all_x.append(x_best) all_y.append(y_best) i = i + 1 @@ -221,43 +274,74 @@ def run_optimization(X,y,model_type = 'rbf',max_spargers = 8,n_runs=10,max_iters best_index = np.argmin(all_y) best_x = all_x[best_index] best_y = all_y[best_index] - # Save the best solution - df = pd.DataFrame([{ - **{f"x{i}": best_x[i] for i in range(len(best_x))}, - "best_y": best_y - }]) - df.to_csv(f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.csv", index=False) + # Save the best solution + df = pd.DataFrame( + [ + { + **{f"x{i}": best_x[i] for i in range(len(best_x))}, + "best_y": best_y, + } + ] + ) + df.to_csv( + f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.csv", + index=False, + ) print("X = ", x_best) - print("surrogate-predicted y",y_best) + print("surrogate-predicted y", y_best) # Make the mean-CI plot for the objective function and save it - mean_trace = np.mean(-1*all_traces,axis=0) - std_trace = np.std(all_traces,axis=0) - lower_bound = mean_trace - 1.96* std_trace / np.sqrt(n_runs) - upper_bound = mean_trace + 1.96* std_trace / np.sqrt(n_runs) + mean_trace = np.mean(-1 * all_traces, axis=0) + std_trace = np.std(all_traces, axis=0) + lower_bound = mean_trace - 1.96 * std_trace / np.sqrt(n_runs) + upper_bound = mean_trace + 1.96 * std_trace / np.sqrt(n_runs) iterations = np.arange(max_iters + 1) - plt.figure(figsize=(8,4)) - plt.plot(iterations,mean_trace,label=f"Mean Convergence {model_type.upper()}", color = "blue") - plt.fill_between(iterations, lower_bound, upper_bound, color = "blue", alpha = 0.3, label = "95% CI") + plt.figure(figsize=(8, 4)) + plt.plot( + iterations, + mean_trace, + label=f"Mean Convergence {model_type.upper()}", + color="blue", + ) + plt.fill_between( + iterations, + lower_bound, + upper_bound, + color="blue", + alpha=0.3, + label="95% CI", + ) plt.xlabel("Iteration") plt.ylabel("Best Surrogate-Predicted Objective") - plt.title(f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})") + plt.title( + f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})" + ) plt.grid(True) - plt.legend() - plt.savefig(f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png",dpi=300) + plt.legend() + plt.savefig( + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png", + dpi=300, + ) plt.show() - # Read data from the csv file. -X_raw_data = pd.read_csv('Xdata_study_scaleup_0_4vvm_3000W.csv') -y_raw_data = pd.read_csv('ydata_study_scaleup_0_4vvm_3000W.csv') +X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") +y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") X = X_raw_data.values -y = y_raw_data.iloc[:,:-1].values -y = y*-1 +y = y_raw_data.iloc[:, :-1].values +y = y * -1 # The function will build, tune, and optimize the surrogate model and postprocess the results. -run_optimization(X,y,model_type="rbf",max_spargers = 8, n_runs = 10,max_iters = 1000, bootstrap_size=100) \ No newline at end of file +run_optimization( + X, + y, + model_type="rbf", + max_spargers=8, + n_runs=10, + max_iters=1000, + bootstrap_size=100, +) From e1382f6b81cd332547646583079c1f52eb2f026e Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 10:14:31 -0600 Subject: [PATCH 21/86] fix typos in readme --- bird/postprocess/SA_optimization/README.md | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bird/postprocess/SA_optimization/README.md b/bird/postprocess/SA_optimization/README.md index 3d79d9db..941e4a64 100644 --- a/bird/postprocess/SA_optimization/README.md +++ b/bird/postprocess/SA_optimization/README.md @@ -1,27 +1,27 @@ # Surrogate-Based Optimization with Simulated Annealing -This project implements a complete framework for surrogate-based optimization using Simulated Annealing (SA). It supports three surrogate models: +Implementation of surrogate-based design optimization with Simulated Annealing (SA). It supports three surrogate models: - Radial Basis Function Interpolator ('rbf') - Random Forest ('rf') - Neural Network ('nn') -The SA optimizer operates on discrete feature values (0,1,2) with an option to restrict the number of spargers (1) to a fixed value (max_saprgers). +The SA optimizer operates on discrete feature values (0,1,2) with an option to restrict the number of spargers (1) to a fixed value (max_spargers). -- Preprocessing the data ('get_csv.py') - - reads the 'configs.pkl' and 'results.pkl' files from a study - - saves the configuration in 'Xdata_{study_name}.csv' file - - save the qoi and qoi_error in 'ydata_{study_name}.csv' file +- Preprocessing the data (`get_csv.py`) + - reads the `configs.pkl` and `results.pkl` files from a study + - saves the configuration in `Xdata_{study_name}.csv` file + - save the qoi and qoi_error in `ydata_{study_name}.csv` file -- Surrogate modeling and optimization ('get_optimal.py'/'get_optimal_with_constraints.py') +- Surrogate modeling and optimization (`get_optimal.py` and `get_optimal_with_constraints.py`) - run_optimization(...) function sets up the surrogate-based optimization: - Inputs: - - X: read from 'Xdata_{study_name}.csv' file - - y: read from 'ydata_{study_name}.csv' file - - model_type: type of surrogate model (default = 'rbf') - - model_type = 'rbf': Radial Basis Function - - model_type = 'rf': Random Forest - - model_type = 'nn': Neural Network - - max_spargers: maximum number of spargers (only in 'get_optimal_with_constraints.py') (default = 8) + - X: read from `Xdata_{study_name}.csv` file + - y: read from `ydata_{study_name}.csv` file + - model_type: type of surrogate model (default = `rbf`) + - model_type = `rbf`: Radial Basis Function + - model_type = `rf`: Random Forest + - model_type = `nn`: Neural Network + - max_spargers: maximum number of spargers (only in `get_optimal_with_constraints.py`) (default = 8) - n_runs: number of bootstrap runs (default = 10) - max_iters: maximum number of iterations of SA (default = 1000) - bootstrap_size: sample size of each bootstrap (default = 100) @@ -30,10 +30,10 @@ The SA optimizer operates on discrete feature values (0,1,2) with an option to r - If SA is too slow or fails to converge, you can change the following parameters: - temp: maximum temperature of SA. Controls exploration. - alpha: the rate at which temperature changes every iteration. - - It returen the optimal solutions along with the optimization logs which are used to make plots in postprocessing. + - It returns the optimal solutions along with the optimization logs which are used to make plots in postprocessing. - Once the optimization is done the best solution is saved in a csv file and the following plots are made: - Mean-CI plot of the objective function (qoi) - - Mean-CI plot of the distance of an iterate from the optimal solution (this is not done in 'get_optimal_with_constraints.py'). + - Mean-CI plot of the distance of an iterate from the optimal solution (this is not done in `get_optimal_with_constraints.py`). Python dependencies: - numpy From 00bdb3d452e4e1494908771dcd62159d92f88b16 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 10:33:34 -0600 Subject: [PATCH 22/86] typo in pickle import --- bird/postprocess/SA_optimization/get_csv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py index 02dd1191..5729ea74 100644 --- a/bird/postprocess/SA_optimization/get_csv.py +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -2,7 +2,7 @@ import os import numpy as np -import pickle5 as pkl +import pickle as pkl def get_config_result(config_fold): From a310482f4aa10dc88ee0461661d4fa7172284928 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 11:49:13 -0600 Subject: [PATCH 23/86] add dependencies to setup --- bird/postprocess/SA_optimization/README.md | 22 ++++++------ bird/postprocess/SA_optimization/get_csv.py | 38 +++++++++++++++------ setup.py | 4 +++ 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/bird/postprocess/SA_optimization/README.md b/bird/postprocess/SA_optimization/README.md index 941e4a64..454a45e9 100644 --- a/bird/postprocess/SA_optimization/README.md +++ b/bird/postprocess/SA_optimization/README.md @@ -1,5 +1,17 @@ # Surrogate-Based Optimization with Simulated Annealing +## Install dependencies + +``` + conda create --name bird python=3.10 + conda activate bird + git clone https://github.com/NREL/BioReactorDesign.git + cd BioReactorDesign + pip install -e .[optim] +``` + +## Examples + Implementation of surrogate-based design optimization with Simulated Annealing (SA). It supports three surrogate models: - Radial Basis Function Interpolator ('rbf') - Random Forest ('rf') @@ -35,13 +47,3 @@ The SA optimizer operates on discrete feature values (0,1,2) with an option to r - Mean-CI plot of the objective function (qoi) - Mean-CI plot of the distance of an iterate from the optimal solution (this is not done in `get_optimal_with_constraints.py`). -Python dependencies: -- numpy -- pandas -- scikit-learn -- scipy -- optuna -- matplotlib -- warnings -- random -- tensorflow diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py index 5729ea74..b3f2426e 100644 --- a/bird/postprocess/SA_optimization/get_csv.py +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -5,16 +5,34 @@ import pickle as pkl -def get_config_result(config_fold): - with open(os.path.join(config_fold, "results.pkl"), "rb") as f: +def get_config_result(study_fold:str=".") -> None: + """ + Read the configs.pkl and results.pkl files from a study + Saves the configuration in Xdata_{study_fold}.csv file + Save the qoi and qoi_error in ydata_{study_fold}.csv file + + Parameters + ---------- + study_fold : str + Folder that contains the study results + + Returns + ---------- + None + + """ + # Read results + with open(os.path.join(study_fold, "results.pkl"), "rb") as f: results = pkl.load(f) - with open(os.path.join(config_fold, "configs.pkl"), "rb") as f: + with open(os.path.join(study_fold, "configs.pkl"), "rb") as f: configs = pkl.load(f) + Xdata = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], np.int64) count = 0 + # Save data into CSV files - xfname = "Xdata_" + config_fold + ".csv" - yfname = "ydata_" + config_fold + ".csv" + xfname = f"Xdata_{study_fold}.csv" + yfname = f"ydata_{study_fold}.csv" with open(xfname, "w", newline="") as csvfile: writer = csv.writer(csvfile) for sims in results: @@ -32,9 +50,7 @@ def get_config_result(config_fold): y_data = np.concatenate((q0, q1), axis=None) writer.writerow(y_data) - -studies = {"study_scaleup_0_4vvm_3000W": r"0.0036$m^3$ 0.4vvm 0W"} - - -for study in studies: - get_config_result(study) +if __name__ == "__main__": + studies = {"study_scaleup_0_4vvm_3000W": r"0.0036$m^3$ 0.4vvm 0W"} + for study in studies: + get_config_result(study) diff --git a/setup.py b/setup.py index 4eaa7bd2..49263f60 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,10 @@ "scikit-learn", "tf2jax", ], + "optim": [ + "optuna", + "pandas", + ], }, project_urls={ "Documentation": "https://nrel.github.io/BioReactorDesign/", From 3dbb89281afe17a264052e2164952825dbbf9608 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 14:41:36 -0600 Subject: [PATCH 24/86] add doc and type hinting --- bird/postprocess/SA_optimization/get_csv.py | 9 +- .../SA_optimization/get_optimal.py | 334 ++++++++++++++---- 2 files changed, 265 insertions(+), 78 deletions(-) diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py index b3f2426e..1876d485 100644 --- a/bird/postprocess/SA_optimization/get_csv.py +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -1,11 +1,11 @@ import csv import os +import pickle as pkl import numpy as np -import pickle as pkl -def get_config_result(study_fold:str=".") -> None: +def get_config_result(study_fold: str = ".") -> None: """ Read the configs.pkl and results.pkl files from a study Saves the configuration in Xdata_{study_fold}.csv file @@ -15,10 +15,10 @@ def get_config_result(study_fold:str=".") -> None: ---------- study_fold : str Folder that contains the study results - + Returns ---------- - None + None """ # Read results @@ -50,6 +50,7 @@ def get_config_result(study_fold:str=".") -> None: y_data = np.concatenate((q0, q1), axis=None) writer.writerow(y_data) + if __name__ == "__main__": studies = {"study_scaleup_0_4vvm_3000W": r"0.0036$m^3$ 0.4vvm 0W"} for study in studies: diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index 43c86bbb..b418a231 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -1,23 +1,70 @@ import random import warnings -import matplotlib.pyplot as plt import numpy as np import optuna import pandas as pd +from prettyPlot.plotting import * from scipy.interpolate import RBFInterpolator from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error from sklearn.model_selection import KFold, cross_val_score from sklearn.preprocessing import OneHotEncoder from tensorflow.keras.layers import Dense +from tensorflow.keras.models import Model as tfModel from tensorflow.keras.models import Sequential -warningsl.filterwarnings("ignore") - - -def tune_rbf(X, y): - # Tune the RBFInterpolator +warnings.filterwarnings("ignore") + + +def check_data_shape(X: np.ndarray, y: np.ndarray) -> None: + """ + Tune the shape parameter (epsilon) of the multiquadratic RBF + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + """ + + # Same number of samples + assert X.shape[0] == y.shape[0] + # Arrays are 2 dimensional + assert len(X.shape) == 2 + assert len(y.shape) == 2 + # only 1 QoI + assert y.shape[1] == 1 + print(f"INFO: {X.shape[0]} sim with {X.shape[1]} design variables") + + +def tune_rbf(X: np.ndarray, y: np.ndarray) -> dict: + """ + Tune the shape parameter (epsilon) of the multiquadratic RBF + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + + check_data_shape(X=X, y=y) + + # Tune the RBFInterpolator with optuna # kernel - "multiquadric" (Can try other kernels) def objective(trial): epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) @@ -45,7 +92,32 @@ def objective(trial): return study.best_params -def tune_rf(X, y): +def tune_rf(X: np.ndarray, y: np.ndarray): + """ + Tune the number of trees (n_estimators) + tree depth (max_depth) + number of samples in a leaf (min_samples_leaf) + of the RandomForestRegressor + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + + check_data_shape(X=X, y=y) + # Tune the Random Forest def objective(trial): model = RandomForestRegressor( @@ -64,7 +136,31 @@ def objective(trial): return study.best_params -def tune_nn(X_encoded, y): +def tune_nn(X_encoded: np.ndarray, y: np.ndarray) -> dict: + """ + Tune the number of neurons (n_units) + number of layers (n_layers) + + in a leaf (min_samples_leaf) of the RandomForestRegressor + + Parameters + ---------- + X_encoded : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + check_data_shape(X=X_encoded, y=y) + # Tune the Neural Network def objective(trial): units = trial.suggest_int("n_units", 16, 128) @@ -101,36 +197,72 @@ def objective(trial): return study.best_params -class Surrogatewrapper: - # Wrapper that builds the surrogate model and predicts the QOI value for given X - def __init__(self, model_type, X, y, params): - # Inputs: - # model_type = 'rbf' for RBFInterpolator - # = 'rf' for Random Forest - # = 'nn' for Neural Network - # X,y = the raw data - # params = tune parameter of the surrogate model - +class Surrogate_wrapper: + """ + Wrapper that builds the surrogate model and predicts the QOI value for given X + """ + + def __init__( + self, model_type: str, X: np.ndarray, y: np.ndarray, params: dict + ): + """ + Create the surroagte wrapper + + Parameters + ---------- + model_type : str + 'rbf' for RBFInterpolator + 'rf' for Random Forest + 'nn' for Neural Network + + X: np.ndarray + Raw design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Raw array of quantity of interest + Dimension N by 1, where N is the number of simulations + params: dict + dictionary of surrogate parameters + + """ self.model_type = model_type self.encoder = None - if model_type == "rbf": + if model_type.lower() == "rbf": self.model = RBFInterpolator( X, y, kernel="multiquadric", epsilon=params["epsilon"] ) - elif model_type == "rf": + elif model_type.lower() == "rf": self.model = RandomForestRegressor(**params, random_state=42) self.model.fit(X, y) - elif model_type == "nn": + elif model_type.lower() == "nn": self.encoder = OneHotEncoder(sparse_output=False) X_encoded = self.encoder.fit_transform(X) self.model = self._build_nn( X_encoded.shape[1], params["n_units"], params["n_layers"] ) self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) - - def _build_nn(self, input_dim, units, layers): - # Builds the neural network + else: + raise NotImplementedError + + def _build_nn(self, input_dim: int, units: int, layers: int) -> tfModel: + """ + Builds the neural net + + Parameters + ---------- + input_dim : int + number of features + units: int + number of neurons per layer + layers: int + number of hidden layers + + Returns + ---------- + model: tfModel + """ model = Sequential() model.add(Dense(units, activation="relu", input_shape=(input_dim,))) for _ in range(layers - 1): @@ -151,23 +283,41 @@ def predict(self, X): def simulated_annealing_surrogate( - surrogate, dim=12, max_iters=1000, temp=10.0, alpha=0.95 -): - # Runs the Simulated Annealing (SA) and reports the results - # Inputs: - # surrogate = tuned surrogate model - # dim = dimension of the problem - # max_iters = maximum number of iterations for SA - # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = max temperature. It controls the exploration rate of SA - # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = cooling rate. It determines how quickly temp decays. - - # Outputs: - # x_best = optimal solution - # y_best = optimal objective function value - # trace = optimization log. Updates at the end of each iteration. - # trace_x = tracks how X changes during each iteration. + surrogate: Surrogate_wrapper, + dim: int = 12, + max_iters: int = 1000, + temp: float = 10.0, + alpha: float = 0.95, +) -> tuple[np.ndarray, float, list[tuple], list[np.ndarray]]: + """ + Runs the Simulated Annealing (SA) and reports the results + + Parameters + ---------- + surrogate : Surrogate_wrapper + tuned surrogate model + dim: int + dimension of the problem + max_iters: int + maximum number of iterations for SA + temp: float + parameter of Simulated Annealing (can be changed or tuned for other problems) + max temperature. It controls the exploration rate of SA + alpha: + parameter of Simulated Annealing (can be changed or tuned for other problems) + cooling rate. It determines how quickly temp decays + + Returns + ---------- + x_best: np.ndarray + optimal solution + y_best: float + optimal objective function value + trace: list[tuple] + optimization log. Updates at the end of each iteration. + trace_x: list[np.ndarray] + tracks how X changes during each iteration. + """ # generate random starting point x_curr = np.random.randint(0, 3, size=dim) @@ -196,22 +346,45 @@ def simulated_annealing_surrogate( trace.append((i, y_best)) trace_x.append(x_curr.copy()) temp *= alpha - return x_best, y_best, trace, trace_x def run_optimization( - X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=100 + X: np.ndarray, + y: np.ndarray, + model_type: str = "rbf", + n_runs: int = 10, + max_iters: int = 1000, + bootstrap_size: int = 100, ): - # Bootstraps data, runs optimization and postprocesses the results. - # Inputs: - # X,y = the raw data - # model_type = 'rbf' for RBFInterpolator - # = 'rf' for Random Forest - # = 'nn' for Neural Network - # n_runs = number of bootstraps - # max_iters = maximum number of SA iterations - # bootstrap_size = size of bootstrap samples + """ + Bootstraps data, runs optimization and postprocesses the results. + + Parameters + ---------- + X: np.ndarray + Raw design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Raw array of quantity of interest + Dimension N by 1, where N is the number of simulations + + model_type : str + 'rbf' for RBFInterpolator + 'rf' for Random Forest + 'nn' for Neural Network + + n_runs :int + number of bootstraps + max_iters: int + maximum number of SA iterations + bootstrap_size : int + size of bootstrap samples + + Returns + ---------- + """ all_x = [] all_y = [] @@ -240,7 +413,7 @@ def run_optimization( raise ValueError("Invalid model_type") # build the surrogate model - surrogate = Surrogatewrapper(model_type, X_sub, y_sub, params) + surrogate = Surrogate_wrapper(model_type, X_sub, y_sub, params) # run optimization x_best, y_best, trace, trace_x = simulated_annealing_surrogate( @@ -294,17 +467,22 @@ def run_optimization( alpha=0.3, label="95% CI", ) - plt.xlabel("Iteration") - plt.ylabel("Best Surrogate-Predicted Objective") - plt.title( - f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})" + pretty_labels( + "Iteration", + "Best Surrogate-Predicted Objective", + fontsize=16, + title=f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})", + grid=True, + fontname="Times", ) - plt.grid(True) - plt.legend() + pretty_legend(fontsize=16, fontname="Times") plt.savefig( f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png", dpi=300, ) + plt.savefig( + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.pdf", + ) plt.show() # Compute the distance of intermediate x from the optimal solution @@ -332,27 +510,35 @@ def run_optimization( plt.fill_between( iterations, lower, upper, alpha=0.3, color="darkred", label="95% CI" ) - plt.xlabel("Iteration") - plt.ylabel("L1 Distance from Optimal") - plt.title("Convergence Toward Optimal Solution (L1 Norm)") - plt.grid(True) - plt.legend() + pretty_labels( + "Iteration", + "L1 Distance from Optimal", + fontsize=16, + title=f"Convergence Toward Optimal Solution (L1 Norm)", + grid=True, + fontname="Times", + ) + pretty_legend(fontsize=16, fontname="Times") plt.tight_layout() plt.savefig( f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png", dpi=300 ) + plt.savefig(f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.pdf") plt.show() -# Read data from the csv file. -X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") -y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") +if __name__ == "__main__": + + # Read data from the csv file. + X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") + y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") -X = X_raw_data.values -y = y_raw_data.iloc[:, :-1].values -y = y * -1 + X = X_raw_data.values + y = y_raw_data.iloc[:, :-1].values + # We want to maximize, so we minimize the opposite + y = y * -1 -# The function will build, tune, and optimize the surrogate model and postprocess the results. -run_optimization( - X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=150 -) + # The function will build, tune, and optimize the surrogate model and postprocess the results. + run_optimization( + X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=150 + ) From 5c0c719fa978d1881d99fc0ca3b2b1f365fd757c Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 14:51:39 -0600 Subject: [PATCH 25/86] split surrogate and optimization --- .../SA_optimization/get_optimal.py | 275 +---------------- bird/postprocess/SA_optimization/surrogate.py | 282 ++++++++++++++++++ 2 files changed, 283 insertions(+), 274 deletions(-) create mode 100644 bird/postprocess/SA_optimization/surrogate.py diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index b418a231..da0851e5 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -2,286 +2,13 @@ import warnings import numpy as np -import optuna import pandas as pd from prettyPlot.plotting import * -from scipy.interpolate import RBFInterpolator -from sklearn.ensemble import RandomForestRegressor -from sklearn.metrics import mean_squared_error -from sklearn.model_selection import KFold, cross_val_score from sklearn.preprocessing import OneHotEncoder -from tensorflow.keras.layers import Dense -from tensorflow.keras.models import Model as tfModel -from tensorflow.keras.models import Sequential - +from bird.postprocess.SA_optimization.surrogate import tune_rf, tune_rbf, tune_nn, Surrogate_wrapper warnings.filterwarnings("ignore") -def check_data_shape(X: np.ndarray, y: np.ndarray) -> None: - """ - Tune the shape parameter (epsilon) of the multiquadratic RBF - - Parameters - ---------- - X : np.ndarray - Design configuration - Dimension N by d, where N is the number of simulations, - and d is the number of design variables - y: np.ndarray - Array of quantity of interest - Dimension N by 1, where N is the number of simulations - """ - - # Same number of samples - assert X.shape[0] == y.shape[0] - # Arrays are 2 dimensional - assert len(X.shape) == 2 - assert len(y.shape) == 2 - # only 1 QoI - assert y.shape[1] == 1 - print(f"INFO: {X.shape[0]} sim with {X.shape[1]} design variables") - - -def tune_rbf(X: np.ndarray, y: np.ndarray) -> dict: - """ - Tune the shape parameter (epsilon) of the multiquadratic RBF - - Parameters - ---------- - X : np.ndarray - Design configuration - Dimension N by d, where N is the number of simulations, - and d is the number of design variables - y: np.ndarray - Array of quantity of interest - Dimension N by 1, where N is the number of simulations - Returns - ---------- - params: dict - dictionary of optimal parameters - - """ - - check_data_shape(X=X, y=y) - - # Tune the RBFInterpolator with optuna - # kernel - "multiquadric" (Can try other kernels) - def objective(trial): - epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) - try: - kf = KFold(n_splits=5, shuffle=True, random_state=42) - return np.mean( - [ - mean_squared_error( - y[test], - RBFInterpolator( - X[train], - y[train], - epsilon=epsilon, - kernel="multiquadric", - )(X[test]), - ) - for train, test in kf.split(X) - ] - ) - except: - return float("inf") - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -def tune_rf(X: np.ndarray, y: np.ndarray): - """ - Tune the number of trees (n_estimators) - tree depth (max_depth) - number of samples in a leaf (min_samples_leaf) - of the RandomForestRegressor - - Parameters - ---------- - X : np.ndarray - Design configuration - Dimension N by d, where N is the number of simulations, - and d is the number of design variables - y: np.ndarray - Array of quantity of interest - Dimension N by 1, where N is the number of simulations - - Returns - ---------- - params: dict - dictionary of optimal parameters - - """ - - check_data_shape(X=X, y=y) - - # Tune the Random Forest - def objective(trial): - model = RandomForestRegressor( - n_estimators=trial.suggest_int("n_estimators", 50, 200), - max_depth=trial.suggest_int("max_depth", 3, 10), - min_samples_leaf=trial.suggest_int("min_samples_leaf", 1, 10), - random_state=42, - ) - scores = cross_val_score( - model, X, y, cv=5, scoring="neg_mean_squared_error" - ) - return -np.mean(scores) - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -def tune_nn(X_encoded: np.ndarray, y: np.ndarray) -> dict: - """ - Tune the number of neurons (n_units) - number of layers (n_layers) - - in a leaf (min_samples_leaf) of the RandomForestRegressor - - Parameters - ---------- - X_encoded : np.ndarray - Design configuration - Dimension N by d, where N is the number of simulations, - and d is the number of design variables - y: np.ndarray - Array of quantity of interest - Dimension N by 1, where N is the number of simulations - - Returns - ---------- - params: dict - dictionary of optimal parameters - - """ - check_data_shape(X=X_encoded, y=y) - - # Tune the Neural Network - def objective(trial): - units = trial.suggest_int("n_units", 16, 128) - layers = trial.suggest_int("n_layers", 1, 3) - kf = KFold(n_splits=5, shuffle=True, random_state=42) - mses = [] - for train, test in kf.split(X_encoded): - model = Sequential() - model.add( - Dense( - units, activation="relu", input_shape=(X_encoded.shape[1],) - ) - ) - for _ in range(layers - 1): - model.add(Dense(units, activation="relu")) - model.add(Dense(1)) - model.compile(optimizer="adam", loss="mse") - model.fit( - X_encoded[train], - y[train], - epochs=100, - batch_size=16, - verbose=0, - ) - mses.append( - mean_squared_error( - y[test], model.predict(X_encoded[test]).flatten() - ) - ) - return np.mean(mses) - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -class Surrogate_wrapper: - """ - Wrapper that builds the surrogate model and predicts the QOI value for given X - """ - - def __init__( - self, model_type: str, X: np.ndarray, y: np.ndarray, params: dict - ): - """ - Create the surroagte wrapper - - Parameters - ---------- - model_type : str - 'rbf' for RBFInterpolator - 'rf' for Random Forest - 'nn' for Neural Network - - X: np.ndarray - Raw design configuration - Dimension N by d, where N is the number of simulations, - and d is the number of design variables - y: np.ndarray - Raw array of quantity of interest - Dimension N by 1, where N is the number of simulations - params: dict - dictionary of surrogate parameters - - """ - self.model_type = model_type - self.encoder = None - - if model_type.lower() == "rbf": - self.model = RBFInterpolator( - X, y, kernel="multiquadric", epsilon=params["epsilon"] - ) - elif model_type.lower() == "rf": - self.model = RandomForestRegressor(**params, random_state=42) - self.model.fit(X, y) - elif model_type.lower() == "nn": - self.encoder = OneHotEncoder(sparse_output=False) - X_encoded = self.encoder.fit_transform(X) - self.model = self._build_nn( - X_encoded.shape[1], params["n_units"], params["n_layers"] - ) - self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) - else: - raise NotImplementedError - - def _build_nn(self, input_dim: int, units: int, layers: int) -> tfModel: - """ - Builds the neural net - - Parameters - ---------- - input_dim : int - number of features - units: int - number of neurons per layer - layers: int - number of hidden layers - - Returns - ---------- - model: tfModel - """ - model = Sequential() - model.add(Dense(units, activation="relu", input_shape=(input_dim,))) - for _ in range(layers - 1): - model.add(Dense(units, activation="relu")) - model.add(Dense(1)) - model.compile(optimizer="adam", loss="mse") - return model - - def predict(self, X): - X = X.reshape(1, -1) - if self.model_type == "nn": - X = self.encoder.transform(X) - return float(self.model.predict(X)[0]) - elif self.model_type == "rf": - return float(self.model.predict(X)[0]) - else: - return float(self.model(X)[0]) - - def simulated_annealing_surrogate( surrogate: Surrogate_wrapper, dim: int = 12, diff --git a/bird/postprocess/SA_optimization/surrogate.py b/bird/postprocess/SA_optimization/surrogate.py new file mode 100644 index 00000000..79a3f9c9 --- /dev/null +++ b/bird/postprocess/SA_optimization/surrogate.py @@ -0,0 +1,282 @@ +import random +import warnings + +import numpy as np +import optuna +import pandas as pd +from scipy.interpolate import RBFInterpolator +from sklearn.ensemble import RandomForestRegressor +from sklearn.metrics import mean_squared_error +from sklearn.model_selection import KFold, cross_val_score +from sklearn.preprocessing import OneHotEncoder +from tensorflow.keras.layers import Dense +from tensorflow.keras.models import Model as tfModel +from tensorflow.keras.models import Sequential + +warnings.filterwarnings("ignore") + + +def check_data_shape(X: np.ndarray, y: np.ndarray) -> None: + """ + Tune the shape parameter (epsilon) of the multiquadratic RBF + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + """ + + # Same number of samples + assert X.shape[0] == y.shape[0] + # Arrays are 2 dimensional + assert len(X.shape) == 2 + assert len(y.shape) == 2 + # only 1 QoI + assert y.shape[1] == 1 + print(f"INFO: {X.shape[0]} sim with {X.shape[1]} design variables") + + +def tune_rbf(X: np.ndarray, y: np.ndarray) -> dict: + """ + Tune the shape parameter (epsilon) of the multiquadratic RBF + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + + check_data_shape(X=X, y=y) + + # Tune the RBFInterpolator with optuna + # kernel - "multiquadric" (Can try other kernels) + def objective(trial): + epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) + try: + kf = KFold(n_splits=5, shuffle=True, random_state=42) + return np.mean( + [ + mean_squared_error( + y[test], + RBFInterpolator( + X[train], + y[train], + epsilon=epsilon, + kernel="multiquadric", + )(X[test]), + ) + for train, test in kf.split(X) + ] + ) + except: + return float("inf") + + study = optuna.create_study(direction="minimize") + study.optimize(objective, n_trials=20) + return study.best_params + + +def tune_rf(X: np.ndarray, y: np.ndarray): + """ + Tune the number of trees (n_estimators) + tree depth (max_depth) + number of samples in a leaf (min_samples_leaf) + of the RandomForestRegressor + + Parameters + ---------- + X : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + + check_data_shape(X=X, y=y) + + # Tune the Random Forest + def objective(trial): + model = RandomForestRegressor( + n_estimators=trial.suggest_int("n_estimators", 50, 200), + max_depth=trial.suggest_int("max_depth", 3, 10), + min_samples_leaf=trial.suggest_int("min_samples_leaf", 1, 10), + random_state=42, + ) + scores = cross_val_score( + model, X, y, cv=5, scoring="neg_mean_squared_error" + ) + return -np.mean(scores) + + study = optuna.create_study(direction="minimize") + study.optimize(objective, n_trials=20) + return study.best_params + + +def tune_nn(X_encoded: np.ndarray, y: np.ndarray) -> dict: + """ + Tune the number of neurons (n_units) + number of layers (n_layers) + + in a leaf (min_samples_leaf) of the RandomForestRegressor + + Parameters + ---------- + X_encoded : np.ndarray + Design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Array of quantity of interest + Dimension N by 1, where N is the number of simulations + + Returns + ---------- + params: dict + dictionary of optimal parameters + + """ + check_data_shape(X=X_encoded, y=y) + + # Tune the Neural Network + def objective(trial): + units = trial.suggest_int("n_units", 16, 128) + layers = trial.suggest_int("n_layers", 1, 3) + kf = KFold(n_splits=5, shuffle=True, random_state=42) + mses = [] + for train, test in kf.split(X_encoded): + model = Sequential() + model.add( + Dense( + units, activation="relu", input_shape=(X_encoded.shape[1],) + ) + ) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) + model.add(Dense(1)) + model.compile(optimizer="adam", loss="mse") + model.fit( + X_encoded[train], + y[train], + epochs=100, + batch_size=16, + verbose=0, + ) + mses.append( + mean_squared_error( + y[test], model.predict(X_encoded[test]).flatten() + ) + ) + return np.mean(mses) + + study = optuna.create_study(direction="minimize") + study.optimize(objective, n_trials=20) + return study.best_params + + +class Surrogate_wrapper: + """ + Wrapper that builds the surrogate model and predicts the QOI value for given X + """ + + def __init__( + self, model_type: str, X: np.ndarray, y: np.ndarray, params: dict + ): + """ + Create the surroagte wrapper + + Parameters + ---------- + model_type : str + 'rbf' for RBFInterpolator + 'rf' for Random Forest + 'nn' for Neural Network + + X: np.ndarray + Raw design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Raw array of quantity of interest + Dimension N by 1, where N is the number of simulations + params: dict + dictionary of surrogate parameters + + """ + self.model_type = model_type + self.encoder = None + + if model_type.lower() == "rbf": + self.model = RBFInterpolator( + X, y, kernel="multiquadric", epsilon=params["epsilon"] + ) + elif model_type.lower() == "rf": + self.model = RandomForestRegressor(**params, random_state=42) + self.model.fit(X, y) + elif model_type.lower() == "nn": + self.encoder = OneHotEncoder(sparse_output=False) + X_encoded = self.encoder.fit_transform(X) + self.model = self._build_nn( + X_encoded.shape[1], params["n_units"], params["n_layers"] + ) + self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) + else: + raise NotImplementedError + + def _build_nn(self, input_dim: int, units: int, layers: int) -> tfModel: + """ + Builds the neural net + + Parameters + ---------- + input_dim : int + number of features + units: int + number of neurons per layer + layers: int + number of hidden layers + + Returns + ---------- + model: tfModel + """ + model = Sequential() + model.add(Dense(units, activation="relu", input_shape=(input_dim,))) + for _ in range(layers - 1): + model.add(Dense(units, activation="relu")) + model.add(Dense(1)) + model.compile(optimizer="adam", loss="mse") + return model + + def predict(self, X): + X = X.reshape(1, -1) + if self.model_type == "nn": + X = self.encoder.transform(X) + return float(self.model.predict(X)[0]) + elif self.model_type == "rf": + return float(self.model.predict(X)[0]) + else: + return float(self.model(X)[0]) + From 188e7b6971bb6c4eca342bba4465f742eb863890 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 15:01:32 -0600 Subject: [PATCH 26/86] doc string type hinting and reuse surrogate functions --- .../SA_optimization/get_optimal.py | 9 +- .../get_optimal_with_constraint.py | 311 +++++++----------- bird/postprocess/SA_optimization/surrogate.py | 1 - 3 files changed, 120 insertions(+), 201 deletions(-) diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index da0851e5..b667fbb9 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -5,7 +5,14 @@ import pandas as pd from prettyPlot.plotting import * from sklearn.preprocessing import OneHotEncoder -from bird.postprocess.SA_optimization.surrogate import tune_rf, tune_rbf, tune_nn, Surrogate_wrapper + +from bird.postprocess.SA_optimization.surrogate import ( + Surrogate_wrapper, + tune_nn, + tune_rbf, + tune_rf, +) + warnings.filterwarnings("ignore") diff --git a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py index b3371e41..4bfc7b02 100644 --- a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py +++ b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py @@ -1,171 +1,56 @@ import random -import matplotlib.pyplot as plt import numpy as np import optuna import pandas as pd -from scipy.interpolate import RBFInterpolator -from sklearn.ensemble import RandomForestRegressor -from sklearn.metrics import mean_squared_error -from sklearn.model_selection import KFold, cross_val_score +from prettyPlot.plotting import * from sklearn.preprocessing import OneHotEncoder -from tensorflow.keras.layers import Dense -from tensorflow.keras.models import Sequential - - -def tune_rbf(X, y): - def objective(trial): - # Tune the RBFInterpolator - # kernel - "multiquadric" (Can try other kernels) - epsilon = trial.suggest_float("epsilon", 0.1, 10.0, log=False) - try: - kf = KFold(n_splits=5, shuffle=True, random_state=42) - return np.mean( - [ - mean_squared_error( - y[test], - RBFInterpolator( - X[train], - y[train], - epsilon=epsilon, - kernel="multiquadric", - )(X[test]), - ) - for train, test in kf.split(X) - ] - ) - except: - return float("inf") - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -def tune_rf(X, y): - # Tune the Random Forest - def objective(trial): - model = RandomForestRegressor( - n_estimators=trial.suggest_int("n_estimators", 50, 200), - max_depth=trial.suggest_int("max_depth", 3, 10), - min_samples_leaf=trial.suggest_int("min_samples_leaf", 1, 10), - random_state=42, - ) - scores = cross_val_score( - model, X, y, cv=5, scoring="neg_mean_squared_error" - ) - return -np.mean(scores) - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -def tune_nn(X_encoded, y): - # Tune the Neural Network - def objective(trial): - units = trial.suggest_int("n_units", 16, 128) - layers = trial.suggest_int("n_layers", 1, 3) - kf = KFold(n_splits=5, shuffle=True, random_state=42) - mses = [] - for train, test in kf.split(X_encoded): - model = Sequential() - model.add( - Dense( - units, activation="relu", input_shape=(X_encoded.shape[1],) - ) - ) - for _ in range(layers - 1): - model.add(Dense(units, activation="relu")) - model.add(Dense(1)) - model.compile(optimizer="adam", loss="mse") - model.fit( - X_encoded[train], - y[train], - epochs=100, - batch_size=16, - verbose=0, - ) - mses.append( - mean_squared_error( - y[test], model.predict(X_encoded[test]).flatten() - ) - ) - return np.mean(mses) - - study = optuna.create_study(direction="minimize") - study.optimize(objective, n_trials=20) - return study.best_params - - -class Surrogatewrapper: - # Wrapper that builds the surrogate model and predicts the QOI value for given X - def __init__(self, model_type, X, y, params): - # Inputs: - # model_type = 'rbf' for RBFInterpolator - # = 'rf' for Random Forest - # = 'nn' for Neural Network - # X,y = the raw data - # params = tune parameter of the surrogate model - - self.model_type = model_type - self.encoder = None - if model_type == "rbf": - self.model = RBFInterpolator( - X, y, kernel="multiquadric", epsilon=params["epsilon"] - ) - elif model_type == "rf": - self.model = RandomForestRegressor(**params, random_state=42) - self.model.fit(X, y) - elif model_type == "nn": - self.encoder = OneHotEncoder(sparse_output=False) - X_encoded = self.encoder.fit_transform(X) - self.model = self._build_nn( - X_encoded.shape[1], params["n_units"], params["n_layers"] - ) - self.model.fit(X_encoded, y, epochs=100, batch_size=16, verbose=0) - - def _build_nn(self, input_dim, units, layers): - # Builds the neural network - model = Sequential() - model.add(Dense(units, activation="relu", input_shape=(input_dim,))) - for _ in range(layers - 1): - model.add(Dense(units, activation="relu")) - model.add(Dense(1)) - model.compile(optimizer="adam", loss="mse") - return model - - def predict(self, X): - X = X.reshape(1, -1) - if self.model_type == "nn": - X = self.encoder.transform(X) - return float(self.model.predict(X)[0]) - elif self.model_type == "rf": - return float(self.model.predict(X)[0]) - else: - return float(self.model(X)[0]) +from bird.postprocess.SA_optimization.surrogate import ( + Surrogate_wrapper, + tune_nn, + tune_rbf, + tune_rf, +) def simulated_annealing_surrogate( - surrogate, dim=12, max_iters=1000, max_spargers=8, temp=10.0, alpha=0.95 -): - # Runs the Simulated Annealing (SA) and reports the results - # Inputs: - # surrogate = tuned surrogate model - # dim = dimension of the problem - # max_spargers = maximum number of spargers allowed - # max_iters = maximum number of iterations for SA - # temp = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = max temperature. It controls the exploration rate of SA - # alpha = parameter of Simulated Annealing (can be changes for tuned for other problems) - # = cooling rate. It determines how quickly temp decays. - - # Outputs: - # x_best = optimal solution - # y_best = optimal objective function value - # trace = optimization log. Updates at the end of each iteration. - # trace_x = tracks how X changes during each iteration. + surrogate: Surrogate_wrapper, + dim: int = 12, + max_iters: int = 1000, + max_spargers: int = 8, + temp: float = 10.0, + alpha: float = 0.95, +) -> tuple[np.ndarray, float, list[tuple]]: + """ + Runs the Simulated Annealing (SA) constrained by the number of spargers and reports the results + + Parameters + ---------- + surrogate : Surrogate_wrapper + tuned surrogate model + dim: int + dimension of the problem + max_iters: int + maximum number of iterations for SA + max_spargers: int + maximum number of spargers (this is a constraint of the optimization problem) + temp: float + parameter of Simulated Annealing (can be changed or tuned for other problems) + max temperature. It controls the exploration rate of SA + alpha: + parameter of Simulated Annealing (can be changed or tuned for other problems) + cooling rate. It determines how quickly temp decays + + Returns + ---------- + x_best: np.ndarray + optimal solution + y_best: float + optimal objective function value + trace: list[tuple] + optimization log. Updates at the end of each iteration. + """ def is_valid(x): # Checks if the number of spargers <= max_spargers @@ -210,23 +95,44 @@ def is_valid(x): def run_optimization( - X, - y, - model_type="rbf", - max_spargers=8, - n_runs=10, - max_iters=1000, - bootstrap_size=100, + X: np.ndarray, + y: np.ndarray, + model_type: str = "rbf", + max_spargers: int = 8, + n_runs: int = 10, + max_iters: int = 1000, + bootstrap_size: int = 100, ): - # Bootstraps data, runs optimization and postprocesses the results. - # Inputs: - # X,y = the raw data - # model_type = 'rbf' for RBFInterpolator - # = 'rf' for Random Forest - # = 'nn' for Neural Network - # n_runs = number of bootstraps - # max_iters = maximum number of SA iterations - # bootstrap_size = size of bootstrap samples + """ + Bootstraps data, runs optimization and postprocesses the results. + + Parameters + ---------- + X: np.ndarray + Raw design configuration + Dimension N by d, where N is the number of simulations, + and d is the number of design variables + y: np.ndarray + Raw array of quantity of interest + Dimension N by 1, where N is the number of simulations + + model_type : str + 'rbf' for RBFInterpolator + 'rf' for Random Forest + 'nn' for Neural Network + + max_spargers: int + maximum number of spargers (this is a constraint of the optimization problem) + n_runs :int + number of bootstraps + max_iters: int + maximum number of SA iterations + bootstrap_size : int + size of bootstrap samples + + Returns + ---------- + """ all_x = [] all_y = [] @@ -254,7 +160,7 @@ def run_optimization( raise ValueError("Invalid model_type") # build the surrogate model - surrogate = Surrogatewrapper(model_type, X_sub, y_sub, params) + surrogate = Surrogate_wrapper(model_type, X_sub, y_sub, params) # run optimization x_best, y_best, trace = simulated_annealing_surrogate( @@ -313,35 +219,42 @@ def run_optimization( alpha=0.3, label="95% CI", ) - plt.xlabel("Iteration") - plt.ylabel("Best Surrogate-Predicted Objective") - plt.title( - f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})" + pretty_labels( + "Iteration", + "Best Surrogate-Predicted Objective", + fontsize=16, + title=f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})", + grid=True, + fontname="Times", ) - plt.grid(True) - plt.legend() + pretty_legend(fontsize=16, fontname="Times") plt.savefig( f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png", dpi=300, ) + plt.savefig( + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.pdf", + ) plt.show() -# Read data from the csv file. -X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") -y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") - -X = X_raw_data.values -y = y_raw_data.iloc[:, :-1].values -y = y * -1 - -# The function will build, tune, and optimize the surrogate model and postprocess the results. -run_optimization( - X, - y, - model_type="rbf", - max_spargers=8, - n_runs=10, - max_iters=1000, - bootstrap_size=100, -) +if __name__ == "__main__": + # Read data from the csv file. + X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") + y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") + + X = X_raw_data.values + y = y_raw_data.iloc[:, :-1].values + # We want to maximize, so we minimize the opposite + y = y * -1 + + # The function will build, tune, and optimize the surrogate model and postprocess the results. + run_optimization( + X, + y, + model_type="rbf", + max_spargers=8, + n_runs=10, + max_iters=1000, + bootstrap_size=100, + ) diff --git a/bird/postprocess/SA_optimization/surrogate.py b/bird/postprocess/SA_optimization/surrogate.py index 79a3f9c9..1dfec6f9 100644 --- a/bird/postprocess/SA_optimization/surrogate.py +++ b/bird/postprocess/SA_optimization/surrogate.py @@ -279,4 +279,3 @@ def predict(self, X): return float(self.model.predict(X)[0]) else: return float(self.model(X)[0]) - From 753582eb01482fcaa92f2e432c0a1a1d9ee1c4ae Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 30 May 2025 15:01:56 -0600 Subject: [PATCH 27/86] ignore pdf from codespelling --- .github/linters/.codespellrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.codespellrc b/.github/linters/.codespellrc index a8ac1050..11311016 100644 --- a/.github/linters/.codespellrc +++ b/.github/linters/.codespellrc @@ -1,3 +1,3 @@ [codespell] -skip = None, .git,OFsolvers,tutorial_cases,experimental_cases,build,_build,__pycache__,data_conditional_mean,Figures,assets +skip = None, .git,OFsolvers,*.pdf, tutorial_cases,experimental_cases,build,_build,__pycache__,data_conditional_mean,Figures,assets ignore-words = .github/linters/.codespell-ignore-words From d6b0c396ac66af27900bc97ad41315a18a82eeed Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 5 Jun 2025 12:23:03 -0600 Subject: [PATCH 28/86] fix concentration computation and default cstar --- tutorial_cases/loop_reactor_mixing/get_qoi.py | 4 ++-- tutorial_cases/loop_reactor_mixing/read_history.py | 12 ++++++++---- .../get_qoi.py | 4 ++-- .../read_history.py | 12 ++++++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tutorial_cases/loop_reactor_mixing/get_qoi.py b/tutorial_cases/loop_reactor_mixing/get_qoi.py index f36f6f3c..7f3897ed 100644 --- a/tutorial_cases/loop_reactor_mixing/get_qoi.py +++ b/tutorial_cases/loop_reactor_mixing/get_qoi.py @@ -150,8 +150,8 @@ def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): nuq = 100 # mean_cstar_co2 = np.random.uniform(12.6, 13.3, nuq) # mean_cstar_h2 = np.random.uniform(0.902, 0.96, nuq) -mean_cstar_co2 = np.random.uniform(11.9, 13.4, nuq) -mean_cstar_h2 = np.random.uniform(0.884, 0.943, nuq) +mean_cstar_co2 = np.random.uniform(14, 16.9, nuq) +mean_cstar_h2 = np.random.uniform(1.04, 1.19, nuq) tmp_cs_h2 = [] diff --git a/tutorial_cases/loop_reactor_mixing/read_history.py b/tutorial_cases/loop_reactor_mixing/read_history.py index 264711f8..be754f6c 100644 --- a/tutorial_cases/loop_reactor_mixing/read_history.py +++ b/tutorial_cases/loop_reactor_mixing/read_history.py @@ -97,11 +97,15 @@ def cliq(caseFolder, timeFolder, nCells, field_dict={}): # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - c_h2 = 1000 * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + c_h2 = 1000 * h2_liq[indliq] / 0.002016 + c_co2 = 1000 * co2_liq[indliq] / 0.04401 - c_h2 = np.sum(c_h2 * volume[indliq]) / np.sum(volume[indliq]) - c_co2 = np.sum(c_co2 * volume[indliq]) / np.sum(volume[indliq]) + c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) + c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) return c_co2, c_h2, field_dict diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py index 9562cc65..83932c5b 100644 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py @@ -139,8 +139,8 @@ def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): fold = "local" nuq = 100 -mean_cstar_co2 = np.random.uniform(12.6, 13.3, nuq) -mean_cstar_h2 = np.random.uniform(0.902, 0.96, nuq) +mean_cstar_co2 = np.random.uniform(14, 16.9, nuq) +mean_cstar_h2 = np.random.uniform(1.04, 1.19, nuq) tmp_cs_h2 = [] diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py index 264711f8..be754f6c 100644 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py +++ b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -97,11 +97,15 @@ def cliq(caseFolder, timeFolder, nCells, field_dict={}): # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - c_h2 = 1000 * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + c_h2 = 1000 * h2_liq[indliq] / 0.002016 + c_co2 = 1000 * co2_liq[indliq] / 0.04401 - c_h2 = np.sum(c_h2 * volume[indliq]) / np.sum(volume[indliq]) - c_co2 = np.sum(c_co2 * volume[indliq]) / np.sum(volume[indliq]) + c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) + c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) return c_co2, c_h2, field_dict From df26bd5d269f21025efb7bfcc403f3d98b3d0fe7 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 5 Jun 2025 12:25:49 -0600 Subject: [PATCH 29/86] replace script in data gen --- .../get_qoi.py | 4 ++-- .../read_history.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py index 9562cc65..83932c5b 100644 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py +++ b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py @@ -139,8 +139,8 @@ def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): fold = "local" nuq = 100 -mean_cstar_co2 = np.random.uniform(12.6, 13.3, nuq) -mean_cstar_h2 = np.random.uniform(0.902, 0.96, nuq) +mean_cstar_co2 = np.random.uniform(14, 16.9, nuq) +mean_cstar_h2 = np.random.uniform(1.04, 1.19, nuq) tmp_cs_h2 = [] diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py index 264711f8..be754f6c 100644 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py +++ b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -97,11 +97,15 @@ def cliq(caseFolder, timeFolder, nCells, field_dict={}): # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - c_h2 = 1000 * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 + c_h2 = 1000 * h2_liq[indliq] / 0.002016 + c_co2 = 1000 * co2_liq[indliq] / 0.04401 - c_h2 = np.sum(c_h2 * volume[indliq]) / np.sum(volume[indliq]) - c_co2 = np.sum(c_co2 * volume[indliq]) / np.sum(volume[indliq]) + c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) + c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( + volume[indliq] * alpha_liq[indliq] + ) return c_co2, c_h2, field_dict From a9644c69bbde674580abc925c00faaeb0d327ef7 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 5 Jun 2025 12:37:29 -0600 Subject: [PATCH 30/86] save optim results to folder, and remove tmp files --- .../Xdata_study_scaleup_0_4vvm_3000W.csv | 185 ------------------ bird/postprocess/SA_optimization/get_csv.py | 10 +- .../SA_optimization/get_optimal.py | 81 ++++++-- .../get_optimal_with_constraint.py | 61 +++--- bird/postprocess/SA_optimization/results.pkl | Bin 5208 -> 0 bytes bird/postprocess/SA_optimization/temp | 1 - .../ydata_study_scaleup_0_4vvm_3000W.csv | 185 ------------------ 7 files changed, 109 insertions(+), 414 deletions(-) delete mode 100644 bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv delete mode 100644 bird/postprocess/SA_optimization/results.pkl delete mode 100644 bird/postprocess/SA_optimization/temp delete mode 100644 bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv diff --git a/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv b/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv deleted file mode 100644 index 771f8ccf..00000000 --- a/bird/postprocess/SA_optimization/Xdata_study_scaleup_0_4vvm_3000W.csv +++ /dev/null @@ -1,185 +0,0 @@ -0,1,0,1,0,0,2,2,2,2,0 -0,2,2,2,2,2,0,1,0,0,0 -2,1,0,1,2,0,0,1,1,0,1 -0,1,1,2,0,1,1,2,1,1,0 -0,2,2,2,2,1,1,1,0,2,0 -0,1,2,2,1,1,0,2,2,1,0 -2,0,2,1,0,2,1,1,1,2,2 -0,2,1,1,0,0,2,0,0,1,0 -0,0,0,1,1,1,1,0,2,2,2 -0,2,0,2,1,0,2,2,1,2,1 -2,2,1,1,0,2,2,2,0,1,1 -0,1,0,1,2,0,2,1,1,1,1 -2,1,1,0,0,1,2,1,1,2,1 -1,2,0,1,1,2,2,2,2,1,0 -1,1,0,0,0,2,2,0,2,1,2 -1,1,0,1,0,0,2,0,2,0,1 -0,1,2,2,2,0,1,2,2,1,0 -2,1,2,0,1,1,2,0,1,0,0 -0,1,1,0,0,0,2,2,2,2,1 -2,2,0,1,0,2,2,2,1,0,1 -0,2,0,2,1,2,2,0,0,1,2 -0,1,2,1,1,2,0,2,0,0,2 -1,1,0,1,0,2,1,2,1,1,0 -2,2,2,1,1,0,2,2,1,2,0 -2,2,1,1,1,2,1,2,1,1,0 -1,1,2,1,1,1,1,0,2,1,1 -0,0,2,1,2,0,1,0,0,0,1 -0,0,2,0,1,0,2,0,0,1,2 -0,1,0,1,1,0,2,1,0,1,0 -1,2,2,1,0,2,2,1,0,1,2 -0,0,1,2,1,1,1,2,2,1,2 -0,0,1,1,2,0,1,0,2,2,1 -2,0,0,2,1,0,2,0,0,2,1 -0,2,0,1,0,1,1,2,0,2,1 -0,1,2,2,1,0,1,0,2,2,1 -2,1,2,2,1,1,0,1,0,2,0 -1,2,2,2,1,2,2,0,2,1,0 -0,0,0,2,1,0,0,2,2,1,1 -1,0,0,0,1,0,0,2,2,1,1 -2,2,0,0,0,0,2,2,2,2,1 -2,1,2,1,2,2,1,0,1,2,1 -2,2,1,2,2,0,1,1,0,2,2 -2,1,0,2,2,2,1,1,0,1,1 -1,0,0,1,2,2,1,1,2,0,0 -1,2,2,0,2,0,1,1,0,0,2 -1,1,0,2,1,0,2,0,0,1,2 -1,2,0,1,1,0,1,1,0,2,0 -1,2,0,1,1,0,2,2,2,2,2 -1,2,1,2,2,1,1,2,2,0,1 -0,1,1,0,0,1,0,1,1,2,2 -0,0,0,1,0,0,0,0,1,0,2 -0,1,1,1,0,2,2,2,0,1,0 -1,0,1,0,1,1,2,0,2,0,2 -0,1,0,1,2,0,1,1,2,2,0 -2,0,2,1,2,1,0,2,2,0,2 -0,2,1,1,2,1,2,2,2,2,0 -0,1,0,2,2,0,0,0,2,2,2 -2,0,1,1,0,2,1,1,1,1,2 -2,2,1,1,1,2,1,1,2,2,0 -0,0,0,0,0,1,0,2,2,0,0 -0,0,0,2,2,1,0,2,0,1,1 -1,2,0,0,2,2,2,2,0,1,0 -1,1,2,0,2,2,1,0,1,0,0 -1,0,0,2,0,1,0,0,2,0,0 -2,1,2,1,0,1,2,1,1,2,2 -1,0,2,0,2,1,1,2,0,1,2 -1,0,2,0,2,0,0,0,2,2,1 -2,0,2,1,1,2,0,0,2,1,0 -1,0,1,0,2,0,2,2,1,0,2 -0,1,2,1,0,0,0,2,1,1,2 -0,0,1,0,2,1,0,0,1,0,2 -1,0,0,0,1,2,1,2,0,2,1 -0,2,1,0,0,1,1,1,0,0,0 -2,0,2,1,0,0,0,0,0,1,1 -1,0,0,1,1,2,2,1,2,1,0 -2,2,1,2,2,0,2,1,1,2,2 -0,2,2,1,2,0,1,1,2,0,1 -1,0,2,1,1,2,0,0,1,0,0 -1,0,1,1,0,1,1,2,1,1,1 -1,1,0,1,1,2,2,0,0,2,0 -1,0,2,2,2,1,1,1,1,2,1 -1,1,2,1,0,0,1,1,2,2,1 -0,1,0,0,2,1,0,1,2,0,2 -0,0,0,2,2,1,2,0,2,1,1 -2,0,2,1,1,1,0,0,0,0,1 -2,0,0,1,2,1,1,1,0,1,1 -1,0,1,0,0,0,0,1,1,0,0 -0,1,2,1,2,0,2,0,1,2,1 -0,0,2,1,0,0,2,1,1,2,1 -0,2,1,1,1,0,2,0,2,0,2 -2,1,1,2,0,0,0,1,1,0,0 -2,1,1,0,2,1,0,0,0,1,0 -0,2,1,0,0,1,2,2,2,0,2 -1,0,2,0,1,0,2,0,2,2,0 -1,1,1,1,2,2,1,0,1,0,0 -1,0,1,0,2,2,1,2,0,1,0 -2,0,2,2,1,2,1,0,2,0,2 -2,1,1,1,0,0,0,1,0,1,0 -1,1,1,1,2,1,2,1,0,0,2 -2,0,2,2,2,1,1,2,0,1,1 -0,1,1,2,2,1,2,2,1,0,0 -0,0,1,1,2,2,0,1,1,0,2 -2,2,2,1,0,0,1,1,2,2,1 -2,0,1,2,0,1,0,1,1,2,2 -0,1,0,2,2,1,2,1,1,1,0 -1,1,2,1,1,0,2,0,1,0,2 -2,1,0,0,1,2,2,2,2,1,0 -2,2,0,0,1,1,1,1,1,0,0 -2,0,1,1,1,1,1,0,1,2,0 -1,2,2,1,2,1,2,2,2,0,1 -1,0,2,0,1,0,1,1,0,0,0 -2,1,0,1,0,1,0,1,2,2,2 -0,1,2,2,0,0,1,1,2,2,1 -1,2,1,1,1,1,1,2,0,1,0 -2,2,1,1,2,2,1,2,0,2,2 -2,2,0,2,0,1,1,0,0,0,2 -1,2,2,0,0,2,0,0,1,1,2 -2,1,2,2,1,0,0,2,1,0,1 -1,1,2,0,0,2,2,0,0,0,2 -0,2,0,0,2,0,2,0,1,2,2 -0,1,0,0,0,1,2,1,0,0,2 -1,2,0,2,2,0,2,2,1,1,1 -2,0,1,0,2,1,1,1,2,2,2 -0,0,1,0,2,0,0,1,2,1,2 -0,1,0,1,2,1,2,2,0,0,2 -2,2,2,2,0,1,1,1,1,0,2 -2,2,2,2,0,1,1,2,0,1,0 -1,2,1,1,0,1,0,0,2,0,2 -0,1,0,0,1,0,2,1,0,0,0 -2,2,1,1,2,2,2,2,0,2,2 -0,2,2,0,1,2,2,0,2,0,0 -0,1,0,1,2,1,1,1,2,2,2 -0,1,2,2,1,2,0,1,0,0,2 -1,0,1,0,0,2,1,1,0,2,1 -2,0,1,0,0,0,1,1,0,0,0 -2,2,2,1,0,2,1,2,0,0,2 -2,0,1,0,2,2,0,2,0,2,2 -0,0,0,1,2,2,2,0,0,1,2 -0,2,0,1,2,0,1,1,0,2,1 -0,1,2,0,2,0,1,1,2,0,1 -1,1,1,0,0,1,2,2,1,1,1 -2,1,2,0,0,1,0,0,0,2,2 -2,1,2,2,2,1,2,0,0,2,2 -0,0,0,2,0,1,1,1,0,0,1 -0,2,0,2,2,2,2,2,0,1,1 -2,2,2,0,1,1,1,0,1,0,0 -1,0,1,1,2,2,2,1,0,0,0 -2,1,2,2,1,1,0,0,1,1,2 -0,1,2,1,2,1,0,0,1,1,2 -1,2,2,0,1,2,2,0,1,1,0 -0,2,2,0,0,1,0,2,2,2,0 -1,2,2,2,1,2,2,2,0,1,0 -1,0,2,1,2,2,2,2,1,1,2 -2,0,2,0,1,1,1,2,1,2,1 -0,1,0,1,0,1,1,2,1,2,1 -2,2,0,1,2,2,2,2,0,2,2 -1,0,2,2,0,1,1,0,0,0,1 -0,0,0,1,2,0,2,1,2,0,2 -0,0,0,1,2,2,2,0,1,1,0 -0,1,0,1,1,0,0,1,1,0,1 -2,0,2,0,1,0,0,2,1,2,2 -1,0,1,2,2,1,0,0,1,2,0 -2,0,2,0,1,0,0,1,2,1,0 -1,0,1,0,0,1,0,0,2,1,0 -2,1,2,2,0,1,1,2,2,1,2 -2,1,2,1,1,0,1,0,1,2,2 -1,0,0,2,2,0,1,1,1,1,0 -1,0,2,1,2,0,2,2,2,2,2 -1,0,0,0,2,1,2,0,0,0,2 -1,0,0,2,2,2,0,1,2,0,0 -0,1,1,0,2,2,1,2,1,1,0 -1,2,2,2,2,0,1,0,2,2,2 -1,0,1,1,2,1,2,0,1,1,1 -1,1,0,2,2,1,1,1,2,2,1 -2,2,1,0,2,2,2,0,1,2,1 -1,0,0,0,1,2,0,1,2,0,1 -0,1,2,0,2,2,1,1,0,1,2 -1,2,1,2,2,0,2,0,0,0,2 -0,0,0,1,2,2,1,0,1,0,1 -2,0,1,0,1,2,0,0,2,1,1 -1,0,0,2,2,0,2,1,0,0,1 -1,0,1,1,0,2,2,2,1,2,0 -0,0,1,1,0,2,1,1,0,1,0 -2,2,1,0,2,0,0,1,0,1,1 -1,1,0,1,2,0,1,2,2,0,0 diff --git a/bird/postprocess/SA_optimization/get_csv.py b/bird/postprocess/SA_optimization/get_csv.py index 1876d485..71040989 100644 --- a/bird/postprocess/SA_optimization/get_csv.py +++ b/bird/postprocess/SA_optimization/get_csv.py @@ -31,8 +31,8 @@ def get_config_result(study_fold: str = ".") -> None: count = 0 # Save data into CSV files - xfname = f"Xdata_{study_fold}.csv" - yfname = f"ydata_{study_fold}.csv" + xfname = os.path.join(study_fold, f"Xdata.csv") + yfname = os.path.join(study_fold, f"ydata.csv") with open(xfname, "w", newline="") as csvfile: writer = csv.writer(csvfile) for sims in results: @@ -52,6 +52,10 @@ def get_config_result(study_fold: str = ".") -> None: if __name__ == "__main__": - studies = {"study_scaleup_0_4vvm_3000W": r"0.0036$m^3$ 0.4vvm 0W"} + studies = { + "study_scaleup_0_4vvm_3000W": r"608$m^3$ 0.4vvm 3000W", + "study_scaleup_0_1vvm_6000W": r"608$m^3$ 0.1vvm 6000W", + "study_0_4vvm_1W": r"0.00361$m^3$ 0.4vvm 1W", + } for study in studies: get_config_result(study) diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index b667fbb9..0fdf2137 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -1,3 +1,4 @@ +import os import random import warnings @@ -90,6 +91,7 @@ def run_optimization( n_runs: int = 10, max_iters: int = 1000, bootstrap_size: int = 100, + out_folder: str = ".", ): """ Bootstraps data, runs optimization and postprocesses the results. @@ -115,6 +117,8 @@ def run_optimization( maximum number of SA iterations bootstrap_size : int size of bootstrap samples + out_folder: str + folder where to output the results Returns ---------- @@ -175,7 +179,10 @@ def run_optimization( ] ) df.to_csv( - f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}.csv", + os.path.join( + out_folder, + f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}.csv", + ), index=False, ) @@ -211,11 +218,17 @@ def run_optimization( ) pretty_legend(fontsize=16, fontname="Times") plt.savefig( - f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png", + os.path.join( + out_folder, + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.png", + ), dpi=300, ) plt.savefig( - f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.pdf", + os.path.join( + out_folder, + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}.pdf", + ), ) plt.show() @@ -255,24 +268,56 @@ def run_optimization( pretty_legend(fontsize=16, fontname="Times") plt.tight_layout() plt.savefig( - f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png", dpi=300 + os.path.join( + out_folder, + f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.png", + ), + dpi=300, + ) + plt.savefig( + os.path.join( + out_folder, + f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.pdf", + ) ) - plt.savefig(f"Mean_L1_distance_{model_type}_size_{bootstrap_size}.pdf") plt.show() if __name__ == "__main__": - # Read data from the csv file. - X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") - y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") - - X = X_raw_data.values - y = y_raw_data.iloc[:, :-1].values - # We want to maximize, so we minimize the opposite - y = y * -1 - - # The function will build, tune, and optimize the surrogate model and postprocess the results. - run_optimization( - X, y, model_type="rbf", n_runs=10, max_iters=1000, bootstrap_size=150 - ) + # studies = ["study_scaleup_0_4vvm_3000W", "study_scaleup_0_1vvm_6000W", "study_0_4vvm_1W"] + studies = ["study_scaleup_0_1vvm_6000W", "study_0_4vvm_1W"] + # studies = ["study_scaleup_0_4vvm_3000W"] + + for study in studies: + # Read data from the csv file. + X_raw_data = pd.read_csv(os.path.join(study, "Xdata.csv")) + y_raw_data = pd.read_csv(os.path.join(study, "ydata.csv")) + + X = X_raw_data.values + y = y_raw_data.iloc[:, :-1].values + # We want to maximize, so we minimize the opposite + y = y * -1 + + # The function will build, tune, and optimize the surrogate model and postprocess the results. + run_optimization( + X, + y, + model_type="rbf", + n_runs=10, + max_iters=1000, + bootstrap_size=150, + out_folder=study, + ) + run_optimization( + X, + y, + model_type="rf", + n_runs=10, + max_iters=1000, + bootstrap_size=150, + out_folder=study, + ) + # run_optimization( + # X, y, model_type="nn", n_runs=10, max_iters=1000, bootstrap_size=150, out_folder=study + # ) diff --git a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py index 4bfc7b02..b4ec9b52 100644 --- a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py +++ b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py @@ -1,3 +1,4 @@ +import os import random import numpy as np @@ -102,6 +103,7 @@ def run_optimization( n_runs: int = 10, max_iters: int = 1000, bootstrap_size: int = 100, + out_folder: str = ".", ): """ Bootstraps data, runs optimization and postprocesses the results. @@ -129,6 +131,8 @@ def run_optimization( maximum number of SA iterations bootstrap_size : int size of bootstrap samples + out_folder: str + folder where to output the results Returns ---------- @@ -190,7 +194,10 @@ def run_optimization( ] ) df.to_csv( - f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.csv", + os.path.join( + out_folder, + f"best_bootstrap_solution_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.csv", + ), index=False, ) @@ -229,32 +236,42 @@ def run_optimization( ) pretty_legend(fontsize=16, fontname="Times") plt.savefig( - f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png", + os.path.join( + out_folder, + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.png", + ), dpi=300, ) plt.savefig( - f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.pdf", + os.path.join( + out_folder, + f"Mean_Convergence_plot_{model_type}_size_{bootstrap_size}_max_spargers_{max_spargers}.pdf", + ), ) plt.show() if __name__ == "__main__": - # Read data from the csv file. - X_raw_data = pd.read_csv("Xdata_study_scaleup_0_4vvm_3000W.csv") - y_raw_data = pd.read_csv("ydata_study_scaleup_0_4vvm_3000W.csv") - - X = X_raw_data.values - y = y_raw_data.iloc[:, :-1].values - # We want to maximize, so we minimize the opposite - y = y * -1 - - # The function will build, tune, and optimize the surrogate model and postprocess the results. - run_optimization( - X, - y, - model_type="rbf", - max_spargers=8, - n_runs=10, - max_iters=1000, - bootstrap_size=100, - ) + studies = ["study_scaleup_0_4vvm_3000W"] + + for study in studies: + for nsparg in [1, 2, 3, 4, 5, 6, 7, 8]: + X_raw_data = pd.read_csv(os.path.join(study, "Xdata.csv")) + y_raw_data = pd.read_csv(os.path.join(study, "ydata.csv")) + + X = X_raw_data.values + y = y_raw_data.iloc[:, :-1].values + # We want to maximize, so we minimize the opposite + y = y * -1 + + # The function will build, tune, and optimize the surrogate model and postprocess the results. + run_optimization( + X, + y, + model_type="rbf", + max_spargers=nsparg, + n_runs=10, + max_iters=1000, + bootstrap_size=150, + out_folder=study, + ) diff --git a/bird/postprocess/SA_optimization/results.pkl b/bird/postprocess/SA_optimization/results.pkl deleted file mode 100644 index 10aa5fe88138e206188453a8ec77f9050aaa9765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5208 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 bmx?O~)77r4$+|=Lqt2Z=yn#3t#+Ls9#_kjd diff --git a/bird/postprocess/SA_optimization/temp b/bird/postprocess/SA_optimization/temp deleted file mode 100644 index 78981922..00000000 --- a/bird/postprocess/SA_optimization/temp +++ /dev/null @@ -1 +0,0 @@ -a diff --git a/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv b/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv deleted file mode 100644 index e224b58a..00000000 --- a/bird/postprocess/SA_optimization/ydata_study_scaleup_0_4vvm_3000W.csv +++ /dev/null @@ -1,185 +0,0 @@ -4.639249158318668,0.12626038361898534 -4.830205075462752,0.1345759778255486 -11.883263228093135,0.3264360683677917 -13.920765170099399,0.4105486654309187 -6.542159679067755,0.2034214300932224 -12.152375869450877,0.36239501906304267 -8.022225385816165,0.2748815895051835 -9.57833436748858,0.28111820963360595 -7.621157957714864,0.25567306815826935 -6.854083596204951,0.2298569939977962 -11.145584784788815,0.31811738493951314 -11.017594168736228,0.3184159068790033 -13.50973761231716,0.3752779584066442 -12.030553522901826,0.3626718350283127 -8.500853761159016,0.2514870108881235 -8.743216029351826,0.2670107363696459 -11.717721199440591,0.3128707825208673 -11.345565509701485,0.353472952974596 -8.988391479133291,0.2580096856927115 -8.543409441773761,0.262301422633598 -6.560127864354076,0.20837078993418331 -6.4149526976824855,0.19347669879343268 -13.741028936629446,0.37516250222124864 -10.260225187485267,0.30671415538616664 -12.38699282309954,0.33678185294994506 -15.459376105064136,0.447246924226177 -9.09803267916285,0.304227995563132 -6.740910374100037,0.21154083742443155 -13.191334348313264,0.34796851870506956 -13.70507196126807,0.38117142223133704 -11.518308973994909,0.35289543460220707 -11.515168788856977,0.3461668678795173 -7.224363681870076,0.23657610962538503 -10.805708473886197,0.3263647525674359 -12.932602535390258,0.370807747121449 -10.892569560758172,0.31241264469905766 -12.069083972134544,0.36751044492654417 -6.4823749997684805,0.19262760715138516 -11.231649637811778,0.3277463243939802 -4.270684191558357,0.10987581442252059 -13.76528529571703,0.38285304273583004 -10.152583557069674,0.3408235347109574 -10.986707238586106,0.324251636154978 -9.79517497061306,0.3029635961468728 -10.419543443002388,0.3170020475695413 -10.258773239532914,0.27979900920419293 -10.189745733831108,0.3250937746551829 -6.514063372968979,0.2268487917845758 -13.953941108502349,0.37603794998487483 -11.992362749779748,0.34762137147830285 -7.882404941588837,0.23711080920489294 -9.275719183296298,0.26904923614363635 -7.776457060294847,0.25905651438188215 -9.725980401340914,0.2985287367453095 -6.807759844786992,0.2048265466788003 -6.630803695687349,0.21680559865756013 -4.063931442420922,0.11272271250075441 -11.225122173481324,0.3453857489445164 -9.82654794320513,0.31766875974197384 -6.157926107439263,0.18678044797135201 -5.855484586386016,0.18450597017794995 -9.18587411892937,0.2978687770910763 -9.88460112835837,0.2705988868749546 -6.841601401913933,0.2050600340598003 -12.79640726757815,0.3623376347189863 -12.83899374588669,0.3975083213578766 -8.457051563045496,0.26991461104084247 -9.763108036495394,0.3275758151015267 -8.984052674188392,0.23708461040402135 -11.471274422120723,0.3049553719602422 -10.615101031292475,0.311189711506409 -13.247670835151544,0.36060925350524264 -10.074632957437625,0.31206315974184845 -7.549823041704327,0.21747657909403267 -13.660770312425418,0.3507548164008165 -9.258995020455252,0.29796230302465954 -9.338803482609384,0.3070023813054725 -10.168223206620057,0.289058067197555 -15.414172835849321,0.4436665921077268 -6.382847851314592,0.18876460404289166 -12.541321134236878,0.3777538153682838 -14.625672661663916,0.38802297600654273 -9.2131453370491,0.2672227550883449 -6.020912999143897,0.18351282428727747 -9.594482823382547,0.3548268182647394 -9.434406063121779,0.32561538569508414 -11.27709117666853,0.3170389795034779 -11.85440229800048,0.3713104751517777 -8.143103526931212,0.24373125334484186 -5.987413503473131,0.19209801113080932 -10.66860645276813,0.3146498728341653 -10.973009201937053,0.326561903194073 -6.736162134712763,0.21831998403883163 -6.319124326915894,0.20386451019469645 -9.85518408546759,0.2547269254087896 -11.752111783293545,0.3360594969365309 -7.2505681489712845,0.22033395375408343 -11.538062664951463,0.32045394779240677 -9.480109510593579,0.2931065455457869 -7.24841607883414,0.23832775051957591 -10.347675591461396,0.28753868506914715 -10.686337772821627,0.30684839551276677 -9.822044747899417,0.32661245475083844 -9.447079333421648,0.3112984064131052 -11.114202667483596,0.3364211476686481 -9.532358320060213,0.31236106613200026 -11.688464102351732,0.35487546667211 -6.532609784598158,0.22168781874321142 -10.192019448061677,0.3054567325499729 -13.077747284183287,0.38903025246203415 -10.096158138823926,0.30867510872414256 -9.343519904040358,0.290375320672992 -11.40697959771358,0.31308878923916295 -12.020609433055474,0.3782750313181893 -7.172718740262101,0.24334049751097286 -7.584794001842911,0.2514622981514563 -9.152594292536907,0.2678778098535802 -12.045947232357136,0.33710722608802324 -4.379843967798498,0.10762641890440137 -3.9571319937219607,0.113635341004926 -9.604472609306962,0.27473135213632965 -8.418838881573317,0.25553219416942385 -10.210954922063188,0.323263474717489 -9.607306751578054,0.29177678625562137 -7.060706536012455,0.2464907666663334 -6.943395438649984,0.23411188612717468 -7.987496774296126,0.23715829335149538 -6.703187252555086,0.21613478186303836 -8.989863908497496,0.29754347680337007 -5.3003428235059005,0.15283433472600066 -7.54340245910743,0.24628416098103528 -9.813350149916667,0.3142972297214279 -9.471843550104339,0.3170324937170163 -14.278150609504612,0.4087077718493328 -9.677308352105648,0.28421158422409315 -7.4440856645917135,0.25581770286934824 -4.385277812944313,0.13265263272190378 -8.22363437693505,0.2223718632499199 -9.408385570236735,0.29099268450210747 -11.77627353707695,0.36591204701029667 -15.043583831144797,0.4168601817074705 -6.7135010881993855,0.20526445170131055 -7.3099809259113115,0.22216331625583294 -6.8867769079468415,0.2117247511316945 -4.409863387744625,0.11906173881006474 -7.641879896764433,0.23103459137397314 -8.483160007686088,0.24982929105677146 -12.2184349027241,0.34412720042998773 -13.108464684640968,0.3854827942762161 -12.301487123370132,0.3546389998626981 -6.778424897986802,0.20380859414550845 -11.153693300454865,0.33196148938956604 -12.848113577592285,0.383963743119625 -7.7318602570176695,0.24995391622250263 -13.889362645764818,0.41917795613750286 -5.029285733575436,0.1565712068835105 -12.815511254920658,0.32045351604623495 -8.916057019146086,0.2593018338761806 -7.649338687785831,0.2492080696611349 -12.8604460492114,0.3723917179012849 -6.601788303188531,0.18369133369166799 -10.288367893781292,0.30306427295098 -6.925703924819166,0.2403483369335239 -11.790867067900965,0.32527202858137033 -12.810837863572981,0.36388377313805176 -11.092170573659601,0.333338222107799 -10.189868448033742,0.2709142473792956 -5.078027062937434,0.15792477880845202 -7.223634361053522,0.21356319778744356 -8.948019498562504,0.24191457543885958 -13.222659710790236,0.3750710095241255 -8.076671575057484,0.2209566383161624 -15.312111223230433,0.3871211907716363 -14.170973262775446,0.42197025792806736 -9.32461479951292,0.29381079716589753 -13.309929308155027,0.3625732862415955 -11.21961903186075,0.3243213919583699 -4.66559471266868,0.1327459953758173 -8.65681333512524,0.2849376420634901 -10.051156775736247,0.33207149084178794 -10.559178404056045,0.31681027963019587 -9.170860767688787,0.2863959162680961 -12.08392615370928,0.3370654696996921 -8.519728499754038,0.24416325998118774 -7.275149264066791,0.2572700464241981 From 73245febea4d164df8a89c174749419e96d2b4a4 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 9 Jun 2025 08:31:26 -0600 Subject: [PATCH 31/86] add script to generate paper figures --- .../SA_optimization/get_optimal.py | 30 +++++++++---------- .../SA_optimization/plot_qoi_vs_sparg.py | 13 ++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 bird/postprocess/SA_optimization/plot_qoi_vs_sparg.py diff --git a/bird/postprocess/SA_optimization/get_optimal.py b/bird/postprocess/SA_optimization/get_optimal.py index 0fdf2137..2aafa6f4 100644 --- a/bird/postprocess/SA_optimization/get_optimal.py +++ b/bird/postprocess/SA_optimization/get_optimal.py @@ -193,7 +193,7 @@ def run_optimization( upper_bound = mean_trace + 1.96 * std_trace / np.sqrt(n_runs) iterations = np.arange(max_iters + 1) - plt.figure(figsize=(8, 4)) + plt.figure(figsize=(8, 6)) plt.plot( iterations, mean_trace, @@ -210,13 +210,13 @@ def run_optimization( ) pretty_labels( "Iteration", - "Best Surrogate-Predicted Objective", - fontsize=16, + r"Predicted QOI [kg$^2$/kWh$^2$]", + fontsize=20, title=f"Mean Convergence with 95% Confidence Interval ({model_type.upper()})", grid=True, fontname="Times", ) - pretty_legend(fontsize=16, fontname="Times") + pretty_legend(fontsize=20, fontname="Times") plt.savefig( os.path.join( out_folder, @@ -286,8 +286,8 @@ def run_optimization( if __name__ == "__main__": # studies = ["study_scaleup_0_4vvm_3000W", "study_scaleup_0_1vvm_6000W", "study_0_4vvm_1W"] - studies = ["study_scaleup_0_1vvm_6000W", "study_0_4vvm_1W"] - # studies = ["study_scaleup_0_4vvm_3000W"] + # studies = ["study_scaleup_0_1vvm_6000W", "study_0_4vvm_1W"] + studies = ["study_scaleup_0_4vvm_3000W", "study_0_4vvm_1W"] for study in studies: # Read data from the csv file. @@ -309,15 +309,15 @@ def run_optimization( bootstrap_size=150, out_folder=study, ) - run_optimization( - X, - y, - model_type="rf", - n_runs=10, - max_iters=1000, - bootstrap_size=150, - out_folder=study, - ) + # run_optimization( + # X, + # y, + # model_type="rf", + # n_runs=10, + # max_iters=1000, + # bootstrap_size=150, + # out_folder=study, + # ) # run_optimization( # X, y, model_type="nn", n_runs=10, max_iters=1000, bootstrap_size=150, out_folder=study # ) diff --git a/bird/postprocess/SA_optimization/plot_qoi_vs_sparg.py b/bird/postprocess/SA_optimization/plot_qoi_vs_sparg.py new file mode 100644 index 00000000..b05b85fc --- /dev/null +++ b/bird/postprocess/SA_optimization/plot_qoi_vs_sparg.py @@ -0,0 +1,13 @@ +import numpy as np +from prettyPlot.plotting import * + +nsparg = [1, 2, 3, 4, 5, 6, 7, 8] +qoi_opt = [13.485, 14.276, 14.635, 15.692, 16.072, 17.657, 19.349, 20.712] + +fig = plt.figure() +plt.plot(nsparg, qoi_opt, linewidth=3, color="k") +pretty_labels( + r"N$_{\rm sparg}$", r"Optimal QOI [kg$^2$/kWh$^2$]", 20, fontname="Times" +) +plt.savefig("marginal_gain.png") +plt.savefig("marginal_gain.pdf") From 0b290671d342f13a1bf7303930476c2dcd626de9 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 16 Jun 2025 09:23:27 -0600 Subject: [PATCH 32/86] remove tabs --- .../base_tank/tank_par.yaml | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/bird/meshing/stirred_tank_mesh_templates/base_tank/tank_par.yaml b/bird/meshing/stirred_tank_mesh_templates/base_tank/tank_par.yaml index 3afc6376..69b86f12 100644 --- a/bird/meshing/stirred_tank_mesh_templates/base_tank/tank_par.yaml +++ b/bird/meshing/stirred_tank_mesh_templates/base_tank/tank_par.yaml @@ -1,23 +1,23 @@ geometry: - Dt: 0.26 # Tank diameter [m] - Da: 0.0866667 # Impleller tip Diameter [m] - H: 0.39 # Height of the reactor [m] - nimpellers: 1 - C: [0.0866667] # height of the center of impellers [m] - W: 0.026 # impeller blade width [m] - L: 0.013 # impeller blade length (beyond the hub) [m] - Lin: 0.013 # impeller blade length (inside the hub) - J: 0.026 # Baffle width - Wh: 0.0026 # Hub height (Width) - polyrad: 0.00866667 # Stem radius (R_shaft) - Z0: 0.0 # bottom of reactor - nbaffles: 6 # number of baffles and impeller fins + Dt: 0.26 # Tank diameter [m] + Da: 0.0866667 # Impleller tip Diameter [m] + H: 0.39 # Height of the reactor [m] + nimpellers: 1 + C: [0.0866667] # height of the center of impellers [m] + W: 0.026 # impeller blade width [m] + L: 0.013 # impeller blade length (beyond the hub) [m] + Lin: 0.013 # impeller blade length (inside the hub) + J: 0.026 # Baffle width + Wh: 0.0026 # Hub height (Width) + polyrad: 0.00866667 # Stem radius (R_shaft) + Z0: 0.0 # bottom of reactor + nbaffles: 6 # number of baffles and impeller fins mesh: - nr: 120 # mesh points per unit radial length - nz: 240 # mesh points per unit axial length - Npoly: 4 # mesh points in the polygon at the axis - Na: 6 # mesh points in the azimuthal direction + nr: 120 # mesh points per unit radial length + nz: 240 # mesh points per unit axial length + Npoly: 4 # mesh points in the polygon at the axis + Na: 6 # mesh points in the azimuthal direction From 5af30ba8c795a344f316a51590494b43df5dd0d0 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 16 Jun 2025 09:23:49 -0600 Subject: [PATCH 33/86] add a check for tabs in yaml files --- bird/meshing/_mesh_tools.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bird/meshing/_mesh_tools.py b/bird/meshing/_mesh_tools.py index 61618b2f..4d6044f8 100644 --- a/bird/meshing/_mesh_tools.py +++ b/bird/meshing/_mesh_tools.py @@ -1,4 +1,5 @@ import json +import os import sys from pathlib import Path @@ -12,7 +13,31 @@ def parseJsonFile(input_filename): return inpt +def check_for_tabs_in_yaml(file_path: str) -> None: + """ + Checks if a YAML file contains any tab characters. + Raises a ValueError if tabs found. + + Parameters + ---------- + file_path: str + path to yaml filename + """ + + with open(file_path, "r", encoding="utf-8") as f: + lines = f.readlines() + for iline, line in enumerate(lines): + if "\t" in line: + raise ValueError( + f"Tab character found on line {iline} of '{file_path}'. " + "YAML files must use spaces for indentation." + ) + + def parseYAMLFile(input_filename): + if not os.path.exists(input_filename): + raise FileNotFoundError(input_filename) + check_for_tabs_in_yaml(input_filename) yaml = YAML(typ="safe") inpt = yaml.load(Path(input_filename)) return inpt From c4a09ec180554393ca00c4199380f3f25eda05cf Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 16 Jun 2025 09:24:21 -0600 Subject: [PATCH 34/86] use the yaml parse function instead of rewriting it --- bird/meshing/_stirred_tank_reactor.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bird/meshing/_stirred_tank_reactor.py b/bird/meshing/_stirred_tank_reactor.py index dc3f2db5..265c8b41 100644 --- a/bird/meshing/_stirred_tank_reactor.py +++ b/bird/meshing/_stirred_tank_reactor.py @@ -2,7 +2,8 @@ from pathlib import Path import numpy as np -from ruamel.yaml import YAML + +from bird.meshing._mesh_tools import parseYAMLFile class StirredTankReactor: @@ -133,11 +134,6 @@ def __init__( def from_file(cls, yamlfile): if ".yaml" not in yamlfile: yamlfile += ".yaml" - if os.path.exists(yamlfile): - yamlpath = Path(yamlfile) - else: - raise FileNotFoundError(yamlfile) - yaml = YAML(typ="safe") - in_dict = yaml.load(yamlpath) + in_dict = parseYAMLFile(yamlfile) react_dict = {**in_dict["geometry"], **in_dict["mesh"]} return cls(**react_dict) From e8fddb569e345c1358f6ed65bfbf94a69a227cc4 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 16 Jun 2025 14:31:20 -0600 Subject: [PATCH 35/86] sparger overlap capability --- bird/preprocess/json_gen/generate_designs.py | 10 +++++++++- fixFormat.sh | 3 ++- tests/preprocess/test_case_gen.py | 12 ++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index 099959f5..461e072c 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -371,6 +371,7 @@ def check_sparger_config( sparger_spacing: float, edge_spacing: float, n_branches: int, + bypass_sparger_spacing: bool, ) -> None: """ Check realizability of the sparger placement configuration @@ -390,6 +391,8 @@ def check_sparger_config( Spacing required between any sparger and the edges of the branches [-] n_branches : int Number of loop reactor branches + bypass_sparger_spacing: bool + If true, allow an overlap of spargers """ # Check that number of spargers is consistent @@ -424,7 +427,8 @@ def check_sparger_config( # Check that spargers are sufficiently spaced out assert sparger_spacing >= 0 - assert all(np.diff(np.sort(np.array(sparger_locs))) >= sparger_spacing) + if not bypass_sparger_spacing: + assert all(np.diff(np.sort(np.array(sparger_locs))) >= sparger_spacing) def generate_single_scaledup_reactor_sparger_cases( @@ -438,6 +442,7 @@ def generate_single_scaledup_reactor_sparger_cases( vvm: float = 0.4, study_folder: str = ".", template_folder: str = "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", + bypass_sparger_spacing: bool = False, ): """ Generates loop reactor case with desired sparger placement configuration @@ -465,6 +470,8 @@ def generate_single_scaledup_reactor_sparger_cases( Where to generate the case template_folder: str The case template to start from + bypass_sparger_spacing: bool + If true, allow an overlap of spargers """ # Sanity checks @@ -474,6 +481,7 @@ def generate_single_scaledup_reactor_sparger_cases( sparger_spacing=sparger_spacing, edge_spacing=edge_spacing, n_branches=n_branches, + bypass_sparger_spacing=bypass_sparger_spacing, ) # Find on which branch is each sparger diff --git a/fixFormat.sh b/fixFormat.sh index 26867ddc..9f62dae5 100644 --- a/fixFormat.sh +++ b/fixFormat.sh @@ -1,4 +1,5 @@ source .github/linters/formatting.sh -format . +format bird +format applications diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 563a6ba3..29729d46 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -22,6 +22,18 @@ def test_continuous_loop(): ), ) + generate_single_scaledup_reactor_sparger_cases( + sparger_locs=[0.3, 0.35], + sim_id=0, + vvm=0.4, + study_folder="sparger_overlap", + template_folder=os.path.join( + BIRD_CASE_GEN_DATA_DIR, + "loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup", + ), + bypass_sparger_spacing=True, + ) + def test_discrete_loop(): From 427f16374b69812a3ec488a382321e29ba6acf6f Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 16 Jun 2025 15:01:57 -0600 Subject: [PATCH 36/86] add overlapping patches to doc --- docs/assets/overlap_patches.png | Bin 0 -> 122320 bytes docs/source/preprocess.rst | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 docs/assets/overlap_patches.png diff --git a/docs/assets/overlap_patches.png b/docs/assets/overlap_patches.png new file mode 100644 index 0000000000000000000000000000000000000000..1579423b2a5f60ab9afb29e2526c1037f9dc6fc3 GIT binary patch literal 122320 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
C#rbSZsvy}-Afn`r0Os(Et2XTJ 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%0AeD6}Y@)G5F>xxdN!2 z(KL>0y}7;JMB=6?EIi1k*b4S74EIiT<4Le@FBK z3C$Q&Hubabekhfh!M&${EH!R1v=>*D(SL=0#M|lrnuUJ{i90*r|K;KmWO*`+H*OsU z2V*zOGrHuZ#NzNb{E}b~;mBYp8?~c?jP#YWNDk~Y-F$Qj6~|J9u*xa>1eY4v+@<=t zJ&-~S`S6*A3<2Cw@7xfhZxfjm#4A$PxKRJ)GVFpr15IyH%I>_nzSI}7f=!uhV^cw* zmQZFn{6{E9C$Vqx3i%FnyLFKwhwj_~>3~-0vc2Y1s{X5;V<4}gR|j0%Pkw#7nLwsL zlS`!YpDIcChhe$@pXdk_h#=nW;o4wScw7`8Rw6Ff&2%MasUWQUPnu?D`=~#9f`tPCGip zlg0_!cl1pNChJ0%#nEFvG8g|QzQ0@hjz|??x!tGK`z(Y8K6Wa?&{3&UtmFHo1z*Tqdq(EMDtH;}fLauT15PIDmO#<9$;F@oz^A`7wQajK zqRU;1{cw^Z>DlQ+jLr`84TE2)!gaPC^lcza(T;a1VoZTkfKlhfAljraAx8R6s|Mb4x z-Wrw6>EVGUUyE6E%cfMnb&#z;!U~?xYcNTqrqty`&B!du|F}Q=8=KMC(DP?^`ybGE z+xa40i<4_nPXf?*@(e%at8p&}vj`~@_Nh%UVoQ1{3R4rckE(XRC;rL{|C7u@2B@&@ zE&^OJVPr%g-}Ih`CMbxw@1(d2H%iz*v~vmn~VqHq0o7Y?RW9e*njH? zjyLF#7)rt>DuR(nB)zB1sHYL4IRM&A&pOcj() zchs(`cq9+{Q=~r#s98H5O#?QmfBD4ZAOsm!#;wT_qn=#oL|7MsQ*{c8!s%O+y`Hk`H zo!A+5M)A!+ZicOIxekkPT;)x8{6U>T$;sGhZ6j4#di+Ic8vT8q{FBBq-Wln|!oTvQ zO{hGX9BZD|mFRCDkzfNO)vTw%x=Vj5XHX_XE<9`6s3r@TpMa&$X z5uE1ke&5u)x3P6nBznWa$NoktO05kd;Cbtu7>779hyL9qY)G17)Qaa;3;gn(^@-^`q%gN zD(8ToXZObUq*{BL8E8`j-Iy6F(+(*L`4AMI^;iZSp#$H&L!(JPerlx`yiG-W%t~W7 zGt&FahFS-6iDsMzWHvGN0f#$ z&S4dqom`1gnZkZ=y6eB|a`{}M$5igO)06&XwrKwsFOI0x)zB8$mh|fxof?gBXjs+A zz?CI8IX9s;tQ(8PcCD)_5I|9UU z`QK$(D_ga1+8j4uM+ELS*B$u;M2{jzTnH9dfyP~PXB$kJ*SfWTnq@!8D$1OD69DHU zw(g2PhYMNHMjg6Mf=JUiwqhBG3_)K1Wq+x9L90K6RtBLZ0D(6r<3JJPdT$e0_4oAW zQw3YxsLhmow7;EhNelkzHOvG$S+{(%_JNPW1H#$zZ*QO9uix)JGz4EMlj*Lft*O(T z+52|9APlp7D0E9{Fg5>-8=)TMj9GHVNGZlX3pH)1?AqU?H@GF|m|@i$=ond#0{HcF z(wN2o{GHrA@Mpl3qIkdSeX4GgSbpl-jVUSL9bCcAB%Y-SpRxRwebf-Gk1+u3O;Wnv z#0lq;>YOq`CIwj4@LYA>R^}$UWM0dCdK*-pn)>lg4jP&)MZk%JMUpNiZHTr4P|^C_ zNT-@t8RErSpoO|m_I#vGJ0??h9=5KSp4$^?ZL*Dgyte!Cjxn6H}ETczzW;KC~CrJ-{**?#c-HJ002xaPef z44hED90^O663Us43ji`3LkV`8(F?>})g=g|t~(Ii|*0hVg_ z)4cUay||F->(av!Q_}qj1+DA?vdgZ(7J)v%Pp{Md7m{F$D0;bN-)=!hr5|~P!qkaB zf^8|z^$H7>vDPm|PNnbcaPR8+Xd zr8h^_nk?1-xYWMVfIHc;JetJmHi10oxtdE$T= z_Z2L)zkeArYI9EvLS_1LY$t&xR1uP9!=te~o?QI$72mQLphgGdAyXX&ZlC-16` z8N3{5TX~C-_=7rS1C4naC$vCkGslzQHd-^ zxG+F-vHv*dU2^dPa12u2H=s%RJ?0gB5I!b&%eYZPdFh}H3^T{grcLwhA-?k%@WP6X zG-Lb*G&;z*GR}xoGh9%d#D}5Zcu`1$G)(r>KJvjQMi(oc`8(-7@T^)Gc-worb2hgQ zmGsC4Dh%{J?Dl(FR8dZBXE6qepvpL5-{;=pfryS*4-ODiRnJ`g_|vRxo@mV?bK5y+ zkycUAcApC0Z_Z~kbArh3>QO@QWXK-`oquDRURuC9u&rsaGc5g!d|rDwp=QAT%&E#b zC`_03Kyi&6yPNXF{Y!&zv%FkrCD(14I-So<;?#1|0((1j+UG|EqN|hid^=!D{tA!? z!b9q;tWZBtt((Pld9+h=mIy8(tobE{z;wVo(7aRt`g z72(Z{OVOIY`Y!#L>&+zDBgW;&)P=NHh1cYE4V?e_D~6mvd(<3WHz3xM(>Hi7txfD3 zX5~0>$3d=&VO2)If+>}!nKzNfIs{e2vp zT)$lphnuJAYF~RO4+cNOP4@}|MRR?ZmN;EuA=h!X0b^*Fg?RIM>?|ZsZ!;?! zGLj28H^(u#2@2B%Ca7YPq;SvO0*gU13`$;yKn@zB?{x4#h=5=20p3^4B{ z4xURqcz-DB^71JtisuV3HW~r!A*6aAKk|@D2#TCfUG335xDGZQ&3}N zsHNHD(OKsTS$L73Bx0nkJC?l}5r-^2fSh+-F}_HuF4OL{TE{wsl^)uQEvhPCYzy_H zvG7AAvp13&#wE=vbMp&aBF5wM(z=@3&+dwgJn`FI$cgIw@78qBf6{xs%okzF68;Rk zLBi=0&C&+XrCFr3FxPnb;r?-M3MK{>%g>i5EZdOv z8^}4zhO@V|a#vJqS{uEjS;KJ}{MzSO)Sl-bo5&tfWu7^F-X33?Yaknq-#HjfK0ZJ8 zet*6Xa{Ul$L1z`0kw5PrQt?6b4f=hFbbY)dom2;ix0wz{lzE>|iu;~>bYZxu6|9bY zJ46d#K?|q2lgs%}(71_$slGs_YcRA`gC%)HKKZ)pM!|RA*(WWQvlka}^-vGtF-ppol5m`biF(`2o59%i-Jbg<0>4cg`ej!l z5vV7G9r+)aIINJcG3ia}v2!5AE|T0St)M7zZIoki+sMU;cjFf!ZKxkF3ivQhdET+; zIhQOGxp5_;Jkq_EBA2OEtw8ohX`d(~*nc*)^_enSt52gXRRvoLQc45k`Q6G|&bLOFqWi^1wf1o= z7z=Gm*i3cq;5jY4`&KGVfxEb@ArZ+0QH@YCWj}dO^<72ESFY{u>K0xgID7@Z%HP$D z@4km+=SHD`ckC2~M!OLiTamK8`#TN1v+J!RS*Ba_B-|{h(8n*q*JwInDicTnawSdbpY(Uk%c;t>h`(xPXjF1Pxi&-?j#y%EoAR)6Y3b@)=G zCv{?vW>2(gsXyO?we55Qn#oIwKs~LR3SWwSj@=IW49sEW-Q}l5qlniaJ{(Y2R{%%p zE;IE2I@v1bb&AU3o%;@Nl5{uf98e=mnbl&(h)nkcAkw)bYi{#+IY*+*;zzT-vU2QY zVKesX#!YcJY;WReW>ypGwDVzlqa@So-YIB;k*ZMJuFjBWO;tGi zx1RdNgW(7zorH9S74_*)Cg_UQIb8lBBxL&-N%%%d=ITLG zK*kyt4sV6l<#GhG)zkj|PkJBaPk=pI(w(S9WU#oA(PK5XvDKU02sNwH2)iZV8a2f0 zqtov0Ge^=CE)wow*cPATA!-p*6*k8Hz2*0)61TW2Yp zU5m3SYi2aE^!}5f*;L58KW5LPR;tn9w(H#?uB1}j3s=Pzi5z(2TvtsjWHAjGlj&flSb3eBgwhb+ARs=~q%c3y=Pl>Z; zF=9fwO%XhEo#(o`xH!yc4C%KOTLJ^<%?r=H^e%>3W-g<2?*5puqJiq)A*xFD=zkx3 z88p^pQ6geuwuaVBh;tZ^z3=9M%jZ zCEk%!X@sGG8McY#w#r0BVm{Z*y)uQ_$Wxn7uI1Ii^*e`?r414*BjM4?&XIL=uN^YQO1Pz(&n}RdPQQrE6~@+avH4a~Pe4=VRy|b(3Y6Mnv>i zKPe}aNJb=BFnS6!DdqqRYn5-@5)CwXB^jUn@0V{g)yI*HA)Rpb4YEp)}Q~E*vohl?^xe2|28$(r5S$-{20h1V!g?MWEQpJJ=i;wdHTY|)j zkM1Vb5e)9CnL8ESOHd9HEq;%vL0I%Spf}Mcr9maVZkzQ&a334jP*4?M-i&^7(q~Uj zRLm3W2Ykz=ce;?6`C?*6NYRuRAd zQZk)Bh!)fYN}khDo5i1czp=zrR=YWZq7^s;ebFIwTvUPor+)n-N51)-;ECsLx~-~V zDkn80pNJaLdCfUvR1jVDTg9qMdlxZ~KER5VbHxS&J@otT4rv9A@$?k0pNqUUej1;y znxSYO%1%HYDTutbG*zS|ZAW3o6f;faYy2m~n3vrB6SpTe9Kiwzl(oM85eZ-fmarB-(ux@$$9zEd?Z3$taj zUaKMe$&{9iQ?fq(ZiM$6>RGqoMgYocpq=?a5`qMfd|dLD^QTO545 zslDzr-_XL?+DRRI1Yx2L`}3bh@ou-3|C@9LR)38Q?Ugz!qst8kQU!(b-ttH^cKomCzXmb~)Du_|R3OK9nq*f0@5z{9&C$(`!_nh?W02lp(Oink&+! zIAB$+h#FP>6+4w+dXkRfO7@>R(hive{m;Fsg$T;NK#eAz>gK9nLL$z9a+cwpq(Lua zr^}^040=?SU3HXRm3;ioCGb_t<~yY@BOT3nPM;mRiwp$Y-oEJO*%=^B zLJ6b?UZ~;7&j`j4ACZb?)Jhi;x}kRvdN@9XyhOc(0X|x~`l@48+fKuY^k2&nqox&! zWD)6R*_bN5u?PsIIgWU&oXc?h8K=Zj5Gwm`M9{j0Y?C-F8V!FsTw&;+h6RaW4X)x@ zcqk4A%rk#uX9E=-wIntVREFrzx$y{%Oxv}&bl=mvkoXxp-Ro4%c^U?OdM_PzrhA3#khx zp)lj;qwS&#yTg2&$MsZlLzQ{0K!x&8aWR;7O1$X)5B9w?kbz8tXyjZ88`!?nx#d{K zSQmcOO2>0^*3I@3Bzp$AUoZQBKE$d_=f(yRksnKyY?U{2Id<4ia9kFH45fWG zN-2UbOZfY&Xi!3H%QT9Ubw_xbRHx)AiK_GL5ke70w*bm}ZN%o4?_<7;96sbzn_nzB zuEOVQN!eW#npFRc>qN~H10gIzdDrkUnQ=+ekPlg4hC4Bsg|AuAdj4|(v>+2n(|Dfw>(GAO) z6n9K~OmAbco)z?qRL>*p=`IYGL_cG^{0SBQto#QWtlq^3Dq2>@T>{s6`f&o;GRS&MNo%R zmu-=XuO+1&PW2vUl=cVMz)@WNI8<=<=)>|iBXg3d5Dw}|*Zle{{g)azn194(SKml3 z&JScHaA%E3$%sg)lJFBuj45myXii->Q*u!2B}bB5)FJYIL3Cb7TKy>Fzfp0UUAEK8 zm;UKc82!3ar&Qt|Xe3cdO5d$AU!n(0wkn7irC_X-G9;l_Rp#@)D3sFe(q=*#jh@ugTxsA!;iOl zsaD0|1HE5O!Rx@lYqzP~lps{JvdTIz{lmVfN_Fg!o<}X1I7g(1$X4$N_TWG4msQ|h zcJ?_}lH4#~fxL9 z>?dTksNzVP&3+jcBT@^bo%UmnB4@GC;Q`5DV_`YHzjxLWi2^TgZy$#c)EEt-{6PpT zs;*W`c(j?2g^z{}yP+0}y>EDLwQitTV59lU8bQ7dZ1BS$xSpk0QKHT>{5XW`8o|Bc zzlsFRJa_a41&wjsxE=}deb!A8&XEW%GHTd{0*0=-Uf_eM%p`n!41yx@?zQxnH@w+RPlpEo;Am zL2cEPfBH*^uipHhaerY>TO96eFD&ozZ5ef48k0G4_Yxn1DgHtpoFKO#@xn-ahz3SR znYg?Rv^Pg2sC+~BiwcWLPv7|zdxGbx|oAW;EfETJ(Ws7CSe`Qqm& zyNz{qS66xPQ${{7bq4_4R*BkOB~l~cU(^%!qwnH;NwUtc5crqymI$BJO~Qx{1%P>) zNdkpiS3+KC!RJ5%AL9w(w9e90`o1oj1=bxub|j3*ZZZMAamuquirG^?OsyDm@9kK_fr7{z@V;bZ@9qG;jyxf|UotZb-HWPk%eR)4= zyQ;PI#dv_xo|A`!sS-qK%=Zty?>-xVnRHmr(dIj~ElaQ0?Oo=`Oy#9^Mc3uNX30`} zoXu)9;Y{2Y8AW~dW5yrhG7Vu2xk8b?UlUQ(j9Q7Or>8QPuG}sZIzEF&S%*UkP|Cl_ z+^LeXOVw!#-~n zfY7&P?7dx6bhNlCYc3opPieI$bo97um0Fui=W1!OF*&v2&`em}ayNwD>usCigcnRY zihNGaIyDI?TDk3SbXm4+~fqmWJ@E=X&G?==Xk>^Kk%FD0la5P=(bblSa z#NXk9N-+UoVry`~ujSo>wp!rfe~AkAezB9E2G1?YIU;YuAUS8{xV}z&#IM|8F3AKi z+jgMc`&rr^X(O<{2wMX$FR=QEdjqOIxD8Z~>TN%Wa?56YLBV>Yp=Zh&`R8%9>wyU| zX>ScUHSB1BUTR^2Azx)xbFQ4JH7O3vnja^M4p1@8&_)`#HRZUw+h1fc=2TG!<&mBb~Bzj}&BUW>hroAvxCPkoX ztUgqQX{>v5lb5sQ0KnJHL0*ev$;>bM~>2EA)c1x~E-oiomeTaMU zveml7k5iBJF&NCCPB$LVVm4Mapv*D*HU+Ou=L6m%ETE~OVc+P354kV=cXqc#`lEcblOf4J;nuRy zR*quXF3BZ|2@4}Uclw*(`N7=v@>R6OKEFucExxHX&^;QKQ3%X0M&IWH&5zcNYIb&d zCMoURAC1)&U{^O`71h}cen`W>L)RFn$A!?Ktn;!Q2HjbnPR8QciqaHVmOB! zrU?UIn^$bmeAv?nj0rLfD1dG-0%cNYzVM}_Lloi(k6f|R!dh}GZwX_3qYc7hYO~*# z!GGdI)3YIWZ2?D)bY?kIyfi15Pw8X+6}|`I83&QjEZk(V>C^9)&)fFlG$4b@A-xq6 zR}jPt979i3(=r`t8)rh)0T}~gW@tF>9X#I>QZv?6*9Kodf^FmOj$3TGIk4rA^lXgh zxnq|z3P7jFptP!Wef|2>5FI6;nM1liFHx-iW{LH?1-b#>F*>~yHX?m7d#VWjNYNr{ zmR)O=t!zYZ@B zN3ew4a-dnac(}rF5u}$HG2R4g@05zX(*phoLF{psxH1xIOE1j05fty4`p6l^=C0dYsQ40oz8YVwQUhl($P^xWL;_} zf?nPnyA(&SzcCYo}`hg&^T%X7}n1AB*9WcZTfAyJS2UoI~j?zi!Z zS@+K1wI>-Lz~Z_8ZnK#ZbJq7AcnqOTvbXB@Xpmb3iLF6eDtU+Yk}JaTQw@S7GI zz&s#pS0Ks=KNQ^IqE$1)YREs#&2p0M97cg z7USfs_K;G0wZY@oO2-){$p8RDh4f$&<^&YL%avP5jQ@%b_!I5f1_Pd!;0Q8yFRfZi zQrlBjCLP9vxdsuBWRehlU8FYXHVIvcaTDky&Q7`in(vT0s6Ll`?_BdeE-9~TX}@)? zN_n1)-v=Q>ZN+J%;Q-K!;a4&G#h=rIN&M9c?dS5#fv7^4vt9C+p(WxFUb<1iXlaLg z?hIkM&BpYAr{alnOo0>$<59hhhCmq%pjn80OdOw1aNQvS{J9-B5#cZgRXW%LZBHqp zlw!XmULcVO{(MUNw@OhprW`*Iak{pSIm{jZ)&G&}@ zQ2DBf+wxhxR^?ei83*kXj#I`YGs&3ypOA49P$NO7DJ5%jtlf zKxoJMm3gyV6CT_;pCOULy{IefCQ@Cw_TS!c#N>g#7Fa-q*8H{NmB0hsN?Pl^(-7=K zG#PO_hD&4*Ayl7#`K9vQzufz-o-ta$Fs?~hQC0$nT#|23rU>2vo0>vQoVWz>a|QA1 z=?0NT(3nP3lnfMzl(*bOfS#Q8Q~uChc-}`bfg_j&(NO)Q^$chuRu@%FiV80nV-9sc z=uS{U%L2j4W;uoNSS3|1sBqAzt}do(HxVoZpvRWTy7#aK06w=GnFPZw6pA`^celx5 zi%oX0DxxoRWmr=|Ko}mNvmXT<0fCcPg0yN)LPZ?DEKKz( zp{Wg3Ng2ZfpHi91=n&3yL+Rdco~zt4=AtZHGy%Xc+4Ot=SkMI_gJN<(t=Afz!7AfF z{DcBz$6vdFs@KORP3G@BC~?s@B&2i_(Y*kNvZ8|#BQs^M$Q;B`~!kWeoWCl%ha&UNj%0uME7NBH3aJoocJRJbB%$JQliV+++ z!|v?*Yjt+?qdLeQMuaz`(z=-{7q}zlg%|Mnblj=NEwa-egLL75HzGV@6W3lRhMdhe z>(Y@sju%cqYk`v`Y$(cu!Y9p2=zYlCj26|JiN_Bxp28_<6)xYgrqvk%hC+Q^^W@Q` zcjI5}=7NS}%A!k&cY1yhIU@17#*9Nrj{B|;xB9nZX1vO!P)e93c9cX0uP(|Hr2#fk zRxZ~mEuK~d7lE?TiB$!rNUx?2l5Q=Ao|puKMa8;zcP-|>2I!JdpjCXi+VepG5rc&gfwm#&4 zFvT`W=m|W)>D&+q;knvR<~d+ag-#!8z@XyBoR;EH$vOlxXugI}9Y_60!+6B# zwqyOluDaod27m23&UNT~V2ZT<`<`|V(WS?VNy)Iyt4~aQ z`j+n0l-0W4S!LNSQ>g>W*aJMOPa5^)hmL)OPLp#>D1uFU>@H!h`|DNwMwkbH+qB)+ zRyF76@Mf{{8i4QNd+hV+cmRc0e7xa`iNgti4VGS0*5}U$_49aj_2%51g;APkW^6+j zHpnFL3?H{m`tXr5X;}h+c#O@2@Dm(t3!c@%RZz_M2nK#}frM0g$lq8xh}-~mhL=!Y zphO3S33BYR6O243Zb@mvrK13vw6Fs);Xenxf&xP-E4#S@0EmU0fGKhsh^`S=kSmdi z>Q9FA#qc)FeT$p}nEeMXdS^g|tMb3kr9Q&W2#T(a255gli zLaQhEwlBR8hPdM*)NUFGha~+6!5vEXJ3I1TMTOdNaX}6E0LR-M=ac58lu*}I)f3Dl z_Y^1uzt>oRli23}q2uc5vQ}AXyOVw}!t3KCr@D8dRI96_=97;802I#kGxlsqRc;QXB4D;Ybp=$nWS{{5u zEM&o+Vf?;63Ui0&$9R{LkKG1f;0+nskWuy!fbkSSLoz5M8`Um`a5V-wYC6FM5Btz+ zKa&t1YNKzje)sz7xp8&nKOv|Xqmc7HjPD!EWWWCXtDReDM-ZvUd`y1D!%pM}I$0Xm z3+vTqyiUnop>`h2?tH_8UTz6apcXI{+W61ZUSU!rJb0%mXRBfK#qaS69Uuw5VKDs? zWx)iSeyu;VXN*`Kv{&MK*~cZuh;>r7R>5DCd%3wByi+0$vWG$eD2RjC#5;gClu4bF zi|Zh(+wB~{o`2zUpMJeTF+u*h-2JYudg}9>ew{mU*`ij>{|mNYVm%a-RPA))udo7p zH!f01@6{Socs(}rDtaoaHn|Cjdo^}qv+PzQg#W&_Fyf$vhonwC!GvGkFN;?Ea@gFX zC_TwJP<-<|xgca#MEMH|Nxj3S(y*@WRVF^x3n|Ya;|;%|>sIlg6ffGF@Q^>=o;)QT zk55l}qPJC^p!cz}v-;0}cC`WXWexy!c;agtN6YcAE57R5uaB$P>dvq0+aG@F<1zp` zivUlz&nQhXH(T3n-o6P^c4x<>`G3r5$^Jma;H`flCgFHsnL_bz5k>C+Z8A_@#h#Vv ziD>E`4qQd=pBJ60jx@uuSE&@r`rOo%aYU4*A`+E_o*u3D4){mz6Gzv0>BEfJ?Ms`@ z#bT9}BH^gX{r&X@)Ij&|dzbb>=~p+>OiMrqD3dwfhTNmB z1~SvaJm~uDYU+^%Juq9#PBqy2y3Yw{c`tZJ`o9i>kOOO$! zR%1WV;Jht%Jp58{9J(#1-p*x_P1Q9D?=R9DJ#ne73bHpqRkSpDgw2P9uiQ07b&wf4 zk^6j1O^Sc3d3~(*IfBe|#pA#D08oPgK<3N&E8llpUt5g}q(6W1pW8n_wm(Z>-|nm1 z_&;vzuksPLoA;m5IUTLQY;z@Y0Bh$p(T~ReAwBvMotP1n9D_LgjrvFc_uiipY^1-- znUupwOVW!+J~}#3-tE^hVRmXW=4%26%zgpwI**zxpJXc?RbgKq(~c|iPm?VFcr8?v zKk!zz_)f%^A{)N7^SBpgNz(Iqo*E*3Nq)=K&6yvXa6C5V+@or1-9sPEw%L5=Tb|eU z&7|vp-d?2OCC8AH+s3>#Wt<;4+i){{HN4Ia@hHmaQ z>7?+bIRUs1uKC_Csy+T71_X6XS(5OKVURoC%J-EJ=f_-}>NVJF7hBthulwKO_z7{^ z>B#>Uup*9WKC@ilnkTtYmCR|z9OS$4llfJS%;W$G=^wn+*Snq!Zx|Ac1_rh+N-hqQ zl-L=>KSucbio3@>UiQLXT7R_=QPM+dW`{^#R^Z~Cd`rHbzKs|PFw4_DPz@#V=AhmD z`LS1){r!x3wQJgWpRB9du=2;+{Qan{?{o3hrYoNFx?K1CSbVit+QD@r&)udg{wmqW z`sbVWvt$&WCYWfxcO}_TabN ztfgWmMG9QHRwKVaX}?@Q2I;{g^;Q40Bmai;I0AVBAf!uqgyPE|#ELHPJ*7f2CBsobBDKT<>_q$A!J-!)p4Y_vw_Y=^{d*21d_NG#}?e34m=??U(=Fb;$Zrkpc z$LYg|$j()4^wi0X2#=;2lCO2+Eupsr0;>ho>=GBnWkjH)FG7uCH}msj1Zos-1dI+l zOhjw;p5ri|));Zi1 zK{b1)N97sxSTis|ynl-G1-YgA;@JeI$N!9hp|!KqviL3dsUh-Fx}kiPs}H|nEvTm~ z5qVZtv7`(L>ua&DYHP^d6zqln zV?P=z*y}{8Da-3VGn19guKV+YyKTE%L*p(rIlANZN~jw>biNgjuj5#W%J=zIuHAH6 zXD#+sF4BYz@@mTaR3kJU%q9WMg1a_>F~*3x#_)vZ>C%HgUb*=EJV2&*Y1O~~YIk8Y zFrA|4?xQNBc}=|ps~=VbL$N4*r@`?+pcJOu%a8hpuCGEKSzXDSOFz6bLi7XC_1e=5 z#2rsv!TE+}k}|URJr8OvM6OCS3-EAc%u5^mjmdM2AdIUz$fSS!z^2$FjgsQM(WMuf zUP0j>XnC%2Cl2cydEz)7EbyJT#9H--u zOyp8C=QE##pmn27r*$zPiHBii)kVW%K_LT&X2(7pzRd~7{{XC>^rRjMRbZVuuTo=q z|8bo2JZM;CrU?nGMfZ~NaMZnvc4>hWxQ0PfvXU*v6dpNfD-kIk zM$bEBnJO^R6Z^XmBz)G5$*erBlDlPs@VnbLu)H`YpI%av&7FX5@R1p0O@*c$FW|{_ zki2spQAO-pB4zGV_xy5r!wLtIR2H|5rJo>DSf-gB_=Xn?N}YJQ4L#Ipw5$Kwm|Z@p zA05vmPYDj{5Z3BP2n5d@0E+YP8^~ZbGDOPOz-L)gL1l&B$*9~ybKvNTQRZyEE$afgm_L0}f2TzItu1Sa1OhnBdkC-+<9cw#^EZ!;LpiOlDeWW>R+fR-FvU z*Rs_C!3Z6M$U6=;DDNK_ZyDyu0@|+bL8_V`R*>~83Hg|wnV^ZBLwpqf8!p35de9W2 zG-IQ}2pT1pWsMEb2$-ypQd*y^X@+cu+#>oVY2?04cG%+bKT<5JDvuRWkrOW(I5n7~ zuvd^j7klOku(jpWl$nx7)6u5_G@8L1>`>=2;{zur)~rv>&c*(z1SyG~H2RWXua__k zX|~=l>xUp;4;B@~U)bOh5rMg}y)+$v@bDxkiw71}38O+R&biR7Wjh?7bi$EA&%dUs z*(axhFADYE%`Z#MU7LXU>k!TxW$SNC$#xfI*4$0SDX$tzIdv`p^J zZqBVr4?2RjuJq!xgmDH1ttIh=r}C#lZcXrRx}q7(EmW`2Ojmlg-!XXYxq!ui$8ovc z(2dfhu2`xi^Dj>Zyxl7CqS7&?FjJo_FF3gk_YC&b7e!KbQ7(Y0Tb z9Cn1UDhItLP2bbNzLrfa*o;N#>e+*YHV+D)xZ{yIU3)_q+LC5cjJ0uB6v{XU%o+>? zbg*z0)I{O5ACubtz%)-C~*+m02PLNvlGmAQUoigr*} zqb5VUtRm_sa2*Bg3?b`&kxC*m@xDlg+KDU2DZ0BWhuuB1okygf+A&VDHr_(;%F6yE zl$4T6h}E6Kj_kJr=enZj14L?_(A;tPB=~ERIMP6nE+QJ#nf%H^%5f=W(Y5n-jT$nejiv zJx=aXcmCkf(LmHFi16B5FitS#sK)BQO)Ere^)s;)vcmIPFmB?&pH@t#?;Ll(_MdOJ zJjdiHV2uNHEm)oTID!rl8uFC(y)Vyxfsai5I6Eib#Q!{}-`xJ#E8g_vcPn0B-@NpB zn1$Tzep|O~pPYo_Hk*E=TPWSC=fb*_4!=adyfmFdN>;2>2rMh-TIlG|E0wBn|7)8Kf9@t2MMG_ zRG{wNA279xLeGYBaK~!=(3Y(n(ip%v{B4n~YKv644hHjD@H{&v3YQIPODUe7_|6+M zxfZAN)C+m-qU-Yr=uTNZ<$F1e*1nGKe)$=-s`EUUyzPYlar1t)AI*Q_^HGn#p7g$V z>>IdS&m3yaJ;M#m|M$cjPO-`*+vl&y^KIz0>*rf^_i2hdASH3#jP`xYwK{8QPF?l+ z4G0#}!Pa0mjISuL-lNQKSPXVs;QHDP4KTQ5C5K3piw)ws!Z@YBA9g=S%Y`HI7UR(fZcVFb%e3vQq0E*l;YE)rNG~IT2 z$r~-H_&NAH8O#4oxPbMEa`-m;^LE+e^i{vPzH|Nkp%~w_ohd|j41>vF^?ygTvn0@D zXc|lMrsK_IU0-T3n}yBp?nPdt$xiRvJo;55o+RAO&iUpd z=Z`0;98SAs_vrb&*&&JVP#xH)N6DIWOeVu{w-UG=0-5a)sz&uZR6nW=j4pF zjdjWef^Ug2As`lvr2D98FIJfF{fyqWoc!Fs(rsKYDx01D-1q(5W+{8Ie)j!1|J(%p z^4r+BIN#@9Il$ZYzuS#c zxsoqY2PYps(^zki;AEjJiDsX-<;!ibW>n+*KIlJxcp@l2`NJo+7RM<(2V2daFY9{R zVg-gjUs<2klde1G0FQ;4j;Ca4|ivz@Z9D8QW;sUs z)UonVw-GGc^84j(|Eo(~%#IPysZNL2UW^uN+{_*v-&wD*!?(|18;685Wg8&)-?3{$`px56^$Lb&E@LIl_8hc5S07)iy z)*dfz91o)VZlNbXk-u~TWmb5&W>yM-s3jUW**DzPwe!E=cY+RItana&ZiZgJ{TRBG z$9P`c5GM}9amu@u#3a(;zJZ~Q_fo4jqe*?c3FSZ0>GZi|{k%2{e996kuFL!x&~&&lHx&h(lHIm=ocF7UJmhQ{x= zK3Iv(jT_g#bu&xZ-~I+-2Yj>wg+N)^*i=d$hcCAuhQ;~58oj1xOe@3zc)wy9+PWd2f;dbv|_1olPrt5aT&<1x5eVL1*(si|wS*)tp> zO<{K!bj*S5+uP05{L};)jmE&Mju-soQWoP)eX*JJCRaKZK=IGrxMttsptC<8(?`Zd z1>-xnI9w{+cL{2`%~Ye_ZE*?$`kz*H8|FmN5S&LwC3tA|D|^$yw%ER+-`5Vq(qjul zh5PtJ)(gpb{k5b{7RMz5T0^Ftnl{*dN%gMUfspiI9QcNc+ry71*k%lRzKD4b%#o!e z1U&ajq(NuvtD~J*H64pQAxWc>ltRM+B*TGkB%)_wCTkcs19sj|FKS!#p|c*hdx^L$ zrdZw^^Aj7?ks4X20>&ZzNnc83hRi|dFVLRI1T^e7d`=sf(SFuSO=HQqi&#Ocnu*tD zLJ`hxFQM8|5IMmCZE9}(n-(7GrUR+E2g>ssi#6Jf7oI>khiM~oQXV>#MJvxm*;S0K zlH*p4G>ogzSB`t)owfXlUMSSLAXzZ~gXCh(s&^GC)Lw(7&&>tP=~5P4J%vU1{(WeN z0eiSq6D(rZg_99RjHVmG3*7DiMC{>UWOsp4BMe}2o6M6~*`k+@O>goA zb(fF<@t^kB{zh<{@;HPFO+s{cMIVt6mJY^=bZI@VIe zQ4k%KleGZhCdCTH^55FBzgFuf&Uk3;@W!mR3Gk8mO!6?h;j2Szwx4NFOE; zhNFH~kJfE!9eQ4DM5fM*m#A419w#iOtQO3_z!o4eC6LAt{Lc@+YlDkEie3#C(W||eg%-9z)9$duFfAhq}#(j!qv7kU}9)Y zUCjx?M{?~)(}^9$Q@&UR|4L&fHytd#W2zd+BOdS zR15>MVa;`9L@=27Sal5iHva``090k2*58oLV2ege<+rQK{*M zez{UDyyU0|01_ni+NVt9rf;8Edi9fw5jeJ1J(D`w5xyCMf&bgM&X?kMz)lP6%l|E| z*!wA3Nmc`UJhoe#PCFWBh;xTr#3j+#@_&M{jsa>auH#p!gJ>sD$UK^&p{<6}jn^{U z8!$;?TviK5A%FE(*tzVfvTW0gwEBXMB#_OdDCFX%ILGD~9y~sR+QK|R#-0^}F2m*U zgqfyA?V^a2TkbJvxBs&t0OZ*umE*=_%4lZmsb)Os&@QmOSvPHMzihYnjrYl*0mAw} zKLvm>8ZcWd5pBwx_oPjkQ|XTwd)evW(JS#yS$ykTMQD~g$Fk`9ENi}8``1dR0(RoX zuWCd}Up#B<>JH=O#P_ZDR`RvmTlw?9H_`)_0Lb;H?3FMIx!Z_>edy}#=nfp6%m~ji zX3Oh&@qy|I_7#84a@#*R=;y<&{BOU2EZZ9SWKwOhkA`MB33kc4#(1Sw-73EJrT-{+ z)sjz_bR)^?>%dn1GIp)K#wNbbArM|)MbeU|1c?QjLNd?);{td*e~=KaYO@N-B^`)g zMmGGA%`VgwEcZ|U4^9983Q=G+yov9nalv8)ow+TXUg`1RUbH}yOmG2w_ilJGJX^0_ zRX3O$+GydF6U*ptL2JCJN&n(6j_5BN$1~T4aMcN%rhXZp`QSS=}qZ#o*PoIZDL#}1>}TW!{TV+v=;vkm-4Sos7hppxI#q2 zN>bV5Yl##=C+N;XPllh}9$^0D)j0(dmy>%}SAe{#u6EI6vYPKt-1>!!GvpJFSuBh8 z+D1NOEAoFH$Tk&B{>qWG&+Iw1N|J4rA z@ufICZq$ZkbGch|gSfQun|q~}ElfJD*S_xWCB66_>n{v%Ye73N9WsD?a z46a4}TKNF{+wV0HwjM{MgVD+b=Z;nF6XFbZQ6hFS4Miei)r^)>QPP)-af?g*ij~@X zibB}b9-&ZSryWD!d$@)F(`ivsyLKlB44l6S->(`z^tY|hQbkZ47T}IAdzVC??JO&? z$De8=9E@2MH3AdMCDF+*5j82uqXZ_rk@Q62xm%bQfym91oqa^2<-do_yJ`I_1^!YB zo5nAJsx-TX?P-g4;d^U_*yXdZWtL+=!&bv|f` zhf4PS>BiQ~E*XW$=RBEAycnz^WQnlHL;sE+%S>xd8VIl8gkkhu5x2`=&D*R8nir`TTc?xN3fRp0H=%Fb5p~7K}irhRiQ zw_nHoL{Uff8eRYAt=^DZe)$x_FYSp2VTzhCnN(ltX6h_}K1;Fv{_}Ap`us% z2jMTyVQ{Ev^7NlNEwU&fa^*tK8zUTyBT(cPzulQyNpS3+5bVEYq)nsMxh{4Ajm7Xx zN*hdHg8C0OYL|FMh3gQ8p$FClebx_>CDD>+(7IbT0+7Kb#qD%F0z=B~{+gBGp^sJ^ z^V9bR)0maU$^WGnd76=wu~MppECjA`m0M59t<@;7o~$%1je8i%WDwrxA}EwA9BaP( ze;&*-0SIeZ;}6z42l=UynEgs1n6eX4V!Vt+I^aVi7H(=E$?tKq9yN`tydBM3kh-X_{;M)e-nm_nWb#efBQ-<4!I>fK(*FUBDlPr{2gP_!?ez_=O}cP;ncAwFLr zLUt^*zqP-#|CfKL5;`^-7zMD#{~{7VSqTXWwO;Z0DFs&?#lGUl6Ztr_KSsrXyJqu! z6V>sg>?)&Bb+}DCV$b&{4y>w)CRmt_phPTQ_`vk?if+`aHYPS}NhP%5DBvmHL7G7$ z7?5%m7kHMQFqxo+9Z9O(=(Wir;Ku(dZ2y4~$sM7M)pOK(vtU{HX*FG4Mw+&HfAw0< zwr1`s{ubLJu-reLWM#0MY{^~_NQnjL3>7~Pu6)mlz^H^_SgNv1GOQCu>34E-(lZ?7 z0~RH>9vCQ2)*o||Nv&ko*~QJL#90)3{TuH4Lpnv^mz4zoa1kxVk%p^l)0~I%32N z*Sc^aCq+c>#EMukud{)poGdOpt_#yekpzBwO10q(#DB?m=~n`Vwv{RZ&ERaCBoUoO zZPe+g(&ljB4-*02M4rK(BtvC*Oyil|sT>LzJ&co#6XXL9j)==|4r^d~E>S29tD09r{Uhi5frA6& z&eEP{Ju=F?Df{KdU%s)+_UpV2aLt(;mZB`UBd&Vfn}b%>uQbZvbZmBi2`ZBvQ`EcUt4lGF$!H(Lx&7yR0`dP?6OcS zg_aUg+ifuVGT!vquahkz!;`{Lp^nO#Zvu0zon6p-{41P_EK zf66K|BTlxz&!&Zg$xnglyMx|=zi0AOGoYY5M7;w-zVvf=CTN4|Fq-%XUSb}LazBqZ zqJA`PgzK#rzh%Laz-O6W8-4SypY9+pA(3hR&-mZ}L1x@zlG)=Y6{&5c^~%fHq0O9H z;)XLbO$DKB3DlQUs!%x)$0h;wWw4|f9Cxo+Rr`kfq;OMs9H^X6mckkw7_98VA_6(* z=;_e04w}H0pCgzhhmp)n0+k`-knq;OQ!ok3g-f~8RWSiqAmxS#0-g0sp~!X9SaA~o z)0UOQCdP;RYvXs3=RZ(Q;mPGXpM2Wgl*Pvgle=R77m)#KEkE*~ngl)O+nm&WBY8AA z%npEc`p+Kx>>VsfiU;@-QYE68_)4oZ?rAg{EHp(}>%t`*7{m*JMq>A;qtY)@4b}$X z>!6KDfSOzjGX^3k`n4jnnT|_tcID=U&vQ_~fEmLL=3^9H6tNUei$~## zj=x?g^L>wr9aZrbEfdCdV^PrsqtG7-KyxchG(*%Qn#DK`ug*mj22ZH~RRJR1*wQFP z17{ohVfSO0NeVk0fi+T`F@z>r-Js#gOAk7wbc22#Xvfhiku6(VY#fs_?|aPJYdivH-y*@$ZOx*>V4x&VqY zX<$iTm4$I*$J4mO^Bq%(Y3iDnH=3tI$K91jst>S$UMP?6q_*S?aDn=u#Y2<{3_KXX zCUtiDhr^t9L?ohqt5qt5cBoA7S=)rQ92n^TPh!%<%*`@Gc#Jfd%cKK zZUR`%s6<3Z=RhhOzI+cL(^VDEF=M>-2#3H%Ony2?g_b(cY$kZHEnQ?U6P|q)5kN39 z2Az3$C!in|u?>wxIIi%^%VIiEkCQuT?CQXHN@A26#;>>Ek5w)E7P`u1m8>=u119Mo z$T=d7KmH(GlZo86eokEq8^oo1^IBr(0s>7dh7r?|DX30R!>bU1dw|La!S3BwSZ8mB zF;0hhFj1JYyHI-!KPDn9DMFz=YV}W~UoKJ#%n{j5-jG~Bvm3VUWpu5Si`lp8XBs1_ zN6BqGntKy!XZj_vv0{^BS|dB&=z>+D&ZXyx3ud# zihjnRHaqcYHEc;qXhTO)4WZ3JG(S2TN@?K)98wc|C*@@q`rlH9VTT;dyu^Y_z8r## zQs~gSZ1S=be8%63#hp8S1_75Fk%<{0VwFs>(UOG_^vJ zYCja@bzulaUT7HZz4Si}lcLaLjVJ8?HJVcEzp+RtfXF)^t=0Z7*y&J*$|&(O zX;|!0TT)l#A;^k>Hgo{#%gft`tDCT|Q0QwM{RtmSlR10^pha)Xz^d`=FgbHs>lK7B zaiP7CPOzsPnjTf0XN4GNZiuxGKm<)+6N4~>&Wcrvc>k?LUyB1A>Z`Yc6<|1fLzA*s z6A|n&U81nBc$Uhae9jaISqt6$zbMD z%xh?>iSdN!VIJX{4)Ch zA$++fECT7TB&a1suD=`rAFC>}Fr3>j$1^7Qi6W+nk+s#6+JM6?qriw23M{mUBJ>+n zk>MvXXULWu5Uj>6KCe?&_~6D|7he{5kk+VDsq2AX@{h?>Ah|d>4r|GD*TK#TvoQG- zJH?G~b8aWpvdKF74oy>fTn-IU50YYp?8WQSdcxncBe-EEa@hUuVhBT(rmGE`$ff`5 zGTABtvv|kwpihI;Irc27>+9>oJ9pa?oLaYMI+;?zjH(2*1lZ{@EG3td)*#n};I1w! z&*Vb9Td1D1Q1QX^!zu%P!|o0HFgPP}h=6}89wriyeHgH#>wfr?zta>*1wa2C9*5xq z(9LaTgd#gv%FH?o>_S6;$c%q!KZ*FKjVl+~su3wiMPGGPeV;^frn=9HPJFnRF0S=v zX+^0R<28J;S*duXNsevy^~{K*b6d-W(e$(qTY}%M8p~#s%wLlR(Al!-m;PFm2rn7; z|7beL_PE}+3&*zYG`4NqwmnH>dtx-UF|m`zcH=ZQ8XJw;C;dIg|IK`Xz4txWz1F$b zMVZ*nz0$?S2cY%Wyf3AlmuM-0m|1u*rSGFA<_G_U^>zMYROA#R#>D;Yb?rPVk->DC zwj&}>UF-S)oh*LImQ+A9E(?i$O4;qJH?!QS6UxJ##w%7NmqA6)gi62TL6b$*x%zC= zW2HP+_@GOR+9ZR@Pr=w=5Z2(_{adUyYHOk>pPo4haS|_K-!14~NN{@Qa(@m!Qmh1# zaj5jeav(Pf$^Ac&>Qm99hcDEC5#lHnU&&QFn8sjpP;f9#zL%!l>#lX^|0a_^*hu*u zbz$Q@xs^$6@>(V(bR=Ez@y;35Sv<-#fDi?EUtXec76kc=g`Q`d$bYmS3xmVkzD6^x z6BQ~}*eN;23eCSi>H-6WIG~KwxUra?Q%pEr;5cB2BBi_$f}tC0GIERwtyG2~nu@Z< zVo#C}s;zo(sxhn_2c`(}}CCR2$pJ^}$jkd@(7ntfqYjuf?WMOV9 zC@pcMbFxckoj;v=lrCn9#gS2l~gW8D&krnX66kAzkCSB@QFsCE8whKHW#BQp(Kl4 zF8ue$3b2O9;_|@gRV9j=*U`Z*%z98MF*qoK)#E-Ln2b(X7p0r!ph@~w7kr)q87$M} z<#|#XmN>cl<>^OQc3cg7mLOz2i6bNb_T^#&vgn?tG&!!WQGQQ}+fNT-_n?u6XqkXQ z=?Wqm>1uw*gkeI!wuB8(8FycCk@2HUxWREIn#`zf5KJ!Qj~ItORSbkgn!C<)eKNBr zozr8QokqAFuCQL@cV%gBpgn2xG#L$KF0V(|NLI6wBuRGT&=+yf{8s$=XXG}?PlJH^ z-{X1tkY@_thd)Y5vQmmLH`Mlp=hc@lP&M8osj>HJ%;Ul5!1DiLQ278Zj__+tK0l=? z)i_NegTo?sk}FSHN+R=J0mQBzog#Mf28}u+SIrM)!YFd#+N3@ZTsRJUU2M}$!j~k^ zG2bRSp~D<#W+Vi)Tj0$|Z(c@7i5B231SYJ$UD1jDrUL^f_xBjOq>Op4?lQwidxk|6 zT7?vr7U8N!J=8`{Gg4HP5%*g$w9yWAx>aZL@BzuqUWmAqFXBt&Z;^$2Sv>h&>rO{P^TF&`ip7mx7qAE%Gm_}z9GS0aA)Jq_T8J)KFsD3AD zt?ISvWCng0rF#dM&nvrAR-FL<4n!?f9Dmjq>x>6QRgDLG^oZX2vbh!nH2!IMwn`i0 z@B^J!w1YvA1tWe$58E5YQm9UC>BSbk$6mQ?y09KjKbAZJ){vGn-mT9d#XIXJBz8OY z!`@HE*zX=c7WDpvntcnvXt^L(CblE$1l$a+4LwUUbAp1VTCW!7Oh3)LO~-%pKEXBW zWC&`}$;$Xi_6>=DOPbL?-9)W5X*@Ab1#zyp&<#b0T zAgw$r3|(46IhT`e)Rg`pvKR@c)RS*+7~iM`eqbp+I4ujY|9saU88e>D(1MPb*YiXZ zScc1*oUoOz&TvCBJs9FWQk0^X!XZtnvqqNuF-<2ozFsLh)5E=$JFnoL>DmXjDu?m~ zStARXGhJGD+dcnxObGVVj}nfC`fu2`=+EqdtKR>~}wQcBdMw5L;M+Ep-BdBWF-)XxK`ZJ;<>C5OIeHH|bM9)lVcuQKr9hIAoAt z2A9#ilbf)Xu0YBc!7XAve6Iv=LP@o10B~U5Fj8~FQ+EpuLc>yEw|s8qB(p{#j54!i zfH>j~fJUV+m*_=P*|33Weu*7sbv-T|jS$%hD2)_4Eb_F9YuOJpv&TP~hE(r{!ZfIq zN%&N&IXnjGwnF_s6>FaVoU%xRa17qdXB8j%SSdzEdIKl}O)rv2g68|SGUQDM2YTVG zu7Os>FiT$$Q%bb>pmgDaXZNBLcDt;1YU4@*x*QTzf6wRMRBdum1vDg!DVFa9yF6uUfI*PaFupR7>iHgah+maBUI83(vgHP^Od@ z3UMu2X+lcTYsvJF5MMhYC190W%f`9E-*_--6=tjvAo95*D2gKTRP_Nm8Xk`IekD@_ z$wd4%#l(pk2+0BN4`%zBdMs<4H`BNeWLGhfQWn(p`*&ob^n(=n8SW*Tnb8Podb)Nq zYDH4nRHUzi#+{o7s9n?PuUNKaUzZVPh$>8cp<~S>Q$JRVI<8qxKc^+aq{eEUo-WPS_=VT+CJxeu;3^m_wBDBEC0UrGTXh@wO>ULQMTlT?chA&+SK7a* zCM%6HleCNpag75h*oc+d4gU*FmdLne!HCKz!@Q2re2MQj|C&<+paLg3?0Okc0{xn>RosBuu zGHqZ-h`!6CBre3;f)u>2k)gtzx~z{2DgrtT6iri@9j zVHzws;Rx+T@n0tuLx&DkAX;|>vK;V-RU2{$WFA4iC~-)6dLD|#IDJB4FAXioKOjV< z2dkD1CGS&h%&fYG!nK9ggr}}aAhPu7>>Kv+0wn&f~j)=?0;B&oEz)^Zj=r z7P$rDJuB8z+6KuPaVxJolDqQJQb?Giee#z*YR8U_PgJz-KcvJB#QX=pf?e-(o2z0b znUlHTDrCdJ^hWo-K$%__YEQZRjDiYA{%%AIt2%u@9y%6VNt&5dWIAOMJl+?qgFn6L z*-_kts*n zaI-R$Ve+%2MzS-V!&O|}T>e*v%I9Sezj9uN(WE(>08pZx1rp~Ck=1jM=|?KezOhaD zCz$*R|9^*?!vHbVwK0ToXO|ebUlBbh>Mfzyr%edC6dfL?E@ja&N$V@q9UeY~Lv1(y z!M2HKC;evL49uH@%}Me*3vaJKO4nHHQEOCK6%+8ElNNqu!5NMdZ$`CWeSx4F#9>nZ zsaJoTCN>o=!2yGhsC4Ahiu(HvKxXXSTgZTf@@*6+6wU84R6!JlT5WN*Sj<+)(}Xhx zU}H_Dz|NJoU~bi5PtqUG_<`4q1uSIKcHAr?$%(?B9{%IHUv^n^A;S8!hAB}`;es(< zbW9O^xlRS}_~%C~Pu$^A|E)yi3>t6UFQmQ4pT*RJDHzL{)j<7q@~6jJOVsw(%24vs z_lfEB@mQeRS}Yp2p+$EvfsJ@Z3@d{mM)u{yiJ7)pkKH7)=q|uQQoZNZXiG1 zv?-C!gr!cayi3>2b;w=B_xk^ z)%+{(bwzXXX#eRWSX_(w#Xd=Y$*Z(vgM^G4+eCvL=r=jQ>YDJ#PVq;t!aYXr7~&Kn zWAceLv0@w87ET17McobhI3{0g&Pde2B)E(nqQz50HLvI13HWUMp4fn;QuA#4KR@kL zl&8a>r9Z){IT*2>Yi)HijV3M)rTnmJ_3EC)7Drf-EbCb+Yw4*8aOY1*6e9atB79QF zin88xsfNzG?o%kpVcta3gb!Tp3pK%@*11*F*J?GcuP_3gPv4{~hKY_)5B$X7aD0XV zqWp23skzrH3Z+ZL=P&aR8ULC%ZDxjf7r^1HoRJNrzGQ*b3Ac$#DXMt@xnOmOgUfuW zs|g=qE3eL`yQn=C+eqeF689kR^%LPYCrnqP0D03Mauv zlH&c`7WPUeC7dm}Ev*nglF!dncDOns5wGbxilPtr_U)Xm@^5IF*m%o*bH`O*9^-P_ z6S576R|&^7S;G#9$$PsOTwEzf#1}GD4v|L|YG%yWUu2OL{bz~iwY}IX?qO0HpH5dT zIPM%F>4Q$H7YLlg{I*sB0e?G+WHNm>02OX2R$0lu!t*ymJ-X5u-7S6UUFgLbVOY!b2XOIQ(VGqi6I zg20N844Tk&cW59cPd)Iggg-T>ccYw;~ zvC49YnH-z*;c5$T^B|*sB@?T;GCSD2dve5IQ%y61!af<);?W$05o*=tdL>#Ne@NhS zs7qH*e#ouzI1+_*vN7P!Lit&ZAY40-Oq4i|f;66S9$3Bq8O=`|D&c^QBvxGGI*y+rb zt$Qi-bIt%d0M{yGhG-28e`xT9YJNS(h%nJrg9@tvUUzS}&vB88!xKutvA(1 z>>FuX_<_8|k%X0UaSNHDhE%a$Mhf>9Z6Zu1EvJ?01(X2u9iVg!GW6*u2C{E9%#WNw zEs`-l4t0<+t@r2*yiGuKm{I0q3Q{4hFt1RGuzr4(ecNpl9D6?Cb8Dq{AhTxgO?~S8 zYYyr6EQbdu)d4Mf-ov+&WI3**C#ieZ_$+|axF$YjTZu{l1YUy4X4wK(eAN4#cu4y{ zSZ&uYt$*Vo=MemL`AV(5hsKGPcxH&Zf^O2%_S$8&A0CAmfL)GHYk#ZBM;R+dic{8F zsg3Z1cJ&(p$kH%9DpkiWBdra=cRfPzN#xGTuI!a}kc55}gs^a+2PHGqKbk@j#Z7c$L@{qBpMkui?<&KIh6WqPjY}GCC2aU zID#PbL6kKk9DjSyWAu#HBuez#+%IU^OhopS^gLg1i}V?P{%W^A7F84OBerMvodiM- z5Bu%6)5AF*x^h#b;7|>o%P$*ca=S?XPGtOWcWm016dGe3S^<#1UXRrch!c*KkRHD+ zncKO@QQKq1^?20E-7OTNHCZFKvx*{nvJ8S)VLb$G=?W2sqe0iJbohqH(i5`Ao3K^Y2f2)A+9S|tLH z?>DmC1biqe!VlSP@TgM@#%aWs35Haf=t8hy^|=xzn__9IHwGxkjPYlzKNtw4q4sq= z(w*4#Uvp^NHO6Rf5{@5N32VY>o~gv*g4TXbIxu_crXr2Rc`gSN^4NUqP!7`@t^Qw zR6+QF#`Dp#RC3;k7PGbH{`uC_S%M*T{bw!)IP0(Wck_`uEA;#fckX2EX92cE@5O)2 za6N{^*=2!%xY$zR;n;N&c^eJ7$W9h3G7nM1&EpIE^8T3tokkbGarpcTPzio-jz7zz zE{3Pd3o)NN5lLi;jX`DRFWj3qQE5w@uk#kI!^HonkL_LLSyIctl!7oFB-V3Qv{$;^ z^>L{q33n$90`Ba#wDL#<@|atRVy3B0q#(u`>M4*>x4TQiB`dG#!eR|Ltoo=ea|MR6 z4v8sw2`k5FeVT*z0LK8K)##@}>mg~kz?L@dIZdXH$XlusiSCq+nRJdh*0OS2aaemHkv%4AIStCF5-Jxj7c!oz<~FY6N$R zyE|NyQ@SxyqNNzw9(u%-enE_X4Jnh#%EwBX*I#V&Wtb`f7T#;xDjJt_tFgx7p>w1k ze9$Hhh}qis=n7d7y5hY~N=ek=2)a-I+1_&;b~n0EXN^u$pgmTGj>aT(sX`j4gZB z&3nYp;Zr5|3xCC2V!{FYwZ%#?C_ztH2*Gef1%DKa*nv&wt41)AjXvEa31ykQR;vWv z(SSMg227*8>^6W|k=2GK{yHv}A1G+W&4?8X%{>p*wx6SR)ja~2?ZPzA!Zgt{(}=3= zo+*2iVRAylH0PYx4jk9C*J(%-hY32N**QxSevSsKSl%oil&?kSann3#7_g4L`CHr# zBo}Qn@|q3O$TJaDz8m28O!|o58y-1F8q69h+Q0?xZC@2%yo<<2fQ(IcSrPcBO9h~| zcm4Tbvj5X^C*?5%tpQo7fCWKMcqF>IjR-xot1-PaQIyg&9BpxWBuS7mFFz^HS2Q1^ z?i%CXKgYPY30+$G3j)y+mA^_lJWinvNF#J!#dvYvf&9Bjg;B*avifFBE?ro3xCM%a zr8^pF_{@F%Qt8WfA2f2$^I~LJ5A-~rtK$&VUo@Q*^`?t}Sg_zi-9kVq1`HTGcVY-k z`FR)Z*(3EJE^1wEtcwi!$V5uZIIMlQZEhF$!tE$y$sRU(mHm(?{3)DUpe6${tGlB5 zQ98D4nUeM2;s{&LGs*QEad$GM$y&aIH>9m?;lMysjk)*ttGPnOM3`CDKMez>M(@c3Q zf$B^LTBhzhP@K{UAgH%FH2GZ=HEY-i*kY=$^$i}x0Tvb_oEh*`QTmablOv&jO$Cz> zNei7lj3mRc3#qO`nkiz{;in`1av4+@u1)N^+FF_I1UZg65I;4sjGf5ZDW^VsQ#f_m z)&r6hn8Si3h7u@!s0|@1lgu`*66K-KcKMIZV6>L+q z7+QwZ6Ch$W+Lan=4jtOO0qoZpU=!b4K97zT z6ZGnDEiSOWq83SuVcCpbsd#uNH8y7#OI9MbhNW-EhO*@g zv~|T7#OMQ#)f>jJpM`Pak3ZZ5P32$I4%c`ybzp!}PD-Wk&s=4)?>9#rPC0~LIer^k z2Rdfs`A)(oPe*B2@he@IpMkgfFcTC&%o1+gx@N*QdSs}Ym^9m40=#q%%D$uroNG{8 zhP9L@WFoz!Y+GF!#W-2NL-Y`o7${s)4F@ZI&@?m8tfV7X`*<5r^&NG3j!hi2VTpzG zo6I1pqe%P*kD?v zwXDsV4A~%5=_D?Dw!}55cXIdhywLP)aN#2|&$*x`%f?eIjW3Fo$L=!4q)0z+3T8Sp z*lTB*999+Z%>tZMDgIqF9|AZhL2{$j@G>HO#d<=6MV$%{I28U;wuVE7PA$gVh-ITr zd!=cY=oncDy-yTKfrk4^1^Z4#+BE%(kamDW>vw{Ze3_p-fbe(1UEC;`4Zqv;+~vf; zy<=WdLk14I2X@sl0P@#Y3^xUz8vqkoP5cd z1m%##N7Q**Zs~=FE@IE%IZchbHo#q$1TI6D>_YqUMZMC4Dd2*_#PH+D#*>0cSm|n=<4lo>Q zd&nM}aJKHNT()pv8Hyg{R2Uq>Nwp6Qsbak}c}^f3kQe~1$E zC*}FQ?&e+f1^zB^mhxm#OyXp#*01q$J+^cy^*c@EYQIbhUZbWS9EH-M$1xvA{zDXl z!7Z&dYo%7ZJ1^Zr3}yr}0K^>8`p4AY3GPXBh4Y&SYareD5BBUPVL4jxd#{RKSa$@? zm17&Rf63uN$y~JW-k-1B#g|onSduNYv>P@|`DIJh0CP$e$c@gmo1Bp_Mn>ZV>K6R& zSD!mRe1?_F%Otd1qV(4nl3ekT__&dxYr%_6w)One%Dm(9j`h1qZ`l6p%{<`pp35S( zi%_**gh~8{Z&=Exx>{e0gwJITveNUUm>Q+Ui%G$Yh`z^AfvId_^Om2AE7sd3R)T*}T6u5Kn`^XWJgUut}T z?yAshkzuz45`c2gMt*6;EA|@qRg|Tl8@mlw9wpS=*IA!GMYOZzuT?-hk>X&c?)?-j z9AEZpCb0>{PH=77Eqd75LB746YJkDzE-c*r8A znaT7usIZe)75a%R20hYCz{5VcETg4=$^}0y9#VZ$;EdHal+ApNG!-HXBh>o%zd#j0 z5#rAly#7brfF6{xu1X15!T^Y6m%`iR8HVckMFMJ7^s}NO{1grFo)jOTG1}}dUvqk* zupz!C{FHXU3&kvHIB}b+tEC#-3D~VHWdKQS1vl>>_So!9`n+`;Z0uwNZeo`IPBFF& z1}u}{+i`&@eu(N=6upzl`g(P3EXTjR<~_HtTQM=Fx-$*D)i29v>o?fM_G9-T_7M3G zH}S2K(K7i$V*u(nK2E_$iIbYNpSn_SMqJfGX0c%%kRC#2!I?Q}sFaj$hs7S<_eVGJ z#NLiFuzs2et7Mr`%!oLtLhBBU)ZNzt?wL!~!-J*eG^vPHAWa*H$$9YrvX6%Iap1Q- zLxGqlFs4E7Mc09XDfdEYYKKXw-g)4&P^lSN{4313^b&oMtq<|({}An)r<)HN!)4UM za>&clYiZ{>=!kfy;66yZgr2}tDq-$4UcIjI;m6XwKOfO6{`ZWgj`8B$#D3cOsZpxt@6O@$0 zpe)rFB5QtNB5`04Vb98CYbQdPK^m&RA_{$s2n%Zgzi2ig^FZZF&Q%C<3SJEJMEwyj zD6IX8aD)CamUR#GBPSAA_eJYR2mTEuIZO*HKr$0g<&e$fT(VGYrZ5%POW~dLFUTW4 zA((1O%}z9;1+W=9L#q!S;Mj_Ix5G=E2}mPVZEMg-;P{>KvQk@LT&%*^M=ht4BmVdg zs5qwTN42=JL6H7q%zouS4EaFaPNU`?m)EJ<=$33lh3lX#{#ApT9wzK(v${J;&G0tZ zm@}3EhOh41k*8ND=>)iC(C=8-M63+lIjM|I7TGDu*WRIw#E+Hex$;_Z<(>yKGmx&+ z&H@Ce34)WNQbgiQNP-Um?^6InM=NnQNK%^jwmY~KCL>yS(u?hfcPq-Iy@ zWBubT7|lmG-rwChUeZI@IcTB3HG@SXc&fv64CJd(o|ge5TXAj+H{)rYo#8-fbFw5k zGU}~Gp7h{9sQhO!8wNm~o(RFdf|skGLVO<63vBtsG^X<0BaIwJM$-uHp`0Kb=gfb$ z3N(-96VM$R3NiW*Y^ndt4;1;mv+VEe@)ML$LgE!Dri%%Hbo+{#QN&;I^YZwdOhUJa zI%zh4Q~criuxTe$DT#Ste1MEecj|XNzf`(V54+=0mn-i)t60A<<_t%@Opv_Z ziX9!jeeJixFHURy-BJSCN9%6)`^QRsiF{hIy|)62D$IpZ*Z!Wtj1X1(ae{Ww=8Iy5kkT#Y(iiQRBwluT0@$OdGL+U;wG^s zJk{lr0=UkUsdV-)BUE%pe`JAOu}%xu9NYtn7nWpbe`tEI-ob$OPGYEypDL$WJRVhzRd_qOOr2AO9edbp9HhQ|!#U97&8Q6pRq$-V(MMmiz!!6039j=bJ z)BNw9OCU@#*0%@ zHheY?^_FYN$dAI4hRBxtd~Q-1!znu8CN7SiK>^QUS0yw-%h^HJo28{w?8B=`MpsmT zGd^Z{`R-dF>gQK0@YOpzyK(T&mxHqzs9(H5eCdVCIr)l+Y!UFfo!IF@5@b4re`Y8c z>&`fjCh1SoIHu&Z##zbGbyYhkVg(r2H$Dy($buh_mQCI{`L5qj` zERnp(9E|WAE!HjNvJ8*!44xr0l{+9mMz|_S#W=yG6PfTUi}?in9eRC>T+vAf zNET+=e*mIG+SSorECAMU2TM``4HI5I8HYO!Uh+WzQP!q%F}lDi2K-hY^6l%S42QmX z31@o<>de;Fz<9|wSBNh#%FKK0Y)Lu zQ^v`K9qBoNib`h9(_AM*NZ%N!C!tEc2PE|^Gm-CT-f7@pB8LV5Sa%;as|k(iOTXl? z3vdiChACVH`<@%#?)^v|cAg7*BFJo(KY2!&*ZHSf_m(*d8(;db`fXRDaXoX}t`0%S zn$k%h@RC+(b0X>VHP{H>wh3r;Prsi4dU4P6|01Y0)t{2^$b1)mYbwg^v?UR131oRN ztT(JVNa1E}B@FkgQTLr@yVx_W`hm0t$%ClsM6?UW(C|Q_xS>hi-JTqx|E$n>g+7xl z?KPn8tNZeq)^a%Wn$U$P#(n{p(4?p&`(EO1if!ANHL7{43Rhm_DrcxFkJ_xu7S_@- zW;&?*-6H_E?VTU;BCoXS>R^eDVT^=7f5jUI?ib3KvOnDH`@Gvi9k1=g`M=7j8A^-Z z27im{-RsLx78ct-p|g8BT5pH=s&NJf5epY`-FfgHa5PjTg(Y5H-;Nx5X26ntOVxeR zD-x_|rb|ty4|mpR3xidaD=9VQ=*>|mUhZyD-J>R^t_Gf}oohY-L>X}*1AJ`!2yoSO zN>(zmSo_EFO{sdZ(G;adG9FyjI59*7?ELG9UM=kNO(}NI=2q1kKWEIZIaCV(Zo1!i z_Y}xBB@&fmU~i{}P+1uADnCltA|ZMK7?w+x<0sK?;zkvdTR+E-i;$r|#R86s)ZeX* z+`>QR%ju%n)gkZ&A6rY68h0TPqcvpF!U{1n08uuSyS?U?p(nhL4C(r##{qNrrzM-5 z;iwEtR8SFvDig474?Mo0>N6S<&yyy0HqD3YFXPe{rKt%`vgh)D0Ltf*IsLQ(?z6LRYpfYdMM0DsM?jCkc5}^Lk`A z-jk8ZcgSK8xAgJPlT#&@g(Y?-a(Ta)zLmMtaTuOB3NQS{`-0^Blafxgp5IF0$Q}b%dZZ=_)UoW=dv4eORILFmEjZE-dgSr40EQ263XFLg^IwDIR7rO&EYcumX9g#P zxY0fzuz0^wP)4s9EIXc?oZ2Bc^OS&@wFt{h=YS7{=BZZNTs`cc)1lPuq4I0ZTR{9KerPmcKE(MokdWS`m%06W_R>eCtMuq1UT&WG)9)c zjnbvfMtLHn<>fYOvZT4kg7p>m6 zlWGRk|Ge@@v}E+sUP;?-0*|oErFj!#Efqw_>hIdrZ3`|cb0U!1YnGW)ruHf8nI7Kx z{(Cz@BSf`09S=a7xdkciwc0?!a`tn))~G?eKzzl$H1-Z=9u{NXhu4kuhpd~DH@QWG z@PY>H#v!PSL3?*VhOfH7*sT~tI_;F`gIw>T&wO`@+LDN`Zh#4NVo*)-hqPf<%VX2Res#R5+vqQXq|nE!1T8SOB1w$OQj;1xp}k#P=)~G@ zQoH(h`*Aq6DW>!*rHWqIRgs|IJ%8Kz75C#k316M3QHKXup|6}JJxrpiImn?tS#q!4 zq6Q!2q}A!3wt^@7d{00+yWk4J*(NIa)Y|>`T;|$?P@kMVnPQNn=F(3mza?F@v|lit z)<|n-21F#GTud~<9r$M(xW)5KqA*`{swra^pzzp zWGEuQ%_@V|sHsP(DD|ks5kYfHiw_}TY~9x^kHPn~ph~4hc9f~q88vA)sXSa&z2}bF zB!f;#2v}%uK<+;H@67Ny@b!hu@VcKTYYGofcTG_J?W|vx=4zNV-D{_?{`MGSLD|1!^fo z5Ak`d!TjQdH3n#!f$^pFNmD%1^vlS|6a*wON7=l@$Fc~rvb3^}E4rwUynx)-5l3R6tKyMjKshxv z%eByncB^4B{&x?jh3(D2TFS^H{Lx!Y?QCZm`H2_>N=KQay*$sJ41|%0ZxB(fss71LleacXO+3iAPhF?tXELK z6D?j|s#>Wj{bf`N@GCeDDH3cXxu!!^r1<~30Q%x8ye-3*W+-hD230*;A*-RPtoh}U z;l}F7Iw-Y>tLPjW7H(kv^imBv9?*{(h6|}H>T?u6kc^?|?E)6#Io(}tY))Y<3_*HO z@HXNCH7`7I!XbKS)2og`F_8%|`L$c3?bmVhigfmwHo2-+5yC&o`Xf&kvtc1qkIiDK zY5aVDk4va~de??@3y_4rkVpj$FN&@|{PPN>cfz2PVm>OvIOYFoZHs2?E)h;t^2sx~c4*`m& zO1CqY4MUm?_c)iKJfj%0%9E+R_T$=TlB*MJ6rwb@B(hsY3%Oo1dmhT4=K4kQxK(V_YDdIjXqLiqM7~0T2HhF?fcRI46GK!Xo&;0&Y30b7 zLlR;(aI`-REjLAu?$~cKQ09VsmWIf--N5=hv*#dr3UXc=r?v{QYI~6HpC0}d;X=~A zH&uryJ^6u_Sj3uvilAq!FZ{x2^v}n6C{ZRjcyE&q5zHE9`mj<<(C$WByt72J>$%%a z+tsyK4!gF&o4{5ix4+BD2QOh)m%NLY1wQ0FUBmL&m$bXFhP74j#h#XLTI7*n)LGIP zzWEUktR$_J*K304&(f}_UV8}iL`qT;be1LD=51)G32SVuzoV4xRbQ1=NsKWa1=Aup zDj?=qGx3~^N^qphHldKRC%esv`A2vCVk9b_mbf>^>`e9*O2!)%qpZaC7*U-N8&Qtg zHZ;XJufjo9=An1sG)UaE5gO(MWD1*q12M*+g@mK7st=W;Ga%+6&-PQ<0fkgMd0g}B zH~fk}hUH*3Ce4O*Jv-~k<G%Up{-MrDJk@1fg{g_r4M`BWb%W;Kel==aJ0J+e<~5Dx*@SCKiT zLKR$S!EdW?=V)2p0EM1}OjU+$5jyC@#RpOS!IwSwa4(lJhPRa5=1vVq4n#x!k!tDDnd&SLu44PD0H`UOti5Q@L`VI=Vngrn1;wM$c|4%R3zxv*C1N zoQ2;?>uhZKDu+j*8V6QoMmO3@4c3wS9*m3ai2ig|`OY*H8`Eg#g%PKf8sqdrtv&v@ zWI_CGdTwWl^T%FM#+Vb)l}C=-N!ycW*IQS8H@Lr3&`L=fCEYeU%)s4Fy<}q4Ihs;WR2q;Bz3u_Beu>rlkqG@Oe83&r+X$WcRyE#tg-}1@K zb*d-sbbhaq-z@p)!uJc)qjPED%}Vjv9z&#pby3onyzNj(K>QFwf;5C|R*ELWtGX(p z;dM5W%U(3zNf?HtQ=e;`f#tLvCaT|(TW_&Q0=`HFoMFGRcTW$#6iFQA7iox$jwFH0 z;9PyT$i^01zYary6GDosl({J6CYt6AFBA(3PLJVS0xE`8C!T}mc*R6S_Zp5_Z{l#?c4_l7g{&_RvaAOJ{7AfOZF~CqH+yZLkcY&TAk{hK zhA-8ew%d6IZj)A2IA+1vwXek?9CWu0=2js{v7T(P}V@VQ7 zM)VGX5d&a#i&3oVdTuay&tmOT4)O;@>YNYpHZ~1YkTj>?&(60#u9|6FRd6tz`ZfNY zrW#W2U(psaot%YEKKFJ$KrXX@OGSvKV0!UxX>=0owGWDCNs;c4VT+vPELNW%41fHD zwu+8laSUuPx#|;d-Q^sY%e+Yz+n9UXOt(XNflZ$an zjNZXs`bEhI?SR6NeR8mrmN(W0vGuo(V5Wq3wFF36iEA`y*G&|T5cX5{AE%Q3;;KBf z_TYCW3FjsxNBzyX1)Z9&Y2|<#lFXpr-pJLF8#itjCCweo{}I{{qlz?vYtsP=N1t2c zAq-f?dJ)WI(TgWfPLlqu_vbUhwqsP$%BLE@loA{ycH4UW>H2xCW-uy;_YitKlM0@2 za~cd^TV2v7Oj_9S&TNFhvJPcg01ibN=%U_sdV_LePj@_N?PuIf$yCII^Tbbzl9jV9 zcN2TuPc*f~cb8r~gkx%}V=g-4ZEYNEPi4}_Wk`6jY6puhy2%G~`gV3P4EMz~TDIt* z>oC3TO9ov{DjjGLFAhb$v2YHom`9$AP>ctVgnb|Lp2}6;g}dm&YS=r4lVX2;Nh=w! z`_jvzJRkEYP|JHs>&~xO^mf?~k1BxDcigH;LrQ*>>|>tRX+o`J5CLluOE#8iCZ{8P zA$It^5Xq5#fN77)7GgsRj=QV87%46|^+ytwN^u%E(I{;J5<-8>$`YO1^nn7 zZol&i_GwXU1bRU5V*v7?cf|G=3UN|xSh|NV{b4(s&gzW9;MU!kvRYAR39 zEy4j78qF@R3uDj5sAmLOL<-Tw#HZM!!+5Np!L?js{&>y(Lt?jLO*Uui>x6fJwR6N3 z>P)Cp_65P)DcTTXKoXtumhNPb(dREq|E8h9$xkaH?lNsQPl4>0xOrK0b>i*5g8;^9UelO?&JfG=g!7qK4xRVKQ)6+^~*mWBR!id0G(=Wy8QP7NO zd2|Fu180gO$?FgNxHC zYDOHa5KM7W(D2{0ARe-cG_-EVhwuzmo-L<0Vf7eZr!e$c1g0S}#5X41`7|m*#h12;uw`4gCPZ>gpC57gM|OASy@-Q+9L?G4Nh--4ETJd4&;1obZ|8B zRN>gvpHwz&1@|zwR<2e*Ts=C&O8)~^3HwTG~zVj=fJ7nC7-rNb(NK{Y@PEBAX;3yhH@}CSRO>Lqc zNG+ugt7X=B1*-HVm%#j}dpkMJ@Fh-qqCUU(4XECjpUB>DAlz~YC|{V;P#!8R?#mr}(B{B~sB&iurB;M52W2SWMo)fbgE$s?Z& zP4v~~`(g9AB1LmMK73{R)U-K~q4dvk|GVKsdt$s^%pOxs-)F>MbPFv_=afhPZq(ZG zrGYk>cT5TjWe2Ov;0}++o zZWdBXoHy=}e#L#@#`!iDh+g+C*;4ymxZ7t4=T2JLDNJB{{PJ6jRd<)m5e%)m%2_M} zvq+NdkP4ALqS%U2W=y`AhaB+Uybdi;%3@*13dW-JO~$$&F|8E!uY%R}g0?gVnk@fD z`0hfLN)f+C{zHrrY6FRDhSYT1N5-Mf94Q1GEtfVcTuROmvz(WDPeMWSY*8GWBUTpA z&Ijp4(7#_~D%^V|N5u*dyy|pl0*# znxA@ckqIpD9x9>i+JeucJkL{w+l3zJOqw%>lo$BMHy%wQo4uss`^9sLg`Drv1Y$R= z;JoDR77GilHlw9P!}a2Dt})j`i$g!X-6lK~L7N=1tz=T%jyngV!bnSD>NJhIY^xO8 zRjO;(q8;y{^pB8}-|xdIm9E>pz;*`M%TME^C089Xu(;2&kX-+_+U}NTP3D@xqPpRu znSp|{v5TXJ$!VHIecDe2Ou-IBncRkPyO`AiO5{$quVZ(b0xomlbt<}~=(vF`6p<8r z+?0Cqo5;*n(NS#Kky0WT1YMnz*j=(rb1+O|BTec-vh`Hj6yInIUhO2^qT0;S%Q(j#aWP+_6S?;wv))5k8aY$=S&*%7Rk z@Sx2^#2C9Q@nh0WAs`22CFh5_%vaUh=x9*4<+J@XETadb4{~eK9#MFtLt0;r*?-FB z$AvLtbS>Fp&_7SY0R2SalBKiV0_Mu3mL@+de+k+($<)>O?abI?_9|7$kRn7AuR}dN z>^gL+IPj_{|Gd;$?ef~BEJs`^K7KS9LU93K>%xBmXG1l;EB-pKp_t(@+U}8|bVD0q z@(ehx)#6-X;K1qkl)N6j)pCDD%||X)<%M31OnNJ<;&sF0{tWJX#h)w0PAy@oxQ9tP zz1|5XytFnJ{463GhvqsY@(Raz)_w6N6Sv>!@*@y~&gUvI{><8Ub773vdz!889RIF* zBP{q}&E8e$J6!^WHoqd*o$yR?hr~0bD`~@Nu~%b+eY5@9Rdpzs0P7*CQGc;?r_e?? zZ2G17t3Pk{_6BOr34Tk=4*fD*4@u%hUPt*48RAz~`g4{E%}HdRUBhPe2(JZKQ>oFC zeOACCfQmi*hiV;cT6i(kQNTmM@<=1%R1f3uToLn8tXkYLaJKh!+wUcBJx**$i_eV` zS^FS)eT4oweVJX=U~$SC-`lTpwn&@haw3vcc*!yMiDi+com^t*JZ8VrUoBZ_g*Di= z%41#I!9SyB(n>X1>uFT3=_|P-tEeRiHPW#3_vm+qbe5z2V2ZRCK1zE$9$3RhHoyQ4 z!5Ge9Jq-Mm*D3`D%roNK*lB)uB*nE^s)T3=(#MTS@a_=6Cas*Ph0!xka*y&1A`}bF zc7&eTCfOoT#h(EE5QmxeU!Fk~pqcYQVG=s`t%|mHvp>%dY=5k=R2(}0uU)2& zR@*)riL{$05&yHKQ{ym{&6R8<)6-I80T`?c;H+u(g|argyH###;VzoF2}-T1$>8=j zW4at}C`OGvO^mRSQ9^p4F}1?VnJ6_|{JUi~#ey_`407BX-b(M;G|#LiEk9eKHxSK! zUAm;$io{ovZf6ob;{G=LNPciZp$8=`Ha39KPk|qjkPuVub7bSO#~ncjNXgBFh9;z`Lvd)nmx!x?{$IPrb_%Pq8wEa zs`9M%OzCAS9yYy9&Bcl40dt4PN!m36mL$l<*b@V=>lYGI94YqGG=}un!3EUKf!Gn| zm>ky>d!D@ zq58vUo}D-;{xM5@q#!W@C{H^_*1sq`6di<|GM#`qIpmcB4jyegO%_-Qz)<*osFDfG zdYP}|Lh(&MEB#vCtr2z+!xtZRwVLLalSjD{Z#?` z!$+#zv#UWM+7=CP@9Dj;?`szCKr?V(KK!72rUT6wq-FLEKKN?bvRRi)pR1m~F0$!* zx3!%t=S^F3aJgi{_s0dn4gCyjA3gWiw!oskN<|N;;9T-8a~^uiGJ{BA_RilyAs9b6 zmF(dZ2toKieJKmO5u5*ohW{u6g&;sk)3bEcXCIs_lN}Yy~3E|tRv03vons}&mS+f3c?t2Q^~35`1K3C z$f5aHbm6O#jGvq5J?dIsyq6y+0F|2x?VKf-QEn==7moGLYQ8dvgOY=t-|iBBbx?s%TLNSAlxo;DffwNgF$iTQOwHt<#qSHqMB zQhmIhF)Q~QKb5C3jJ{&bu~Pyg4l6HFgSn;-2BfC8cb=(vvOH(xCn9ipJSnbf_dBDG z_l)jlzoS6A+d1%8=Rn$Tb%S);Q}oh3(vMH287Gd#4Y8HzB++ji?E3q1U`c1`p4VZ~ z=wG^aFt6!bc~cq@1%z}4`tZ_yI;qHD#2GS;K{r1FLN;eKl(CP_%Ax<5-2R0w& zpfKb>%BLPRAbN=ZR|z$@HB_D$F?0RF1M*H6qfQwse>0$2v!Nc4V%Ydz;+|)#S|H4PgeU5xofYN}{kWn_Pb3=dz13r}raKiMG&qvCB^ZxGazi{}; ze}DU*&hB5cB4a#L<$Qi*HCq;Ca?i}1_h2g27tGFSEH$|f(iViAL9cL{^``qqW*{1- zeIf|?Y2^3?Z~t_|YTeVtudSB?&ekkh@=@POE-D?2NYya{I+3NO1k z`3pm?j&}Ct*R)7XayD^6^J`t;Q;ozGFCN%WcYe;xyaG^RhwxO1&E=Q)6Lz*LN&t4r z>fA$OvIt;s<_RRE6={@k2a0~iP zPAB+FT)O^!R{(laXrKO^Jzmj&b=qZ&yK8hIasFEFm-x=Z82SP{82$h4KmKpX>Zg3> z=k5JXX2dcT$HR_cPMS+C^~YZDdGHH9j&FQ&|C=8%$6Wt!fB(;C|KHnxs6dh4X)$6U z&7T^TpY0=ZeRxMB8@B?jA}B-U2RtqqShlOGDPBO2G`Y$Hq?I2i=^IY3bQ&Wa;^;n@ zcp?%Pc+;(ybQ<4yL^AxjaTz)>nSp%;ve&QcKsI@onZC|HvRY1P(9;?9;syw$Ljz@{ z3Zbp2=&M;>fpW_XoO=q3w?VOAzK{dE0r!QB`y;YB%4Y}s&5_6x%Iu5E?B11*cN#;3 zbm}S4h8eHe^kF-zkS>Ry6EoX#$ zGvAB5#fQOFj0R^k;|5<z5!|VBIP|2}wikVmFy|jv|t_8h0pvvLw~f#!{fyC@yD0qewL@X`8+f zEL7uPoCUARA%yZY;_w%nz>UZ9dfCezAA5h_%O@9fyLacnUzr0Lf1@E+-ziGq7 zAHXg^>Imgr4(M9+o!x5zJIMYl0AT5o%3 zXLb($dg5hlXIHAIGt`557Un-c`+xuOzve{A|MUO(zs~;g?tj8Y3^N6>YQ{Zld{b0u ze9j?cJS5T`G@u-Ci&!J_pVz;g{n!8ezvn;`Nh%dp58pDXHE_4#7`8$9<1%grrVDuk zra`B1HSD_Z!HVJH2heRRDUM6tqlQvXsico;qi4`=M5^PcS@L-A)ikY_iy$e`jeCL5 z@EbmtB(L3OApYPP-~=~EpfL$J{R1{U4~~PBEkC|HjkgGuGBgho_rPa!U^n1Cn`O^4 zbaOpu>;tURp64RwDDi^Xtv77mK0Z46WZn^JnMY_o1pY~B6Ca+)D{^(+`Ys|x;VeIr zg%JPlE6O$>TG!RH0nGo#m=1cwm!s}LUOT~U&6IgQ>)2;jbHa% zL{Y(zEcb{4L*XE7-CO5Jp*yL z?*~8bQ!xNMpNtu2s08|C2^h6g5!uay5^o7YY;c|1wvJn9{G=_G!bKoz_fwWya`%#;<{o5y|99U{_41NGJ z0v-42>Wa_e{FYDS*oV8El0`E(v3I^1(H;|K-`gUO$WDUN!FcJ9q&eTwQ}-?XC{==li{~-;~Kyvd^}8+yK|G>HV{aHO=s`vrlNay?MjwTj?@F*e&d+mEZfZ z`xxE{e*}0GoM3u7&5k((1ESMH)QCIqFL2e*5hc8^YiPV;PXQk?pu;Pqxue+pWgh_s zU2PiIs58ElpFC(K*~R@z_ZW8YEBom@#yI@o3UhOklm?tXNAef|h(SkKjiAtvX*8Z= zo~IbnkVb1F%?YO%DdHquxPmdjFRtLFXXH$#z>}_VR7PIIRZ4IT3}$RWf03!OGPtt5 zQM^%{mQN3Gq}4U~8eY&K9=}VcN0Rb_#0ybEBd%!;$*GH_4SHyOBjczWA7_8f@)-=_ z2WF2|mS=uc)be0j)7e#D4LakpB**$_>`BjHnDOpa$m;(P>W5b!&i=)*$uVH}6DY&d z{qh5FARVdb^c}Wc$%^Vu#-;st;s`In@-F#1?KK}$etpfs3Eg+Nm%MukNtM<47xK1* z8EMb2LTA*-csjPRR=rM;+u|jFou0}1E$ft{3K?g1RORladvbLOF1n4uOyzmLOQ%6M z_R^^bYcR7AHeV|AYh9K1y0iy4pTlE}V3CG=Dg97e51g%YmhP6rN3P%ehqJf;@&4>B zy5hI@@6P^y^RKhF7;fSU!4?V#rBB) zRI>Y*Pt_>brNfx$hpqjmVUVTUM3xdt>u52v{WZXr)LB()!`TO{qa>iS=xiq0maPG<#x4y?U#?h*znoj>mwZja2I zxQsw$R!2EA<*txI;nhAevoD1#B>fs*_yc33*x=E_`7r7tku}i8N1@oRRL-xc)R-Kk&Qen}h*oE*F30hR2FVpV@A{J2 zwSWBM>@_n=Z`t4cmP52|-n==x)8KQEm}jD1c$Wq%Z+T+lIYkGFlQ@?KY8-)@fj+>v zJKi@R{m9zf2Sr@3da2|IU5O*lQP$b!>|g%+T?Y!{KgUoN$b14==LGO)(3|(9rYvnn z`UlqNH&UffU9FD$o3n4Gk2r3dlYqEN?}XXMNMGwJ^vpjzylo_#?+Lg0_S*w}d_pzK z{r-g

$^G3pvoud(EDvd7^z9bqapX>*(7(OD)oA5#;s`N0)RtejZ`tl7VzY(pG$H zkr6)RNt{4<)^zc`6 zeq4Sz;(c1hK24~0CGz{xr5FOuX72~>z-+chB>Q}%LA7N{DWON-23IDR_;~Q5feMw2 zOCBvJ3RIu}8m4hz@U$<(^ciV!Rc2<B3`E&pK%0z`q1b{Inaqd>dcC6)p-^t4HkY> z^g%cz90Q2#*fp(3@iSV_f@kp_fxhAX@g@}jCJ3|M4k$czKb|f!ccF#4;bvO0zFj-?xgW(eWcXi2W z!w)QRMIT;rnux}ai#{UTn`b^8V$>1QQ?b?(8C{fFDrLNn&PPy1n`Lw3xpcn}M%~a4 zY$VS-t1g{sKqtowuD|m1P1I%-1 zQw`k*RQZC`0tYU`U?ixoLIWlcRbE3|vI7);`z?F`^bmSv+HarDf!%=nY?h4-FSWpG zwI{Kf^isApLj;l$^sIXYwGQLO9+Mds5M}=k0w~Mph|gblu+g(NJl)C0{R1>w)Jo$@ zbFzIP@5!Wr%-bN(BA&Tf=0M_%5potG%bPh${PN8GQO>l$4}N{prKf;7L-*jzxua_E zY}R{}(t`e3p+9Q`%vAK&gLIi84(2~TvHskLfKm&8;cFRn9G8s*KhF|Ef$PO$J`+3< z|H}iOX+Z0b66(kWE@UJg`~d`hWDu-X1qJv|ghm+x-zAT@nxR7>sZz;TYRD@&%|Ih5 zeE74j-g?+iNVo0aRMZ;$j~KQByUg*ooIC&?M{+d+t@6MhgAAA=vy^%Im&nH3Yhc6; z4gImw)CEh@gJ62eWloN90i}zE)6LUyovzfJA7PJ zolymz42!@-d4F2}^sQ@%apcLd}y zb-zG(G)VbYTMuYT@Zz2P<@9Hb8$Vp_!5!Ba&sQ4IgmW6S0^K&ntp+4Z{?BJdybsJh zW5W28@0}j*j=1ZADIKyFAi(aaeY5b{q#`FZ2PP35u+vD8kuBobXLXL z6<<#*>aAW+~E_8svWeaB2MI`B8Y>SG4}(}!r3wy8-JXL(1)#o-T8ZUr1V`<2TLUseHzD zd~mFRqf9M$g7nk{0PEwdq&Dm2^)^~rGj&6uFJmpOr@#r@JmrcF-Y}Zz8pM^xe8c#+`%OKdCEtOwj^ue@5YzUm?otmsv77hIE(Dmu;?2k-H+a#p zmg(%AGQDFed)55o3081W9%Tg4K0VCLNd#MK$0$%S{ZiU?b1ch$IG$ne%yTsCNwwU z08ZHH+3-K(#VHuU8=p0yt_rLe93Vj4lFo~`BbEqhms>hh=L}s=|58x#HlUEo%+mTm zHd1~8Uokf-M8@a84oI=`k1_0gDXN({2L-a1&V6<8ENjra|X_e)VJf390>c{4T|f|+0Kou2H5XA2Ywz7&==gZcK!Nw z5F;M;+~p_l%~Gw+x7mBf)$5on@h~Ly)@9 z{K^RaPrCq|PWr6afxq8%rM2aOaY>#PIK#~;)L#{q!z#YF!$A!_g%uV1IzJy<4i8GtF-wS5y>MoCLOEmWAJ`bf*S2&25 z6)WA?Kh&bfb8L{AMV_Z&)bp5+V0rFGDUBkarrBPO&{ss_eaEZz_KK5Ou3o7F2{Wj8 zam8{{myiOWUKcSnD|qxC!TiDt_hF_W2S82PN{p|Ar08s$x2{4N{nTWaPr}ip@0YLR zz;3{ODgSJLw%=#YPH=H{%~7?NeCNhaFX**O!_Gj!nD_6;M~lIc_3OY0chI2;cM*Lj zs^Ihtx28>h=o=wAnzImYx_$IxmincOUoX4#aEn%U0q9EznpmqNo1fnn+rXc8H=p@M z5E-C95%Xru7rD^n1;ka3^;F<0cU5Q}>imY{RB$RVe$2{cPXXWRsu%U%OII)bO^E#Z zwcJ+U7*Z@%O-tf*l)1!Eo*?vN(Dl+w+7c~`cw4$SHayz{Ka8FrfaVzvMkl}7>tT|; zo~tYm-s{`*r8Q&%ps_CayV#-Pxe@2f9FHZiY8( z_w40nCP-%=$mxpQ?J0)SFOEBhI67^>7T^+zSD=}7LF7`xB&+m>>!s3utIdAuGC5uP z_JBFKiBBS4axbJeJuhXA5q3m(U`K|NXa~~99T?&9OuxZasK`5?h2luv{_2T=WVVrO zwE6ReI?F9J>KI(suyate445jVakY$li48w%eAU}`hT@y`lxOET`FXs*p&x3clLMK< zkc%F4uv6yq8@i|XGJOJxw1FF!9D3CX7#T?W`B+Gc1a`5ckPJK zaEq?J*weVbM&u^riC7 z>qweCAsua|&gjwUmIS<0D38uG6&(hq3QlFF;K{N}U&QfCgZKGzJo2Vb9-{x`P}gDB zZ$xywh$!--?j?|>O?*+9Qv{Kce~nj8qoM~qv7hcWFB?4*mjhBzXb(fN$)gzG)Vuj7 zVG+6iz_#x9OQ-QRsuaCpe<$qkic$Ww zd@c_RBDzG&cvG`i8DIB|#1nAEalve9$&y^d*O?3gyatU@UN??>&x^SGVcfl+x(-Qz zF34ae{X23!Wf#Jh^GY1?H$L$nxTC_?;Ilj=mK-aZgSRsr0e7@z9V9IY5uqj?08}t( zCiu@Yp{CTeD1Tx(xiTutF5mN9T%m&O^5jcr?KY!M_$sjZQ5b%`YrO*u=4%kAp>o2a z$|{4FrT=2kF}RTxDi}WX!0S2WVW>DnQx7PNoNXfL>)lJBfC)kN=>`dFcufv$!;oMx zu3C9}S$)q}PT{kp@c?*YjHgf(W|x(AkJ7I*9ZiakbD*YX-bLs8hxUSbfW>(NjT+Uh}oObM4Ek%5jha618`hm7Dy zu!>p(BEV)y7dWK)0Pm92@gvJ3Cl;XcdYYB;CnHQo(fM2(?L9^vb<5H+1yuad<=ZVt zo2tH>y^9ViFT)+jclboD4sdjS@26nbbp4HYi(hh)Sn1F{05!a=7bjQeWh4nCwsNaO zQatgJBb^1 zTXVN<#@XcJsR#%BETdZCfH5o(nfm#9zhdd63n%Y613K+p(~GiA3y$O=pXA^|dwI9( zi1fyUZk+jj`Ez97cPeB;Vx}t@8Cg24{#g=2G}Hsx>6^X zUtYfAIl?Umm3kiSq`NBQ_-jw;h;aCi9aqYJ2Pl$H1SD}pG!YiA`{=?PG9dM<14oiaDd6m_ zoufhGYuJuIxOd@3egH7^bVdRX5GMW_X+aqIW=ot(PGa)WRs6O7+o1zul*Es_4FEEBZzng*+Yjh=gUWpSe9{@ z=P{7AfO}rvuM%VXp{|~s%7;{I*(D>DHstZkh_FkqV6;YE_7!Ag&o*XWdBr7n{lkqk z(ZuOVS>tBGrWuX*f~8b{%esHRk3+OCiH;^Ftx))E*JoynKX9`!Vo^k^n|*{V_Eq=3 zU9d#!6@5ap@e!l(;gO{ze#&)vjHL!2lWy(wAyWrrE+|_|3L{yPJvWueJON5ZxNh$guFtQStG=t(E_oNQ^q<30@CAlw+w)0GU zL>#1bhIt2)O8I*4pw#6P2ZNc13ig1d%VDGchVIkQlL56AWJt|^#uM+a=o$Ktp-ewI zo`=d_^3s1AZnPoWR9-UFeXdaP-Osi)^dG)@c0PsT?QCm$Yy8*rb)trM`m^w|MlXAc0zTlc1J?bY_vH`3n-cRvX_`wgD%Go99ZjM5pLsUX2l zae-myJ}#YIYChryKyDx($BjDtmQpo8{F6MdGup=V0o<>rEV_ znVzoP2)n3+M^?ZsE8`a42TBhJxO;z_eo^JP2c<@}&9b|l13xMU>{AZhy;0vei`M#% z3UwD5y(v{3-DOr+n8N|fN=hC(km=|k>!s)NO!F$dx$Z zNjMQzk(8Zfm%u!Zy>#cy)Y+#qh)H($(J7nMdAjuXSy-Y@UXIH=1_}y*CftJaAZ`YZ zF&u_FyWsv3ULxCjm`mU)z#y#&P@Gv}ihWHnY zn9r&8pMw)Vc)~yQLe9W zSIoY-eDc8+C>BmyJ>CH{o^|rgi$EQ~6QVo(^8Fkgd&}N8zg<{|f@wI?f63zByNU8; z6nn}ia6rkpyHDkSJ;PtOK4+tNdwcVSWhCx-n+bU7F#!iw@NW0sjn}RHC2?GWdf*?o z0b3!P{wfd^rsmD&WJXoy@vjtAWmaj8tM3)N(2`P4Ujoa;d*|=hXG)wa!=9LBu~Hgz z3Lll6A7Irl%{@zDDTX6NP8YewHlZUX!6t$@N0p91w!9~N$FK{k6VQb`f>Q6uoa5sO z*YnfZ;D-V?{hHIi{PT1#m0FCrj8xV#Ar)K<;5?s=n)R8UCgkUht#$w}@?JI$2l2%) z23@r!PrSTPsb!W zg086~7@p}Pt>r+2b{>rub3}&YbHL7*Zr=l<OB4C=hC zAo`OuJTolta1C#;(XMd~*NA6Y15beAiGVG?^>r-F(?_2!M^4gX)baA3WwGw3gFk-C zpq|x`gF%6xY`?PJ>xT*_#=~|07|ijIlzTn}HFV&Edr8rI(RqBwO#@H9#lQnxKK46G z`^FQghvk)sH_X1N*VKWAi}wY&=C4dIGy?nL(hj&7bvO;~NDa<3a6cVFmUUojFnPMe(Vnm$p=q>#_}wXDWt-0C-6eX4ICZ0s!n zGirm@i9oe{dQe8mkO-D9+}vki8MxzOEB4E;!AD_rG_j{#so<&<>54(;OE9!#^i~@1 z=ZC)yx>5Z}$N`;4iLKx!@Mn5fbX`uzb@&)%ZTdZwV_Bz8h6utuhgzDD(MlfxzdHE~ zg_cd?ZV<<>LFcmAJf|+X_;OtCuMw74{&N|f$Uy>_h9Tr3DZ^mafa67OF1>WtE(?ER zMTJ$q0@|jWeS72Vn<(iQE^G9WfGmC3duF_&Rp>)Wk%$1rj(pOXOuEqi7@#28D8mCC zBts>$ueO8go2A<9dj-jDcRL4uI1bP+GV@k@-l}I*C2aq3Yh=eA-A5Oiv1Xb1@sBQd z5I2E^O^35*55IZ*k?}&Er_!n3Sn)2Eo-ugHxRy6S`xe+kfQk}e=EKu;Ce`nVDWig$ zRWTSWQW(PyeRrwui`*)iP;z15{n%~rbk%ct$NyM9(xrD~&wUMdcH^G8&h#wdd4&0` zPqJ{=uET`Ih^=LF>NQuP_+0$mbNY5>QF&JCJ{j4@aAW^0{&c3F={_1)=|C&yzC#8b zX#_Qh=pE}BL(W0Xr_4}0fS~SEd-?2nGLAySeo+cjkaY>;zTmgeNefxmB%v+aIPV{G$9WB}ai?&saUnMaR&4TS zDA)RU;-!j>nOfS6OJZ}{D9=EHHz!}B+_G-mQA-tGH!A4(<&|2V1swIbeic6k-C6b% z$ebGeW^HTQJzCA7Vf-GhaB=nkk2?KzU%J&Enmspy`(Ix%0m#&s#T6C1KcLTUCa>Khs2BKgaZbW_3 zt;<+}kK@GKaW9tP0|v-; z%vI~$p0sdjcqv!^_T+7!Hz+O*^6X_8pq}8CrB@p$DZAS_@FQ@b^uhIOK8cSIC_CS*6N5dN)d0TnAXHt2L28P z@rldF(#>+R4g=S5{u!R00r(=4G7qX=3}jyMF5;tEo{9R+RVluQXwW&6ss77A=72N* z2MHZy{QS4U__PiDc{gC?$33f5WDt5yUOmyKF8TfTHwP^lOr+^2ryYexua?XA;0!w( zH(HYM?dr)H>NqgWj#hLue6|eGxBm6+#z#Bg@bG_&0hg1h8}WYsY7XoM+-I;bO_etM z`puiO*BEd%Xd7wzLbcHkV(aFA02HLec&{P-EqP>UkR%Y8eP=*+AMW@K!3p267<+^a z9m30I7hDR?nXvZ=PI`O_%cz(={{sbth7-kgo!2_7u%@d>eF;Yf#hr5um`W=%bd*^m zm$mFtx_iV@oWU<$B3%n9R0ClpH7#q>Oa7wkdFm{aq%N*ugMUO0Ha|x|jMa?6F#H1L zdGHu&`3Yi_Wx3ZTP_HO9p2a;&^ZdTpH0XQ@@fqc1MBsA$Y&-l*+`DbUuRMY=Q}>%6 zuf-sKeVNS$`&@*ei6UE7D{xucITKk3fOk;is6A0ik?_}+4X4oJ&f@)YcG;P68cXrnAzf{7Wl>m2yUtXo5*tw>UPA*|y&7Z1xX2KeUUFBz<-2qmbnIE` zrI>jk5B+BNbZ2o*(s3^12RR2xo#&;r1K<$|?2=*P9)+(oSdE0Ow{>Qw?lODn(0U}w z{|sa@aOr9k_mcZg@^HrwIcxfSM&#ce90(hH0Q}?;5l^gCdHojN^7qK;kDi8Xe6}8N zzhF?tz1-FQ+Em*f;*8m%UT#c21fGS@>W=r6=)n-hOgJ-tw;li_%wc?TZBT@Ta`tkhMd$GXoC8XLiOdyNq4EJY zNZ!CYf;|0hVD=stYEps~+O`WJ$p5u_&%!~_rST*W*TtB@SRfcHdF^R z<3!-Ynl}NCTprdM&o+YFTX2b^%nMQ6xO{SKVK~CD2fu;?y8-tZ%(Ef#wtRN`&woRZ zUh#g$Qa?|`@`NB;y2g%e*@piCXKHXY%m^gcOIc6FpNeaoUwGe0FQV*+*}s+9iYibc z8RJS2gu-Kl!siilQ2#+|Fbp(J$Ds`71)P^*bZeB_9l|@yE&vVyMFO;N-hqYYj&2w;zoI8LMZo zoOMH9hG92_|^NNv=wgp)5open582@L+J|r zu8VQNJ>NS^j1wHs-$qw@L#K5RzKI@r5e;!Gyb&u{WV(@_yp+3(&wzo}9>mm$%V8lH zc{wBn0A*VtDr}MaoIx2V|8 zODD<#TaT%A=LVd7D~27it>DbHItL$aZk)-kk~*)4mu#5U9J~K(IItUVpUFL!W8L3+ z&;cLiMH1vg#B>6Wd42LDjGn&42z(3&*C$49O9*r3)C_qp^-wBb zu60?^FR`2VB|6QwrfoSs#aE?D`iGFn%D@0-jtJRgrA|Bp59x5gi*u#Iclo1HQOXR3 z=F&@-qj*Qb#Q-1V6;DRsC%DNgUvA#KE@QqpR<8t^>7;F3UcU}MopqCq#tXqFI>5P) zw=5q^FPjvumP`7m43+b_+%o%i>+(tbIrxMm4^eQ`b9XxjenbxVEvLu3-#K{dU4*;e zW6KA(9kA>ZU~TZ~IbAx->Gpm)wz7TB^(3B?k)MP06s)?U;`3cPyfr9@hw?NQG#=W` zL9&kMyoP}fiL$DK1+~yU<3~OL7H{DoB0ALpI%YJCyingE&m#Yc@`e1BhNwCZ3{x+r zL}XmklH7?rxOABdX$R>B0&@bO`Bg9g04VH~l}F*=$z|XyE|us7nFE?5dEBXQTfY`U zy?yDpVs>(DsEfh&h|G1X!Ta~j(0S@5wzki!W}s>?Ga`Nvu+lBspT$`(Kc8c}brAF5 zeT>I*dBw751}SXD!dBbhX@xK2x#c*XwIO@p1i%0c^(?%cady zze`>|U0!*?vPly$e^Fi<{_@Frv%$@gF?-p@JF1x0cZf!Q%gFD&qrJ{j0N}fY13i!T z1J|HozwI3Op*TQ)@z1y5$O%qS)X;yz?eKkj;M&Xmtk@OJHR)j!?(tF{f%pXz?O(7 zWQ=KkX#6~@V4{r4PzR+T-OzXFIP_jVW5C^a>de#`&)@O05k2TW%?-jf`ihLg z1!Kp{fo7uxZSlte2&w-7NtUpIp8~juZG6`0!WR*@x$xN->+&@8o-;!=Q}-C-&V9-6 z8OVI^5_jabyeK?iIUtHce?TW^uHnVs-*T820S_v11A)%EIuLWo5}rC-t5%Nr-MOy1 z`b%!xW(FgTAgUoM*jJ;)`}@sJ1})UieTX)(9Kv|=-6!pejh*kJWFf2^awN!t;8(#BhZH^gXQ#p_72 z%G~c3`oVFL=BRB)CmG4jsh4-2qR4adg~XvbfW)awQ5O6ccar$_jW3lLQCUJ$G~2(< zQSP;;2hRACh0~C0eje725K8Fub2hDb;tRkne3MO`-(ugtFR9RRDQv&&9QYwQP^~yE z+J^2P8mBglrt@SO7BbY8D6H#?A!lDZpU0YyUVtze;^ajxbo!_En}&Z8;b$g@FJ({9 z2?W%6rB59qxQ8UgFd9Nree&oxEF((`Pts4JG!7&ww%K(IXCh7;CSo4Az_0P7JFt9# z5Hk8bG`J$64La|hOql(2#K-aM6!eA$s*3;Mbx6jM$=iH~A<%x~U&6YU)pnWnGLLD= zJAis6gcT3&ks+*3p2oJ(r4CTHU-GWyfS#~VPi45cdPk$~_D(~NUko}|r;KN4>Gcni zAIn+ozr86#_TJMcL%y*%ZkJcCK#6r$aqu*}35b{Ge)F`z0(6oo?s0Pot9$F}{m+8b zgE#2GQSv~t-@X+Gb_4EPaq<)%dc>25BX}Hn8*~}LM0jTOQe`&`JHiT@sD*CrkxZu$ z|Fz>bva2G|5p?9tfMGneQ#2qp;ZF%n6@5gXTkX{e3@Xt~@ z4V95WMXN%~(i5J8v^tuXCt@M|il!mZxHXLM&skge_;cbkFz zE_uD!lMY?S^xf1h>Jai&?qi6r3`@jIg)gX^R^`Eg4kzy`_G@X_IlFJ&J$NchE*GOt zRKWqf#SM{FB^;HX^pRmTdJ1^EMp^-^PIacv;_~?vK;Cm8SO!{sM&mj8$rYW??89!z zL4m;EG_=8%xPjn^^vK3jxWk@87e<8b(mvuKvEnT+UK58-mH_M5`2`p*JjFk0G=B!v z)dlE1mf64kjqJV)jG4^$Oc&kWa@Ii(rIK&!EZu^<3;7G)d3`y1`&vVaL3E9?(a&rP zK3@$uA4bBma&o%-#kagEjb}DE*$h2V=XWfhbeo1{>U?H*tU$po-rXO>f!%=ngBW`f zEuE(f6kmuH1PCB+8gA~J(}2hwqs~#x2vRzOFzGlS=shP3{C<%Kl#M{@Ch3IR-!J@v zSgp&Ptk?pi_>-9*gzN*OVj95V(-1%e4xG4k;})D1!<7OoL)UQeFY!s_a*j2WrLa5; z4H=c1v^qlwSd6&HR|J)J ze!?kF4~jd&xds()%`fkZxXW`$L4&8JDVthGc}%~~GM_y0()SSlUgbMZo z*tQ$nZW^0AwvD#2ZQI;YWAmi%Ip6jDgXel??!lThYx=uuyr_D2R;W=%E&DJwa+Vli z{of9c&m6k+Z+v~JjD)h;Tv$%5PkRI;BV^5NO6<{(?6xBx6x)Yyn&?S7EIu+N?K)iS zp%w(Cqi-!xAKKZ{>i6~+JQI!{;*UD{voMgDv5&wmF1V^TSt1;bL=OKr?j_oh89(@u z{VQL%O!h(f$A@}MKAp!4^x0aWby6AJI;9PJ2s_xWjEWJczxb9{{D~i;*DXYNeXI#q z5`c#+kOjL&{!rjSET153;lcaRN(2z+jg72tV_}P8ApCF{lS^;b zTp!4ra}}^i!V}`td3SBtlYVa3K&&e10A(p}nzbhL*-ZSbtY&!f)YRf{qJ0Zu*dACW z)hQMG6uNg36)Zw*`JB+h2~R?2MvE4i|3LS54}gtI8{#@McDc@RzjPnUj(qk%_c4!r z>wbc`j>_cI)9M^qmhJ;%rfJ^=c-`hxdA)5t42rI^w2|cB-sl7Ke(yV*Cb#&CxILK} zhz}*9^d2OAB4BkcU`R!D#7m@RS;vkRxtlIFrOw4-6O-mG=B`-;GeBX)wjL2j+r%rT z-UY?uT|_Z~)wPs{&DPRVmc8Qkf?hw^G<<5W)ixm1A(v+&-VUNev0^I6Iy~A}7UZx} zaSrF2pBDSxHv)hltA4BPyexv|Tl@FKzn!)8(BGoDUC^xC_qKnljqw&EGt?fHwVT<# zkVsk?!?z~ewN5&tqEsTiz7DKsjn)K0P|@43`amW%kA-lzE5W0>2m9#x*W>{@qhasi$PMnql!(wpa#B5_EQ|z9!?ZZ%$Bd|infb#kZL8jS*>66 zCW8-^%g}3&UC-0p8P+t?nAlDm$%*frpGtF+($!W3q42;ksB?nov960M&*;EyOgG2e!w0-AwCc0@ z95oKh#6lNO_4?8JpN`-xw&Tk{znzfy!Akhq=O%%Q4tJOXyNM%*GRl%05UhL3f3$yI zurCD%58K|*86gHzVYcrMti~?{Iq(^N3RHorO=BA*^+odF+Z_gY9y7rcx;BU zf2DhfJ-qpi@~<~DF>N93|9<($MS<6<5} z6QLBQ$sDmtN66V49Pc+~pOZXU(eYBCgrG%2WS%O_gI?R>( z%psM71TG+0&XjnOSS^Lo#4chx+^S#TlRR-jL@p049fxfWwO)qTkQ?vU_$rLNU;R8H zz_CpI=2_Zd>CZ^FPspx5Z*+&4!0t#Aw#7cI1Fir;30C=v{9f4I zA=!H9a^?t+v*#Y(dTgZ$dt+gefuq$34g*FLIXXhG+xN$jbOYb{z6E!6B6jVFL>lOY za1N-}uepV6#zTF;s0j+=^|mo*x9vg3%OpvXZoNW<@+!d@<6rqSbmDyLF7v^a{aLVl z^1}b55ZCD18Ti$3s!WzvVsW~QwxzzK(L5?VSE$Gluti8{e@ z2JZyjOc&|nhfrM2Cb*((Zc0fWu}RFSN%ZN7Yt~LTAr;v1GNuWKxs|x2#!J1^RUQ9g z3No%E?#hhM3FNCZ2sfC~b3*lk0}f^vJw$%N9eF}8V{lZccNS&3chgG$DTxA1jk}Ui zUq#`OKiyb7QtvH6l$#m~@;+LVIs5Lc!}X+{&jPeg<_Ef%Gzb2NO@mH8f7Xr?5M#7=Jjne z3iO(I_79{{9&s0~bvd$td~EGvkEl~rR+o@zV6xKI>-*{xFT1a$5HAEd_{9Ehww_Vlf`9>Js@|gW{nY(5-svv28Ca8;S!6u^eL-pJD z6&HlIjd_@H(%w2PO^2K9z7*%S9J_vw!ViHivS z>m5hIij$Tfe?CNu2>}Qf__Qd4HZ$zhepil8L_ut6n-@I!45hp#*iV0Ra8s~EXqtN6 zJW~j@2FrDkROX{%F7^H9^d1~^6o|4d)!4ZAIAG^>;%4xHbbtx!mcE z%)tVVzsB_LqGc&KFb+GnX#h-%P+t9(ZYB#OTF%$!T2R8#F~`LVh!e#uRbmYLvS`@o)tG%0n)P->3cg z-QOv!(WjL;gBF?f|CB{CV365GjNM(DL@EVsJEw^)kJ`xGw*Q?taz{s(pMwp*z=x=~ z8Mnz6&^qNPHGnrJ;5hP?^f=4!+{?_rv*X?U6*@y#k?QPC4a(bA&^<5uh!K(VI+>Ne z(bBLH1;pAB0T3`_KLVp73vOi%b0|X;Smm$|cG>wI9C+MP+cxUr)gR2{?)TW4n%yrz z|7lO;S_W+3Z}iPQwIhJ%bhvWJYL@EFCa61OyR29mb+%WnLL#@G8aCDB%HS*YWo(lHcILAhpik+dtm>qv)JIP(+<%bIhz7Prf66% z^1+}X(Sh%XT%x4toc-eOEXE5jTlkg4Jv<&o3IY+qFYY;oy)pMoL?6ji9g4#E9qnw) zlEQwEoC|gxzT97)MzNSAr-{-IdK;p4H6y&%-J^LHO}J$>3sWYyQ^3_9f3%~MQ&|dr z#}`cxs35HET-MX;EH?A9z5aH}+w(FMW&MSI6fxRomD7@8G-A&7Pr3(J6PcRKr2T%F zadBmv325zeC&V4eR~Bh{WR~H0&ui{(01GmtPx&kt8Ryq+h#R&m68_UQF3*?lqdW?+ zKkpqXp1kfMkEc&Im$gR~7=U>`w>uXq1U(gFH^p}jbjQXVF~ZojWK%YRIIA0Tp%y%m ztBzS(DWkh<)n7u_l1wz=ZkKsCo|I2&$O*HnjqZnNbw`I!n7pP@_I=>1-ZY978QyWpZ;$t9M`x%*n5ydt5dEpeJHScY^2FiS%+wNSM z8#PMXEkCjV!DWgjM^zmq$7=iG^7XNiX-~ z*@RI1OHvYTo;b&pE(omE(KksH_M3)+~=Uk3bmgc6~p(>2q|tjPf19=QB$0Ry3;$9$(GE#WPl2h+U-n>9$3Cc{I5g zfx11mg?W#s-H<(oLr;3{_W@=yhz5&B32bh6`n+a8y}K1;w-}cmLJrZCWlw-U1G;8{ zLsz2dOyWXgMBH~`Dq5l-OFZgvtm`9$JA4)aSzw>ac28v2o4iS{K{#{G+!f;j^~#FX zz-nSy!?O8(Xu|?ndO5#)Tv}s9Y$i`ydCpw-Om~ur<++>#SqZ#-MINLBu(VXA^_*Zc z9N}!ZPJ|=2pzDNuIuKTchUinEf<3-kTHLLI@U18HSywR#nl{_`GSc1aP$qHf1SchS zj@U$UE?D`sr;<;B$DOHV>!C6-*AjXZ!)NQ)4Si{*iz{*PVl8tb8c~c?Jtj8piTwor z?wN0Pk%6llJg)r@wD1oPi>>+KbZ9KTbP_rky$0CkHlgT$bH}jyd!}g5+v#xY5aDm; zwTl~V-%{xN9Nm$EY7HWx4@X+YG7CogTvT4v9KHqDbD)OyT2 z)E#kA=h9hx(^y94lzTRV5Y3(w)ymMYTrqWWjPvH_oUDv5id__grV6>DE5S^Y$1NK% zTFWvaclaV65vKZi`SsrzH3kxRgH^v;jkbFKvMKL|7!J>o8O!s?XM0K2Q?|9r+`be} zpm#9~>{%J%@((~SoW$V;LLG2hqQj!4?g-EdK8`aTjx)0{=c|C@v2Z#;N|dwNDQ1oC z&JM{MU=sQQ*=qSYzA$<=`#wxkc3 zTs-|0aWmPsyv1c2>tw%iSusc8+l4ITX1$2pRY+*958_o99>Mfej*O-4g*;gqR7T*v zjfc@_%(JvJlNjsP#zz_CsA8U*_z4cBCOwl_QPGB-WEReo&QhN^l}MM3YtM3+rzw`7 zx`Rd}5TgbY;aNhqh0EUAJMTjoo!0Hlz~GFVMb)a6cM+()yF|SK$JNiMpV(hv3LyU%OXBv=kwf`N ze*kBxe)f@_E>3OD;gNldPp-*zx0uxCjR9DmHIPH6xjyUp*Wo(sOFGizqO0%KFt=W_ z?jDZgvoPkXuBHT0jo&;|Su|n<;@E>@_`yv;2gAfsJMCd&b465ERmjbPMgCAt+R?0U zne4FqEB6Ir?9^GjIoSb6{~Iu+d2cMk5)A2Al0Qnd5uTzvi#+Ko=m4yXtp1&_Tr<@~ zGCu(~?K`M~>sMOeE3o&+VPwoMD4IHlO~)%vqsz_|z^`{gcfyz zwR9h|=1S$XYBm<8iW81oz55v++0xA@UkO49gkNfpdne7Q)8x9VxF1!wK-8s2jFm{? zpbGq(Hv^(Oj_RNyac=SyoO1IaqTFGjG^w4g0rtjeP-tG=X%Xn*X4YMGe{j^|dg zr>k|k2k?kV)r~7qpuuTdmM2^_3rt%#hSfCCzC4k3hLw}~)W-Tae&Smt{^{LiUJ^R@ zsUCL4=1Enrm2J;~BoCk+?s~T`P(}`hX#xzjykK-i)*~jzdpCG5V5xgIdt!m+56FPo ztYyG*h;o^+Z>qV*uGr(diiPydKpp`GwSaAuV|O9xLeJTO6rdHgxGwpe?c;AJ0H4v9 zs0q(;D{M+gg9gRKRlOJwwMP{aM^mK0#b=7f2p;XHuLYMuhva4DzV8*Zu(@X9Ls)sE zC>7EarLELF>P%U)Fe^_$7#OsMBK?To$6Wlnpf zZ&Wm*Q&3`k6@IEEBjk9&7>9oGO(PK}r_UvudRr_Bg#r`_iy_j|U2uqJ>)&OYkq~>v z^@X@3D^VbcvVI2%8=BBHXqY1eT;3hfA33~qpQ{SXtJ-6cswfzGVz$NKF~#WbkqK zBCqjBOVcjEbVIcJJnr$!p8p#e&*$1j0J%p1{Q7addeZH=_e{0h4YnHy+f~QC^Jn4u zb-t67FaBhdvSrgCjg@N;%YgX6TSyJOkbW5fx#su`M=u*kE*wyEiG|!miALB9XLysB z1o!Iadou_fh$}C72;;Z9K#M;&Dm=IC{(11ZFg%}&%@ud;k>ju(IFEyOaFIQJK3Yr4ck!0YpgYhhf(W^t>AJiHJt3&|-e`&PMRM!Sg?9_}pA8Bq zWs48pwrj@$OKKsqj)C(O>C63QVWmdbnYH&3&p#)Hsd@bg4Mc(I?R>)RfKHx&fj+bG zR#Ox^LIgOdw4Lh(t5IY${qzI@Wg$tcAKFX*?H=XM{* z@Du_*P==Jv4PJ1sgD;9FnuaBPw;_4TOp`jb6TM08)InL z$;EZ0Cd0jn^R3OSg$cW7%UW^aPcsVjxZA`L^LE1xMyOtfZCx3Hw%~}`TFjWFl0^qs zw>LNLnIDNC&G(0M{AHfYK9@kG%|=C7u!=$i0?3pVooPDdL2L97kuN`C+u|IBlK~JX zXxl;%&a6)^W8e|mu6SPNuAUHOt-IG8nMvTIf?^1*2qq=x#iu%eh`fU$Zl=&p`0G7c zEE(VK;qT?MMxJx9*)t&3_bmDt{D7$-B8xOxh6SI=X{RZ^NcKc2p4|4H%rZSzG+-j< z2FjMGF7SQkWmwe>2Ri*2OfwV#4Ng33k(!gzq?P#|2Jc;oR7z%W|0$oc_v8KIZ zack{L&*&=*+^T;TF;vBUcW|CTSMI@+E-RFj*#}yb0WTy56I~ZBTZqjUeIUb%WNdoI zHrM^@%{fLMd}tAEq8fKN_zA{F*Vx#6FpL7RctYt$A)rIzZ>2oqY!o=P-QA76>e}ta zu^Fb*ao z6qohBFrBu}QUabpTh!~3PuC#+DJkesN4}N$-wZ50^2cn%OUt?!8L;l+v=s08J&(3x683 z7s*5cFxoA>I$RJm&+qjPD>)wc_jY}VA~=nh`SBZ`ubGl^jg6zaKzZ(9<^_?SV#}aU zmd5mAz%+wG#GM4B{=?K3Pe^)FCK0vjy1xzAR(&Dg+4+mpKS2?q+JO4Rqa!h1ak-ak z2M{3OikeIkR({;%z)!gQ~SBS_;lH%O%msq(Y&hCqZB z&Ai;MFHk%9Bw}9$omEyLB{!nKtcQ)I9UxDsth=mIGKcS>&)qVe5jMy?FLV?5I_D%MZ(gCzbjC zrWc>G6K?&JMm1K!d%CM|Dm zSvC9_@{3$z0;EW_$Q|;VWw(WBq2WfboC;C#%2D5LnnZ%?j_oy94!SU>?)Z#ewonWv zxYBza0lFmEz5Ys8-E3!1HwvKa%(!u$o z{7a>y9T>rwrgF_9uB%{2G#I@DZ-ab>^k0EKGy+LzC0;T!eAMEI+yEAivlBeGm|CMq z@jcEerWD<)wL8;Ro_PJNAZ78TF2%m8l_u7_jL5M{E{<=^C%;pkc?8#L?NSR!?i19h zy0+Gh`|}?N4N(qpdWnFl&)UHoeun>M(+>v^2CLwJP_sxVH6t=GQpmIlpAW`fDmK^2 z`TQe2B&EVs4i_vnhIP{4I<5rh*Yc1<5FUs?;@lITR62Eei!HvZv|Vgh;sxsKBH10b zYv{blsfj+8vTw}t0nvOrgK@gABb5Qe3n?1LtLiuy4M_Pkk1V+b2(tAw)v6^P;3Cbq z&a^E#`E2r9zpFauNhGv{#~Xg$+SR;{G)W9DDIrtGd_M=@uYTJ6q_5{KyNEwVDK0Pw61~n~~YS7R@ zjEbs#Ap_7vDm%sQcjb_oWk1pRhKNr@53)g19Tc8D@Q0XGocseO$rsI<+^Bs|7^Hgv zxuUvsx$crTPMS$EW>)yxQqh5ww`<=|dW?ukdzWBUsS3)d=^?H8zI((2P)d2(%SvTo z8G0-f3%E0Iwl;dQ8QZ!ENc#s=7{8yDf41rT$Fsr8kFW@{34O};%Fc{@ zP60_|KwS5;(#rIF6?Y0d^5<_Seudt@^3e5Z!V#}<1q0WFk^IzT^B?7~k*RIsbpo5u zs8eZ`K)!JU-2PY~51ew_or1KC`ICqKy&TK&6 zec_B~7~$m(a#_Ejhz6V!X!BTPfR)Ia=$)%KjQRQpc*uG)^o;oPz47GRcV9I9Yd=5H zP9y+nYjVI5Zg<$hXb(^Vx zv~bpdM6P;7=BE=ae%4k=if(9je$*>Ne31`MOaG5Ae;x~10~ca+ZFG=>zu0|)^I zW09e_J5sr5EC`^1!O5zuPh~2ej#0L2o`r3T(L{HQO)338bPV0p*f2pljvpnur3fXI zbr_BuNDEKDoSS@m>~NyiezAq(vK9G%{0;h{|CC|!_HM1A%8>4U;j!~ix(18$BE**m zkgT+?p~Zr%A~P9B9T%+pbqS}%H94^ZdUAFmF8U$U6UuNEPzSf!0_sp(SUgRDuMXLJIC_B+K&yBtLg zzEB3u>vL`&zkO%5>w8an$g^!0rHYyvxzLrqZ8x*G>#TdPZ?c{Y*A<#av>fG2#6H8U z_^l93 zwB1mUb0fI3tdM`U*MKEK832ZdVFjCKuMBz>m5kr?lb$u6m)A26l-&0Lcmn01v+_dRLR3+e8z~n$u zSGKGEvQ=)3A+s+a+?1A8Z3iAz$#vmrYPbx0r2#jWzNmReLceP1BV#s2#$b50(({M^ zdqoU@&`Xq<>xoaKaI6hiqNkrR7~5r(7ie%jMxPB6Q_3&pI5F;n(f6%$Hf~FD3|{(ejusO%eX)~&D$+^!sJxOQnC3QLW4*u zm$EaOyu1~RZFwToRDQv}pH;5ggcm<^mYkco2gGL497PS-k3oTs_hR^Xe$F_^&Jlca z(j36-?N(g(Zaou0l3Z=W6Wm11yVqHN#c>`-CWJuIILwwn&R`DPN82ONE! z1K34U4dD??)#GWoxuPt8u@8y|=raX=!OJ>|8t*>1}bK1ib)YPTo#bZ0Q7E9%P_ zPaLFUBFcNT!>%(!QmvFkj2;xNHN7e9tnU3sE8w}T=4SqBWDigJ{uz{NR7pt=6YhAx zqdx%Ia z_XxiwJgwi^6v*u{@Y;f<0jU& zy|DdT!w7&`dSLB>1n96o6-@LH(OjL&@th#YLzQj8xr8$QNGirr4x|utB#zu_b?0JLI%SDCzBaJ_;jkLpWEhf=I-h6g_fJY zyDpmiWlKY@xszgBbclizY2-f%610c6s*H%g8U+7`y7!-?()+TSZ6Xso$X}d-jo?*E{M*CzM=+{!u-GO{0c>=Fo9@7uq zv!(*VaK_(tXk$t&IT?_)IiuXP)xIAH#*mHyh2qVSsteF*qCo&gx<8lyg2`ki zs9!f6*JQmF=Kpev3b?55g3&rzz3Sm9x{+Aazb5FERTrj-pzR-yP_A7X*-_cd{O4>EDR0dn8lYS}c2_+aJaisMhEEPQ6`K z)zoZ7vNt*v(5c9Xa0D>uvW=q*m{R$kD54&rdW-XPn%*;zWsv4K%wBhYv!<&z<@fRY zU#kS*0!YOf^wTG%f+&Y_(dSRt{ryX*Lh*B}R*+W|G3(Q9dHcC9$%SJ5c(M|Y_o5~TzrR{k4Llj;RU;m1n9VTvV-`ZOJwGr6W zHyvMTEtt{faV2=3?ASw{_Bo}Owk}MB$;Q>LeLcs6oGlh<+g4g02-%)@oo2W0C}!&1 zgxM)2uy)49;SD0mEcVOLM?Fo}_<-t(^9lFG4T9(QUW*hfa z%t5e}nS)H%;2O}!U&FsOpyq{0eaOM51_iA1R-0n{{JYvEZl}@^)XbHo9It=CbR5Ao zkqb0;v>o#?3oM?d<)QIpqm%ZFo~S1#MtltkTb=`qU{lfJbZ@<>OANvr^SI z61Kawtt-LH%}u|mtSYFXUJ8;m{H8~rw2S)ie#SAlMi)){w?;Pkh8?MLU%8uFzzoxz zF=YN=6$2M#u;uKKQT4|x1;#X_lN)M!;D7wg&+KlP1}PRRIa+VYMF$w%p~i*Qj!D#R z#cdx4DqEK89+G2fYFm#&>H&P$uN;p)=!}1{uQjG0TWS=;pY5a--7-v zYx21z@XPVqM(*H0^2wZ57uG9qS6KQyzZ8913m%si<@6i(EJ6M4PuY?RYz)(uoWX2a z)kH4Rrk@}xjjE_|+j(U|#5Bo2qf7nB%l$6e!lsYt6skx7Z(VEH*B?r?vW%a+Z6W-l z-6Yfjl0fmBvDw(20He4xnSMpMa6K^x8q&vDrVf~{y=;`VYjle1VtqlalPa7zid!TJ zj{s0vUa6u=Cm;r4wc)iP&V4bDE$OzzAS6KDdeF5w$MWhpl;h(feRlq5a(7;y{xawP zLh_$lv$2pA@1#@Mh+`xJdfGj?|^gm15c19>kh7T@MbFS^j>K|Ux8 zWj>90J1+)L@ee0=I3wURGd9@LN3VdbFR623FGh7`VmITh;x_E5!7oJ)-erPM&wc9= zm~p&{T*-eVvl~BY2i9NN!mRSK4ZB*n+st!uO($j&c=Fk`7pfaMMLbSSr0LYcs<$q> zK9Jz-wd?i^MuIkOK4l;VMlUe`4NGDcd_;sW0XoTc5bQq3pQ`N#2avPRfvIOjB=piw^k;ve_z-eZD&9Z>YT5O^D7k8g%z8LaDO;x=B(Lo}Z_QAL%dos=KDTFi$hLng=QHQ= zNMREf%Mlk`nsC>F78ZY=_rUstIhfP$ z7e$j25leosY_jewLYBEhkk#UbBL6xlMA*OFad5-cXu=}>i%ld93c}K6H!7$@VI@%k z9eqraaIz+NEm+>Q7QbcRayTxg&Gsng*MqNx=s-zZt21RnM}V?@XVQGQ1#3dhZtGrQ zadelc+5449*PV6*(x1;;0-qsgGAK6!QtXK2)R{Z-3o`pr{105l^b{8knLn7(bqy-{ zZSbxkpfBgd1HJ$h-4bMS=;2#a4}1^!-%dl0L!LPyN#(BLSy#GMvl}ppL~Wdc;VtDp zv_)62woG4N+WI%LJG6Q`+)`aYZ~LwWmsU0 zau}FIB!rm7drI(Jo+?<9Gqq$~FS&a?+ZKyh914CZo;`K~4*pKL4_`-OZgFM|tpYn_ zbb7mJT2cp?<6@;_A(R@FGK_lggD(-|Xw4S~^4&u!xa=aG*<9xKLEE_|rwc_6b`PmL9h!PH-$v8-kuS&LiHL^gBsW#L3YJKdH{REwyPQpqFDBJdVIHKSI1+LX7e@3zNb~Wxv8XBU12xT zUea=af4^UmtUOw3aGTmJ_0(rXV6^@Z&ph0eNvGNBWHWBRI3U(ieNZ3Ke{(|dDEyZm z5|b4vpL18h|1bL$CxTKN)UHR~psRpWB~H~)oDfUpDyHcaY_8g|Kc>e4O+hqIZLr3d z?4#(iCChgXOfOFS3?$1N7rChGhP|<@`_`t(;1CentXC zqYSc);E}U=>RM`Mc&Y|9$>95))2Pq00UC!+%n`LIxKUZ_fBiw6QYC zBM;%lGh~jImRwrsMTOIh%ZiRY9^;dYu|lkyu>HZ^{E6u$gc5L%UNtCH5{L*4b%lYF zt-bHd(9popB?G@hIWE?R{7vyt&T9=eH|j^_G$|gwC`;x+eE$u4qE7t=U*|oR3lwP7 zKi)eSq+W}%nGPL5Mhiltpz&hP$gRr#qG~p=@G;Z>m*uh{rSQLt-~{~@2EH6Ev-$gv ztkyKpES-~TuxyAfs%7{1EE5#1&vdHcCtPA@-3W%2RVvy=ecv;^9~ zf$3#FVLw|&sOFy7!DE!V+OIX9ZV`ueGHfY@7E}6rS-l=fo3Z`X3G3i3x!@7EpOuy$t@ zHtwb$Tj3HSU?%0P!lNJnVncov&}N}CB99PbvDilEUAOqK{@ztQn-WBx_0S7Q4*L2ihvoqGeb*cAL_j zOH=)&q=cC{MY|0L00=MhI9tr!oA%%q8&vjbLDU)kZSt9M#d1hI1hc9JP2_SEsG&p6dOY zBuL{;);a(6U|-(mw4*y0ZoN`l)sO90HkwkdSQz6qA|S|^tAMsRX9bnDj?M&Z;1qdx}4zDL{eF8oc-ppHC2XU3+i1J)VlQgxLCqd>~DBDXHT&vWqv%3G!6YV78b z0eykaWfaOHWQpy;QESFd&9bUkJ)9~I$>kk9$Bqq#?-`CK)TKlfL+amG9x9EKowMBJ9%LwDFmPDk#6Ub7 z%Y4qoLf1_wIlIe7UZ%N8kKoD7LQf4D`L^haW`ZeoulXCihs{Mr%yGOK0zF(#$!gn- zt>ZcDi}jA^HG}@nbV6;oeiBt&0$WoJO^dRlCt`(TlQZoYp4fA8o%bSQuf>h0XBIdg z{rx{M-gf#o9{4^w$}N9{!65Cw7zXhQZ>vMtSBJsc%jb5e*Dsm!2v1ZnVBom&WDa@A zceqX!zu|Az{F?mSHC4aFR#9H+9rScAb*g=AUHev9*&Dju!W+pYUQOPjc-2OBHF`sS z7%vrqoL=H9pzBN;t?xq`*|4mqN#&AQmD>ni0qv^;7yn>w^W>Y8{XRsR&HxWWf%qB- z2GtqcO-{^u1{RyADGL zlyf%3>}3(Qhshq=exhKY8r_oESjl>JF~66JGy!l^O!r+j)Imoh^a!pb2Y7WjL0e_Bt#<2wmJQ> zuOz!MSeQZjXIc6-JvX2CcS3lFfe;l&;2FiPKa*vtWa^_oT2)|=@d2f_uQT)93 z8?vSSzR@?cp0CYjp_1S^`J}^jYB;umSNZtC*8E5G)53IyKfHyvW#P*1{Hy?xghbXZ z=x`Z6jCSLQg>Eb-6=&rG9yaeimEr|o0$tJ@vhiIuS?X@J9(c?a%j!8z+s!n+7X601 z2h(b?9pE3rGkm_j*@EAE)4NV;i$(aHnC1%k6^Y2=^`%*nK?n1F`gG6*$#&?ProGj# z1)U}e7-XT$NrB3QfZK62JU2nm8F4VN$7O+O$c897;D6s24ghzgq6ykFn zsHid#<$S1%SjUY`$f|^N~C6lsm8!4Y@gK_i61OPEMN0W0*RK}`=lO;cQBe`U6`!n@5$=k5a;|HUh z1kse9fC-D6O$SUwn2+a(Bh$j$1nW%Ns^Tj2rg)%3~vW0I2 zY{&BjL;>Z}!^!Azv(nrACO{hCc}JCv{o$D9Y|qHvTdXqQPzIw*g(so3i+n?w56*EU zs6E7Ahmk=fBGY5um1lGENqJ7yuyP7R0tNcN_vKF&Wc_Pn&TG5( zt5ol~g%idFzx1vP6>iDL+n|}CWGp+NSbhT3(PavLA^#Hec(_aI_arbzfb6y6eaf`I zEk!`o{hI*-+i~rga2^JtE1|n!z@6?~nRm-z)it>aeyqTWhU}5Z=7H*AZT&kRfm?EW@vDC+(I?V9TWSkyn-Qq%@usS{&T@LJ z)0mUZ*pc1HC6xfpj^l3iK2&08l|69u+jEO%_{vp1ZhL>Ly^@Ss+pGVqTDd8FhBYZi z)4roUv52QaZ>6J1eS<8SV${@m{a+v>uG56D27x5Itg@n$-G7Sk9iLr_>AEkMjg?UI z_HK_1X65{z&-;>aF27enR)^z!eE*t<;(?d5S6P!u56|6Gas0Rz=Pu^ zG#}wLeun-;l)307f9K3TsYeO-3iA!L+F}sFlEWw60W%5YWA^g}Te|=E3NJF1$Y5== zx|ipDToqzoq7ZpG?v5jE=alE&XnK;C&8;KPS&mwjfn2^~i=o>+Et7*N-bJRFHzdeQ zruQDb;y9IE!^8e4Z67DmzF2zJkyx1B(GWZ18>T|1g}PW)rS@BRGPb+8DKE$)o@8dQ16}) z!M2{CccPVNO{qRC?P5vkPL`!kNL}Z2cpaj5f4ys-u(axgLrm53JbS10(b){utu4hf zDtphAyZ>0Y(K_AYzM&j`4Ei)FSP&_5R74D~L;SVlk<^_hXCxQ+%auphSY6}et=J&j zU02jZvzNxfYr4hmry#$0D7E8nsRE;F|i%#)S1y`Fa-Y$qFH+_O1jP)YK~_1XvbYP1d@Z z($2Xydfp!gVEy+(s=k&%JE{=ZVm!F{riy@|H)c8T>;!X4Ilo!)@vW&@({k9%pXtyX zyctmZz*5n;)H`WkT_dO)I#cMSJH>9*TLp6OMmCbXyQki<2G}lP@C6!UCV@ zJ!SZJ&X{R5uVUM6vaI5W8rlMM0~D1NQ#W1t)rRx@;=DAi&n~Yy7a z)RLHSS4WZ0^cL8C3T2TJ=~tqkJ!kw_Wd%ebu~Lqi^;#{KI<)@9ICQ6Wo}`kZrry2w zkPnaXG(JZ7+f+*e_uRbEtAKir3c!r#V$yAQ7OX@7yN(>Btr443VmxtTb1ZY_i>c2k zogm_oye)l2Ns^T!4`0Mc#a|RP0;FM?%D7zdRv9X5Ci!%zPoG~xJ8ESc znKDV9)G**>^7|>W2355P4WJ?ZtNB zq7QHK-!A}z+N7+T=e5czrMH%pvBRI0JYH>a4J++h$hDfNoRkGBp)t!|=#-DFm|zVR zWMLlcI!vfI{R9FHM}KHjV;S3#e`aqoq96aWET9jR8$~~Q<}WyMuKSq$7E7-Xua9?% zH5*TJ%w4{GO8t4g;D}x4BA0BM7n68x=gAY^G5Xryxop2%?K}U(yQmJ7tAIwxD+SC7 zUtBV)E>rW@Px+U#OU}MNntJA>VehrAe!fd*PuBXA`_`-Eug|u7oA$kOIQ!!EN(a5< zkAKdB(#KEc02>yC0^P=-m0j|}K6hKqo-Ii{&jmaR_Cd}S*}b=y&XYHF`X2F`TO{Py zG&wi>-$7v~eY!Xq;{-|$3@ml}jlpYA3pMZKj96p(GHQ0wO}A%!{!#wo_Ul!* z^R3z@wk2lrVw<)NH(y^_%9CU}@7Jd-h#E$q!n`r^%`)Botr>UN=9X{Wa$)P)0F^4M z2GiXQkq_l=o18u|yIM-MCihmyZrhzu4@WBmNL_z#w64!?{g>%iz3Z%|pJThz-D~l- z;^oAhhcCy!+Vdrd`D@U;8wKk8>H5!&1N!GVbuyM8OUss%D2V>8bcG$*Ok-&j+BL1! z^@P)P5uax>TlB1BqrRk^F>Ty@V(0TuA0Gw=yXz#pJo?8)*Mkcj$^tuh8`l+YEja5o z-HgfF?#GUKWoma1|G!iDQc~m9mXhO3d92QRoGdxBGU)lSm_C6Eg&fPhtW*zdR0P(h z(!V4^fOYkcEGAXw(&?JdH&?BR-c@~Vsg9Lofz|rsvlnOFPVd>i{vX>DcCcOBl?`6s z4r#Mio*yqauePPpN-ufh(T3`(A0NN;e>u17y88=}7j@q5UjJ3w%l##`zd6uw`_uJ> zdBDuc%TZ!jd`?jJZNZ|)+(mk^UeSVwrLQ#UK=aFX)eB$GL_L?(lv?`x>zdNXRp;`i zzuR-h@v+gfn&}_QR>UsIk2Wm&uL5kp_%o?kvM795E^wgWckn)y_l#hdWwY!$_ww1x zNm7e|MXhAXy%*ZbB`>eWcz4`C2C6y0`5Krc!L=o*%%A2US-kIb!Dp*oH}dcP-_|<$ zw>oFSfm$JjlTa5y>eYBAiQMYnWm3jZn?PkUB)~y6E<>ZOfJMgX9JM{~9(o75gWGCg zaiEe1?>G&Z%D>k$F~CG17JmaZj`icas-OuJYF@cN1M|Cg|5+H2jc4R|(+aFr;`WJr zZ9%d5-8`W1-T#~n2+x50b%PriG<$0_UvVP25$I1_pz!Yh!VD;8fNFP%f@ Date: Mon, 16 Jun 2025 15:05:16 -0600 Subject: [PATCH 37/86] larger image --- docs/assets/overlap_patches.png | Bin 122320 -> 64780 bytes docs/source/preprocess.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assets/overlap_patches.png b/docs/assets/overlap_patches.png index 1579423b2a5f60ab9afb29e2526c1037f9dc6fc3..133d1d79f47800eb31b05a62657c8fc4de28a392 100644 GIT binary patch literal 64780 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 literal 122320 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
C#rbSZsvy}-Afn`r0Os(Et2XTJ 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%0AeD6}Y@)G5F>xxdN!2 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 Date: Tue, 17 Jun 2025 15:21:08 -0600 Subject: [PATCH 38/86] update the ci to test python 3.13 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 852dc071..f56135d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] os: ['ubuntu-latest', 'macos-latest'] defaults: run: From 9e0db3bcbd48e2c99ff90bf1b65ce44b3885d80d Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 17 Jun 2025 15:25:03 -0600 Subject: [PATCH 39/86] remove intermediate python tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f56135d9..55a639ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.13'] os: ['ubuntu-latest', 'macos-latest'] defaults: run: From c5e1fa608e2be440da3255fdf3dc199665d3d4d8 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 7 Jul 2025 19:06:20 -0600 Subject: [PATCH 40/86] document ofio --- bird/postprocess/conditional_mean.py | 2 +- bird/utilities/bubble_col_util.py | 2 +- bird/utilities/ofio.py | 498 +++++++++++++++--- .../bubble_column_pbe_20L/writeGlobalVars.py | 6 +- tests/io/test_case.py | 26 + tests/io/test_readOF.py | 110 ++++ tests/postprocess/test_cond_mean.py | 10 +- tests/postprocess/test_early_pred.py | 10 +- tests/postprocess/test_kla.py | 5 +- .../bubble_column_20L/writeGlobalVars.py | 6 +- .../loop_reactor_mixing/read_history.py | 30 +- .../loop_reactor_mixing/writeGlobalVars.py | 6 +- .../loop_reactor_reacting/writeGlobalVars.py | 6 +- 13 files changed, 618 insertions(+), 99 deletions(-) create mode 100644 tests/io/test_case.py create mode 100644 tests/io/test_readOF.py diff --git a/bird/postprocess/conditional_mean.py b/bird/postprocess/conditional_mean.py index 19918882..eb268146 100644 --- a/bird/postprocess/conditional_mean.py +++ b/bird/postprocess/conditional_mean.py @@ -88,7 +88,7 @@ def compute_cond_mean( diff=diff, ) else: - field_tmp = readOFScal(filename, nCells) + field_tmp = readOFScal(filename, nCells)["field"] vert_axis, field_cond_tmp = conditionalAverage( cellCentres[:, vert_ind], field_tmp, nbin=n_bins ) diff --git a/bird/utilities/bubble_col_util.py b/bird/utilities/bubble_col_util.py index b2d21795..068a42af 100644 --- a/bird/utilities/bubble_col_util.py +++ b/bird/utilities/bubble_col_util.py @@ -5,7 +5,7 @@ def readFromDict(val_dict, key, read_func=None, path=None, nCells=None): if key not in val_dict: - field = read_func(path, nCells) + field = read_func(path, nCells)["field"] val_dict[key] = field else: field = val_dict[key] diff --git a/bird/utilities/ofio.py b/bird/utilities/ofio.py index a8cd223f..43367689 100644 --- a/bird/utilities/ofio.py +++ b/bird/utilities/ofio.py @@ -4,86 +4,404 @@ import numpy as np -def readMesh(file): - A = np.loadtxt(file, usecols=(1, 2, 3)) - return A +def readMesh(filename: str) -> np.ndarray: + """ + Reads cell center location from meshCellCentres_X.obj + Parameters + ---------- + filename: str + meshCellCentres_X.obj filename -def readOFScal(file, nCells, nHeader=None): - # Check that the field is not a uniform field - try: - f = open(file, "r") - for i in range(20): + returns + ---------- + cell_centers: np.ndarray + Array (N,3) representing the cell centers (N is number of cells) + + """ + cell_centers = np.loadtxt(filename, usecols=(1, 2, 3)) + return cell_centers + + +def ofvec2arr(vec: str) -> np.ndarray: + """ + Converts a vector written as a string into a numpy array + + Parameters + ---------- + vec: str + Vector written as string + Must start with "(" + Must end with ")" + + returns + ---------- + vec_array: np.ndarray + Array (3,) representing the vector + + + """ + vec = vec.strip() + assert vec[0] == "(" + assert vec[-1] == ")" + + vec_list = vec[1:-1].split() + vec_array = np.array([float(entry) for entry in vec_list]) + return vec_array + + +def is_comment(line: str) -> bool: + """ + Checks if line is a comment + + Parameters + ---------- + line: str + Line of file + + returns + ---------- + is_comment: bool + True if line is a comment + False if line is not a comment + """ + is_comment = False + + sline = line.strip() + if sline.startswith("//"): + is_comment = True + elif sline.startswith("/*"): + is_comment = True + return is_comment + + +def read_meta_data(filename: str, mode: str | None = None) -> dict: + """ + Read meta data from field outputted by OpenFOAM in ASCII format + + Parameters + ---------- + filename: str + Field filename + mode: str | None + If "scalar", expects a scalar field + If "vector", expects a vector field + If None, obtained from field header + + returns + ---------- + meta_data: dict + Dictionary that contain info about the scalar field + ============= ===================================================== + Key Description (type) + ============= ===================================================== + name Name of the field (*str*) + n_cells Number of computational cells (*int*) + uniform Whether the field is uniform (*bool*) + uniform_value Uniform value if uniform field (*float* | *np.ndarray*) + type "vector" or "scalar" (*str*) + ============= ===================================================== + """ + meta_data = {} + meta_data["type"] = mode + + # Read meta data + with open(filename, "r") as f: + header_done = False + iline = 0 + while not header_done: line = f.readline() - f.close() - lineList = line.split() - if len(lineList) == 3 and lineList[1] == "uniform": - # Uniform field - val = float(lineList[2][:-1]) - Array = val * np.ones(nCells) + if not is_comment(line): + # Get field type + if (line.strip().startswith("class")) and (";" in line): + sline = line.strip().split() + field_type = sline[1][:-1] + if field_type == "volVectorField": + field_type = "vector" + elif field_type == "volScalarField": + field_type = "scalar" + else: + raise NotImplementedError + if mode is not None: + assert field_type == mode + meta_data["type"] = field_type + # Get field name + if (line.strip().startswith("object")) and (";" in line): + sline = line.strip().split() + field_name = sline[1][:-1] + meta_data["name"] = field_name + + # Check if uniform + if line.strip().startswith("internalField"): + if "nonuniform" in line: + meta_data["uniform"] = False + iline += 1 + # read until no comments + comment = True + while comment: + count_line = f.readline().strip() + if not is_comment(count_line): + comment = False + try: + n_cells = int(count_line) + meta_data["n_cells"] = n_cells + header_done = True + except ValueError: + raise ValueError( + f"Expected integer number of cells on line {iline}, got: '{count_line}'" + ) + elif "uniform" in line: + meta_data["uniform"] = True + sline = line.split() + if meta_data["type"] == "scalar": + unif_value = float(sline[-1].strip(";")) + elif meta_data["type"] == "vector": + unif_value = ofvec2arr(sline[-1].strip(";")) + else: + raise NotImplementedError( + f"Mode {mode} is unknown" + ) + meta_data["uniform_value"] = unif_value + header_done = True + + if len(line) == 0 and (not header_done): + raise ValueError( + f"File {filename} ends before meta-data found" + ) + + return meta_data + + +def readOFScal( + filename: str, + n_cells: int | None = None, + n_header: int | None = None, + meta_data: dict | None = None, +) -> dict: + """ + Read a scalar field outputted by OpenFOAM in ASCII format + + Parameters + ---------- + filename: str + Field filename + n_cells : int | None + Number of computational cells in the domain + n_header : int | None + Number of header lines + meta_data : dict | None + meta data dictionary + If None, it is read from filename + + returns + ---------- + data: dict + Dictionary that contain info about the scalar field + ======== ===================================================== + Key Description (type) + ======== ===================================================== + field For nonuniform fields, array of size the number of cells (*np.ndarray*). + For uniform fields with a specified n_cells, + array of size the number of cells (*np.ndarray*). + For uniform fields, a scalar value (*float*) + name Name of the scalar field (*str*) + n_cells Number of computational cells (*int*) + n_header Number of header lines (*int*) + ======== ===================================================== + """ + field = None + + if meta_data is None: + # Read meta data + meta_data = read_meta_data(filename, mode="scalar") + + # Set field + if meta_data["uniform"]: + if n_cells is None: + field = meta_data["uniform_value"] else: - # Not a uniform field - f = open(file, "r") - if nHeader is None: - # Find header - lines = f.readlines() - for iline, line in enumerate(lines[:-2]): - if str(nCells) in lines[iline] and "(" in lines[iline + 1]: + field = meta_data["uniform_value"] * np.ones(n_cells) + else: + n_cells = meta_data["n_cells"] + + # Get header size + if n_header is None: + oldline = "" + newline = "" + iline = 0 + eof = False + with open(filename, "r") as f: + while iline < 100 and (not eof): + line = f.readline() + if len(line) == 0: + eof = True + oldline = newline + newline = line + if str(n_cells) in oldline and "(" in newline: + n_header = iline + 1 break - nHeader = iline + 2 - f.close() - Array = np.loadtxt(file, skiprows=nHeader, max_rows=nCells) - except Exception as err: - print("Issue when reading %s" % file) - print(err) - sys.exit() - return Array + iline += 1 + else: + raise ValueError( + "Could not find a sequence {n_cells} and '(' in file ({filename})" + ) + # Rapid field read + try: + field = np.loadtxt(filename, skiprows=n_header, max_rows=n_cells) + except Exception as err: + print(f"Issue when reading {filename}") + print(err) + sys.exit() -def ofvec2arr(vec): - vec_list = vec[1:-1].split() - vec_float = [float(entry) for entry in vec_list] - return np.array(vec_float) + return { + "field": field, + "name": meta_data["name"], + "n_cells": n_cells, + "n_header": n_header, + } -def readOFVec(file, nCells, nHeader=None): - # Check that the field is not a uniform field - try: - f = open(file, "r") - for i in range(20): - line = f.readline() - f.close() - lineList = line.split() - if len(lineList) == 3 and lineList[1] == "uniform": - # Uniform field - raise NotImplementedError - val = ofvec2arr(lineList[2][:-1]) - Array = val * np.ones(nCells) +def readOFVec( + filename: str, + n_cells: int | None = None, + n_header: int | None = None, + meta_data: dict | None = None, +) -> dict: + """ + Read a vector field outputted by OpenFOAM in ASCII format + + Parameters + ---------- + filename: str + Vector field filename + n_cells : int | None + Number of computational cells in the domain + n_header : int | None + Number of header lines + meta_data : dict | None + meta data dictionary + If None, it is read from filename + + returns + ---------- + data: dict + Dictionary that contain info about the scalar field + ======== ===================================================== + Key Description (type) + ======== ===================================================== + field For nonuniform fields, array of size the number of cells by 3 (*np.ndarray*). + For uniform fields with a specified n_cells, + array of size the number of cells by 3 (*np.ndarray*). + For uniform fields, a scalar value (*float*) + name Name of the field (*str*) + n_cells Number of computational cells (*int*) + n_header Number of header lines (*int*) + ======== ===================================================== + """ + field = None + + if meta_data is None: + # Read meta data + meta_data = read_meta_data(filename, mode="vector") + + # Set field + if meta_data["uniform"]: + if n_cells is None: + field = meta_data["uniform_value"] else: - # Not a uniform field - f = open(file, "r") - if nHeader is None: - # Find header - lines = f.readlines() - for iline, line in enumerate(lines[:-2]): - if str(nCells) in lines[iline] and "(" in lines[iline + 1]: + field = meta_data["uniform_value"] * np.ones((n_cells, 3)) + else: + n_cells = meta_data["n_cells"] + if n_header is None: + oldline = "" + newline = "" + iline = 0 + eof = False + with open(filename, "r") as f: + while iline < 100 and (not eof): + line = f.readline() + if len(line) == 0: + eof = True + oldline = newline + newline = line + if str(n_cells) in oldline and "(" in newline: + n_header = iline + 1 break - nHeader = iline + 2 - f.close() - Array = np.loadtxt( - file, dtype=tuple, skiprows=nHeader, max_rows=nCells + iline += 1 + else: + raise ValueError( + "Could not find a sequence {n_cells} and '(' in file ({filename})" + ) + + try: + field = np.loadtxt( + filename, dtype=tuple, skiprows=n_header, max_rows=n_cells ) - for i in range(nCells): - Array[i, 0] = float(Array[i, 0][1:]) - Array[i, 1] = float(Array[i, 1]) - Array[i, 2] = float(Array[i, 2][:-1]) - Array = np.array(Array).astype(float) - except Exception as err: - print("Issue when reading %s" % file) - print(err) - sys.exit() + for i in range(n_cells): + field[i, 0] = float(field[i, 0][1:]) + field[i, 1] = float(field[i, 1]) + field[i, 2] = float(field[i, 2][:-1]) + field = np.array(field).astype(float) - return Array + except Exception as err: + print(f"Issue when reading {filename}") + print(err) + sys.exit() + + return { + "field": field, + "name": meta_data["name"], + "n_cells": n_cells, + "n_header": n_header, + } + + +def readOF( + filename: str, + n_cells: int | None = None, + n_header: int | None = None, + meta_data: dict | None = None, +) -> dict: + """ + Read an OpenFOAM field outputted in ASCII format + + Parameters + ---------- + filename: str + Field filename + n_cells : int | None + Number of computational cells in the domain + n_header : int | None + Number of header lines + meta_data : dict | None + meta data dictionary + If None, it is read from filename + + + returns + ---------- + data: dict + Dictionary that contain info about the scalar field + ======== ===================================================== + Key Description (type) + ======== ===================================================== + field For nonuniform fields, array of size the number of cells (*np.ndarray*). + For uniform fields with a specified n_cells, + array of size the number of cells (*np.ndarray*). + For uniform fields, a scalar value (*float*) + name Name of the field (*str*) + n_cells Number of computational cells (*int*) + n_header Number of header lines (*int*) + ======== ===================================================== + """ + if meta_data is None: + # Read meta data + meta_data = read_meta_data(filename) + if meta_data["type"] == "scalar": + return readOFScal(filename=filename, meta_data=meta_data) + if meta_data["type"] == "vector": + return readOFVec(filename=filename, meta_data=meta_data) def readSizeGroups(file): @@ -129,7 +447,29 @@ def readSizeGroups(file): return sizeGroup, binGroup -def getCaseTimes(casePath, remove_zero=False): +def getCaseTimes( + casePath: str, remove_zero: bool = False, verbose: bool = True +) -> tuple: + """ + Get list of all time folders from an OpenFOAM case + + Parameters + ---------- + casePath: str + Path to case folder + remove_zero : bool + Whether to remove zero from the time folder list + verbose : bool + Whether to print what time folders are included + + returns + ---------- + time_float_sorted: list[float] + List of time folder values in ascending order + time_str_sorted: list[str] + List of time folder names in ascending order + + """ # Read Time times_tmp = os.listdir(casePath) # remove non floats @@ -140,7 +480,8 @@ def getCaseTimes(casePath, remove_zero=False): if abs(a) < 1e-12: _ = times_tmp.pop(i) except ValueError: - print(f"{entry} not a time folder, removing") + if verbose: + print(f"{entry} not a time folder, removing") a = times_tmp.pop(i) # print('removed ', a) time_float = [float(entry) for entry in times_tmp] @@ -152,8 +493,23 @@ def getCaseTimes(casePath, remove_zero=False): return time_float_sorted, time_str_sorted -def getMeshTime(casePath): +def getMeshTime(casePath: str) -> str: + """ + Get the time at which the mesh was printed + + Parameters + ---------- + casePath: str + Path to case folder + + returns + ---------- + time_mesh: str + The name of the time at which "meshFaceCentresXXX" was created + """ + files_tmp = os.listdir(casePath) for entry in files_tmp: if entry.startswith("meshFaceCentres"): - return entry[16:-4] + time_mesh = entry[16:-4] + return time_mesh diff --git a/experimental_cases/disengagement/bubble_column_pbe_20L/writeGlobalVars.py b/experimental_cases/disengagement/bubble_column_pbe_20L/writeGlobalVars.py index 0594eccc..3445bdb8 100644 --- a/experimental_cases/disengagement/bubble_column_pbe_20L/writeGlobalVars.py +++ b/experimental_cases/disengagement/bubble_column_pbe_20L/writeGlobalVars.py @@ -34,10 +34,12 @@ def readInletArea(): def getLiqVol(): cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres))[ + "field" + ] alpha_field = readOFScal( os.path.join("0", "alpha.liquid"), len(cellCentres) - ) + )["field"] return np.sum(volume_field * alpha_field) diff --git a/tests/io/test_case.py b/tests/io/test_case.py new file mode 100644 index 00000000..95c23416 --- /dev/null +++ b/tests/io/test_case.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path + +import numpy as np +from prettyPlot.plotting import plt, pretty_labels + +from bird.utilities.ofio import getCaseTimes + + +def test_case_time(): + caseFolder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + # Read non uniform field + time_float, time_str = getCaseTimes(caseFolder) + assert np.linalg.norm(np.array(time_float) - np.array([1, 79, 80])) < 1e-6 + assert time_str == ["1", "79", "80"] + + +if __name__ == "__main__": + test_case_time() diff --git a/tests/io/test_readOF.py b/tests/io/test_readOF.py new file mode 100644 index 00000000..efa398ab --- /dev/null +++ b/tests/io/test_readOF.py @@ -0,0 +1,110 @@ +import os +from pathlib import Path + +import numpy as np +from prettyPlot.plotting import plt, pretty_labels + +from bird.utilities.ofio import readOF, readOFScal, readOFVec + + +def test_read_nonunif_scal(): + caseFolder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + # Read non uniform field + data_dict = readOFScal(filename=os.path.join(caseFolder, "79", "CO2.gas")) + assert abs(data_dict["field"][0] - 0.616955) < 1e-6 + assert abs(data_dict["field"][-1] - 0.625389) < 1e-6 + assert abs(data_dict["n_cells"] - 137980) < 1e-6 + assert abs(data_dict["field"].shape[0] - 137980) < 1e-6 + assert data_dict["name"] == "CO2.gas" + # Read non uniform field with flexible interface + data_dict = readOF(filename=os.path.join(caseFolder, "79", "CO2.gas")) + assert abs(data_dict["field"][0] - 0.616955) < 1e-6 + assert abs(data_dict["field"][-1] - 0.625389) < 1e-6 + assert abs(data_dict["n_cells"] - 137980) < 1e-6 + assert abs(data_dict["field"].shape[0] - 137980) < 1e-6 + assert data_dict["name"] == "CO2.gas" + + +def test_read_unif_scal(): + caseFolder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + # Read non uniform field + data_dict = readOFScal(filename=os.path.join(caseFolder, "79", "f.gas")) + assert abs(data_dict["field"] - 1) < 1e-6 + assert data_dict["n_cells"] is None + # Read non uniform field with flexible interface + data_dict = readOF(filename=os.path.join(caseFolder, "79", "f.gas")) + assert abs(data_dict["field"] - 1) < 1e-6 + assert data_dict["n_cells"] is None + # Read non uniform field with prespecified cell number + data_dict = readOFScal( + filename=os.path.join(caseFolder, "79", "f.gas"), n_cells=100 + ) + assert np.shape(data_dict["field"]) == (100,) + assert np.linalg.norm(data_dict["field"] - 1) < 1e-6 + assert data_dict["n_cells"] == 100 + assert data_dict["name"] == "f.gas" + + +def test_read_nonunif_vec(): + caseFolder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + # Read non uniform field + data_dict = readOFVec(filename=os.path.join(caseFolder, "79", "U.gas")) + assert ( + np.linalg.norm( + data_dict["field"][0, :] - [0.140018, 1.20333, 0.127566] + ) + < 1e-6 + ) + assert ( + np.linalg.norm( + data_dict["field"][-1, :] - [0.0409271, 0.176052, 0.0302899] + ) + < 1e-6 + ) + assert abs(data_dict["n_cells"] - 137980) < 1e-6 + assert abs(data_dict["field"].shape[0] - 137980) < 1e-6 + assert data_dict["name"] == "U.gas" + # Read non uniform field with flexible interface + data_dict = readOF(filename=os.path.join(caseFolder, "79", "U.gas")) + assert ( + np.linalg.norm( + data_dict["field"][0, :] - [0.140018, 1.20333, 0.127566] + ) + < 1e-6 + ) + assert ( + np.linalg.norm( + data_dict["field"][-1, :] - [0.0409271, 0.176052, 0.0302899] + ) + < 1e-6 + ) + assert abs(data_dict["n_cells"] - 137980) < 1e-6 + assert abs(data_dict["field"].shape[0] - 137980) < 1e-6 + assert data_dict["name"] == "U.gas" + + +if __name__ == "__main__": + test_read_nonunif_scal() + test_read_unif_scal() + test_read_nonunif_vec() diff --git a/tests/postprocess/test_cond_mean.py b/tests/postprocess/test_cond_mean.py index c453b99a..d1c3e983 100644 --- a/tests/postprocess/test_cond_mean.py +++ b/tests/postprocess/test_cond_mean.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from prettyPlot.plotting import plt, pretty_labels @@ -10,7 +11,14 @@ def test_compute_cond(): - caseFolder = os.path.join("bird", "postprocess", "data_conditional_mean") + caseFolder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) fields_list = [ "CO.gas", "CO.liquid", diff --git a/tests/postprocess/test_early_pred.py b/tests/postprocess/test_early_pred.py index f7899163..7339b92a 100644 --- a/tests/postprocess/test_early_pred.py +++ b/tests/postprocess/test_early_pred.py @@ -1,4 +1,6 @@ -from bird import BIRD_EARLY_PRED_DATA_DIR +import os +from pathlib import Path + from bird.postprocess.early_pred import ( bayes_fit, fit_and_ext, @@ -9,12 +11,18 @@ def test_fit(): + BIRD_EARLY_PRED_DATA_DIR = os.path.join( + Path(__file__).parent, "..", "..", "bird", "postprocess", "data_early" + ) data_dict, color_files = multi_data_load(BIRD_EARLY_PRED_DATA_DIR) data_dict = fit_and_ext(data_dict) plotAllEarly(data_dict, color_files=color_files, chop=True, extrap=True) def test_bayes_fit(): + BIRD_EARLY_PRED_DATA_DIR = os.path.join( + Path(__file__).parent, "..", "..", "bird", "postprocess", "data_early" + ) data_dict, color_files = multi_data_load(BIRD_EARLY_PRED_DATA_DIR) bayes_fit(data_dict) plotAllEarly_uq(data_dict, color_files=color_files) diff --git a/tests/postprocess/test_kla.py b/tests/postprocess/test_kla.py index c28f3fba..66ee5e80 100644 --- a/tests/postprocess/test_kla.py +++ b/tests/postprocess/test_kla.py @@ -1,8 +1,8 @@ import os +from pathlib import Path import pytest -from bird import BIRD_KLA_DATA_DIR from bird.postprocess.kla_utils import compute_kla, print_res_dict @@ -14,6 +14,9 @@ ], ) def test_kla(bootstrap, max_chop): + BIRD_KLA_DATA_DIR = os.path.join( + Path(__file__).parent, "..", "..", "bird", "postprocess", "data_kla" + ) res_dict = compute_kla( os.path.join(BIRD_KLA_DATA_DIR, "volume_avg.dat"), time_ind=0, diff --git a/tutorial_cases/bubble_column_20L/writeGlobalVars.py b/tutorial_cases/bubble_column_20L/writeGlobalVars.py index 0594eccc..3445bdb8 100644 --- a/tutorial_cases/bubble_column_20L/writeGlobalVars.py +++ b/tutorial_cases/bubble_column_20L/writeGlobalVars.py @@ -34,10 +34,12 @@ def readInletArea(): def getLiqVol(): cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres))[ + "field" + ] alpha_field = readOFScal( os.path.join("0", "alpha.liquid"), len(cellCentres) - ) + )["field"] return np.sum(volume_field * alpha_field) diff --git a/tutorial_cases/loop_reactor_mixing/read_history.py b/tutorial_cases/loop_reactor_mixing/read_history.py index 264711f8..a2a77ad7 100644 --- a/tutorial_cases/loop_reactor_mixing/read_history.py +++ b/tutorial_cases/loop_reactor_mixing/read_history.py @@ -11,12 +11,12 @@ def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) + alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] # print("reading alpha_liq") field_dict["alpha_liq"] = alpha_liq if not ("volume" in field_dict) or field_dict["volume"] is None: volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) + volume = readOFScal(volume_file, nCells)["field"] # print("reading Volume") field_dict["volume"] = volume alpha_liq = field_dict["alpha_liq"] @@ -28,17 +28,17 @@ def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): def co2liq(caseFolder, timeFolder, nCells, field_dict={}): if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) + alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] # print("reading alpha_liq") field_dict["alpha_liq"] = alpha_liq if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) + co2_liq = readOFScal(co2_liq_file, nCells)["field"] # print("computing co2 liq") field_dict["co2_liq"] = co2_liq if not ("volume" in field_dict) or field_dict["volume"] is None: volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) + volume = readOFScal(volume_file, nCells)["field"] # print("reading Volume") field_dict["volume"] = volume if not ("indliq" in field_dict) or field_dict["indliq"] is None: @@ -59,26 +59,26 @@ def co2liq(caseFolder, timeFolder, nCells, field_dict={}): def cliq(caseFolder, timeFolder, nCells, field_dict={}): if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) + alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] # print("reading alpha_liq") field_dict["alpha_liq"] = alpha_liq if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") - rho_liq = readOFScal(rho_liq_file, nCells) + rho_liq = readOFScal(rho_liq_file, nCells)["field"] field_dict["rho_liq"] = rho_liq if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) + co2_liq = readOFScal(co2_liq_file, nCells)["field"] # print("computing co2 liq") field_dict["co2_liq"] = co2_liq if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) + h2_liq = readOFScal(h2_liq_file, nCells)["field"] # print("computing h2 liq") field_dict["h2_liq"] = h2_liq if not ("volume" in field_dict) or field_dict["volume"] is None: volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) + volume = readOFScal(volume_file, nCells)["field"] # print("reading Volume") field_dict["volume"] = volume if not ("indliq" in field_dict) or field_dict["indliq"] is None: @@ -109,17 +109,17 @@ def cliq(caseFolder, timeFolder, nCells, field_dict={}): def h2liq(caseFolder, timeFolder, nCells, field_dict={}): if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) + alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] # print("reading alpha_liq") field_dict["alpha_liq"] = alpha_liq if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) + h2_liq = readOFScal(h2_liq_file, nCells)["field"] # print("computing h2 liq") field_dict["h2_liq"] = h2_liq if not ("volume" in field_dict) or field_dict["volume"] is None: volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) + volume = readOFScal(volume_file, nCells)["field"] # print("reading Volume") field_dict["volume"] = volume if not ("indliq" in field_dict) or field_dict["indliq"] is None: @@ -140,12 +140,12 @@ def h2liq(caseFolder, timeFolder, nCells, field_dict={}): def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) + alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] # print("reading alpha_liq") field_dict["alpha_liq"] = alpha_liq if not ("volume" in field_dict) or field_dict["volume"] is None: volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) + volume = readOFScal(volume_file, nCells)["field"] # print("reading Volume") field_dict["volume"] = volume volume = field_dict["volume"] diff --git a/tutorial_cases/loop_reactor_mixing/writeGlobalVars.py b/tutorial_cases/loop_reactor_mixing/writeGlobalVars.py index 0594eccc..3445bdb8 100644 --- a/tutorial_cases/loop_reactor_mixing/writeGlobalVars.py +++ b/tutorial_cases/loop_reactor_mixing/writeGlobalVars.py @@ -34,10 +34,12 @@ def readInletArea(): def getLiqVol(): cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres))[ + "field" + ] alpha_field = readOFScal( os.path.join("0", "alpha.liquid"), len(cellCentres) - ) + )["field"] return np.sum(volume_field * alpha_field) diff --git a/tutorial_cases/loop_reactor_reacting/writeGlobalVars.py b/tutorial_cases/loop_reactor_reacting/writeGlobalVars.py index 0594eccc..3445bdb8 100644 --- a/tutorial_cases/loop_reactor_reacting/writeGlobalVars.py +++ b/tutorial_cases/loop_reactor_reacting/writeGlobalVars.py @@ -34,10 +34,12 @@ def readInletArea(): def getLiqVol(): cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres))[ + "field" + ] alpha_field = readOFScal( os.path.join("0", "alpha.liquid"), len(cellCentres) - ) + )["field"] return np.sum(volume_field * alpha_field) From c5b79b283b6047521e0ef8d384323cddf78e8ac8 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 7 Jul 2025 19:07:23 -0600 Subject: [PATCH 41/86] remove data from package --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 49263f60..57b409d7 100644 --- a/setup.py +++ b/setup.py @@ -37,10 +37,7 @@ "*requirements.txt", "*.json", "*.yaml", - "*.csv", "*.dat", - "data_conditional_mean", - "data_kla", ] }, extras_require={ From 26a51f563fba1e41493e82571ed06d4854e8a467 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 15:16:06 -0600 Subject: [PATCH 42/86] add tests for dictionary reading --- bird/utilities/ofio.py | 216 +++++++++++++++++- tests/io/test_read_foam_dict.py | 83 +++++++ ...est_readOF.py => test_read_foam_fields.py} | 0 3 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 tests/io/test_read_foam_dict.py rename tests/io/{test_readOF.py => test_read_foam_fields.py} (100%) diff --git a/bird/utilities/ofio.py b/bird/utilities/ofio.py index 43367689..f7f13902 100644 --- a/bird/utilities/ofio.py +++ b/bird/utilities/ofio.py @@ -1,4 +1,5 @@ import os +import re import sys import numpy as np @@ -19,6 +20,8 @@ def readMesh(filename: str) -> np.ndarray: Array (N,3) representing the cell centers (N is number of cells) """ + assert filename.startswith("meshCellCentres") + assert filename.endswith(".obj") cell_centers = np.loadtxt(filename, usecols=(1, 2, 3)) return cell_centers @@ -502,7 +505,7 @@ def getMeshTime(casePath: str) -> str: casePath: str Path to case folder - returns + Returns ---------- time_mesh: str The name of the time at which "meshFaceCentresXXX" was created @@ -513,3 +516,214 @@ def getMeshTime(casePath: str) -> str: if entry.startswith("meshFaceCentres"): time_mesh = entry[16:-4] return time_mesh + + +def remove_comments(text: str) -> str: + """ + Remove C++-style comments (// and /*) from the input and markers like #{ #} + + Parameters + ---------- + text: str + Raw input text containing comments + + Returns + ---------- + text: str + Text with all comments removed + """ + + # text = re.sub( + # r"/\*.*?\*/", "", text, flags=re.DOTALL + # ) # Remove /* */ comments + # text_unc = re.sub(r"//.*", "", text) # Remove // comments + + text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) + text = re.sub(r"//.*", "", text) + text = re.sub(r"#\{", "{", text) + text = re.sub(r"#\};", "}", text) + text = re.sub(r"#codeStream", "", text) + return text + + +def tokenize(text: str) -> list[str]: + """ + Add spaces around special characters (brace and semicolons) to make them separate tokens + + Parameters + ---------- + text: str + The cleaned (comment-free) OpenFOAM-style text. + + Returns: + ---------- + token_list: list[str] + List of tokens. + """ + text = re.sub(r"([{}();])", r" \1 ", text) + token_list = text.split() + return token_list + + +def parse_tokens(tokens: list[str]) -> dict: + """ + Parse OpenFOAM tokens into a nested Python dictionary. + Special handling for `code { ... }` blocks to be stored as raw strings. + + Parameters + ---------- + tokens: list[str] + A list of tokens produced by `tokenize`. + + Returns + ---------- + parsed: dict + A nested dictionary that represents the OpenFOAM dictionary. + """ + + def parse_block(index: int) -> tuple: + result = {} + while index < len(tokens): + token = tokens[index] + if token == "}": + return result, index + 1 + elif token == "{": + raise SyntaxError("Unexpected '{'") + + key = token + index += 1 + + # key followed by dictionary + if index < len(tokens) and tokens[index] == "{": + index += 1 + if key == "code": + code_lines = [] + while tokens[index] != "}": + code_lines.append(tokens[index]) + index += 1 + index += 1 + if index < len(tokens) and tokens[index] == ";": + index += 1 + result[key] = " ".join(code_lines).strip() + else: + subdict, index = parse_block(index) + result[key] = subdict + + # key followed by list + elif index < len(tokens) and tokens[index] == "(": + index += 1 + + # Peek to check if it's a dict-list (starts with '(' then '{') + if tokens[index] == "(": + dictlist = {} + while tokens[index] != ")": + if tokens[index] != "(": + raise SyntaxError( + f"Expected '(' for label in dict-list, got {tokens[index]}" + ) + # Read full label (e.g., "(gas and liquid)") + label_tokens = [] + while tokens[index] != ")": + label_tokens.append(tokens[index]) + index += 1 + label_tokens.append(tokens[index]) # include ')' + index += 1 + label = " ".join(label_tokens) + + if tokens[index] != "{": + raise SyntaxError( + f"Expected '{{' after label {label}" + ) + index += 1 + subdict, index = parse_block(index) + dictlist[label] = subdict + index += 1 # skip final ')' + if index < len(tokens) and tokens[index] == ";": + index += 1 + result[key] = dictlist + else: + # Standard list + lst = [] + while tokens[index] != ")": + lst.append(tokens[index]) + index += 1 + index += 1 + if index < len(tokens) and tokens[index] == ";": + index += 1 + result[key] = lst + + # key followed by scalar + elif index < len(tokens): + value = tokens[index] + index += 1 + if index < len(tokens) and tokens[index] == ";": + index += 1 + result[key] = value + + return result, index + + parsed, _ = parse_block(0) + return parsed + + +def parse_openfoam_dict(filename: str) -> dict: + """ + Parse OpenFOAM dictionary into a python dictionary + + Parameters + ---------- + filename: str + OpenFOAM dictionary filename + + Returns + ---------- + dict_of: dict + A Python dictionary representing the structure of the OpenFOAM dictionary. + """ + with open(filename, "r+") as f: + text = f.read() + text = remove_comments(text) + tokens = tokenize(text) + foam_dict = parse_tokens(tokens) + return foam_dict + + +def write_openfoam_dict(d: dict, filename: str, indent: int = 0) -> None: + """ + Save a Python dictionary back to an OpenFOAM-style file. + + Parameters + ---------- + d: dict + Python dictionary to save + filename: str + The file that will contain the saved dictionary + indent: int + Number of indentation space + """ + + lines = [] + + indent_str = " " * indent + + for key, value in d.items(): + if isinstance(value, dict): + lines.append(f"{indent_str}{key}") + lines.append(f"{indent_str}{{") + lines.extend(write_openfoam_dict(value, indent + 4)) + lines.append(f"{indent_str}}}") + elif isinstance(value, list): + lines.append(f"{indent_str}{key}") + lines.append(f"{indent_str}(") + for item in value: + lines.append(f"{indent_str} {item}") + lines.append(f"{indent_str});") + else: + lines.append(f"{indent_str}{key} {value};") + + with open(filename, "w") as f: + lines = write_openfoam_dict(foam_dict) + f.write("\n".join(lines)) + f.write( + "\n\n// ************************************************************************* //\n" + ) diff --git a/tests/io/test_read_foam_dict.py b/tests/io/test_read_foam_dict.py new file mode 100644 index 00000000..3f9fa47d --- /dev/null +++ b/tests/io/test_read_foam_dict.py @@ -0,0 +1,83 @@ +import os +from pathlib import Path + +import numpy as np + +from bird.utilities.ofio import parse_openfoam_dict + + +def test_read_phaseProperties(): + const_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "tutorial_cases", + "loop_reactor_mixing", + "constant", + ) + # Read non uniform field + foam_dict = parse_openfoam_dict( + filename=os.path.join(const_folder, "phaseProperties") + ) + + assert foam_dict["phases"] == ["gas", "liquid"] + assert foam_dict["gas"]["constantCoeffs"]["d"] == "3e-3" + assert ( + foam_dict["liquid"]["Sc"]["code"] + == "os << ( $LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq ) ;" + ) + assert ( + foam_dict["diffusiveMassTransfer.liquid"]["( gas in liquid )"]["type"] + == "Higbie" + ) + assert ( + foam_dict["lift"]["( gas in liquid )"]["lift"]["swarmCorrection"][ + "type" + ] + == "none" + ) + + +def test_read_thermophysicalProperties(): + const_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "tutorial_cases", + "loop_reactor_mixing", + "constant", + ) + # Read non uniform field + foam_dict = parse_openfoam_dict( + filename=os.path.join(const_folder, "thermophysicalProperties.gas") + ) + + assert foam_dict["species"] == ["H2", "CO2", "N2"] + assert ( + foam_dict["CO2"]["thermodynamics"]["highCpCoeffs"][0] == "3.85746029" + ) + assert len(foam_dict["CO2"]["thermodynamics"]["highCpCoeffs"]) == 7 + + +def test_read_momentumTransport(): + const_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "tutorial_cases", + "loop_reactor_mixing", + "constant", + ) + # Read non uniform field + foam_dict = parse_openfoam_dict( + filename=os.path.join(const_folder, "momentumTransport.gas") + ) + + assert foam_dict["simulationType"] == "RAS" + assert foam_dict["RAS"]["turbulence"] == "on" + + +if __name__ == "__main__": + test_read_phaseProperties() + test_read_thermophysicalProperties() + test_read_momentumTransport() diff --git a/tests/io/test_readOF.py b/tests/io/test_read_foam_fields.py similarity index 100% rename from tests/io/test_readOF.py rename to tests/io/test_read_foam_fields.py From ac71303670c2cd8867a692aec39a97c48db6fc4b Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 15:23:43 -0600 Subject: [PATCH 43/86] show how to read a control dict --- tests/io/test_read_foam_dict.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/io/test_read_foam_dict.py b/tests/io/test_read_foam_dict.py index 3f9fa47d..1be1d279 100644 --- a/tests/io/test_read_foam_dict.py +++ b/tests/io/test_read_foam_dict.py @@ -76,8 +76,25 @@ def test_read_momentumTransport(): assert foam_dict["simulationType"] == "RAS" assert foam_dict["RAS"]["turbulence"] == "on" +def test_read_controlDict(): + syst_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "tutorial_cases", + "loop_reactor_mixing", + "system", + ) + # Read non uniform field + foam_dict = parse_openfoam_dict( + filename=os.path.join(syst_folder, "controlDict") + ) + + assert foam_dict["writeControl"] == "adjustableRunTime" + assert foam_dict["maxCo"] == "0.5" if __name__ == "__main__": test_read_phaseProperties() test_read_thermophysicalProperties() test_read_momentumTransport() + test_read_controlDict() From 4134b94a19d205aa9393064175995ba50c4e80d9 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 15:28:28 -0600 Subject: [PATCH 44/86] remove too strict assertion --- bird/utilities/ofio.py | 6 +++--- tests/postprocess/test_cond_mean.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bird/utilities/ofio.py b/bird/utilities/ofio.py index f7f13902..59ee0d46 100644 --- a/bird/utilities/ofio.py +++ b/bird/utilities/ofio.py @@ -20,8 +20,8 @@ def readMesh(filename: str) -> np.ndarray: Array (N,3) representing the cell centers (N is number of cells) """ - assert filename.startswith("meshCellCentres") - assert filename.endswith(".obj") + assert "meshCellCentres" in filename + assert ".obj" in filename cell_centers = np.loadtxt(filename, usecols=(1, 2, 3)) return cell_centers @@ -513,7 +513,7 @@ def getMeshTime(casePath: str) -> str: files_tmp = os.listdir(casePath) for entry in files_tmp: - if entry.startswith("meshFaceCentres"): + if "meshFaceCentres" in entry: time_mesh = entry[16:-4] return time_mesh diff --git a/tests/postprocess/test_cond_mean.py b/tests/postprocess/test_cond_mean.py index d1c3e983..fafca77f 100644 --- a/tests/postprocess/test_cond_mean.py +++ b/tests/postprocess/test_cond_mean.py @@ -39,3 +39,7 @@ def test_compute_cond(): plot_name = sequencePlot(cond, [caseFolder], field_name) pretty_labels(plot_name, "y [m]", 14) plt.close() + + +if __name__ == "__main__": + test_compute_cond() From 73e1cb9886f2bcdde5e7028b81e1fe8c488d463e Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 15:29:15 -0600 Subject: [PATCH 45/86] format --- tests/io/test_read_foam_dict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/io/test_read_foam_dict.py b/tests/io/test_read_foam_dict.py index 1be1d279..adf15227 100644 --- a/tests/io/test_read_foam_dict.py +++ b/tests/io/test_read_foam_dict.py @@ -76,6 +76,7 @@ def test_read_momentumTransport(): assert foam_dict["simulationType"] == "RAS" assert foam_dict["RAS"]["turbulence"] == "on" + def test_read_controlDict(): syst_folder = os.path.join( Path(__file__).parent, @@ -93,6 +94,7 @@ def test_read_controlDict(): assert foam_dict["writeControl"] == "adjustableRunTime" assert foam_dict["maxCo"] == "0.5" + if __name__ == "__main__": test_read_phaseProperties() test_read_thermophysicalProperties() From 587ec49d842f3ee6800272ccd028ded131f13e2e Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 16:47:40 -0600 Subject: [PATCH 46/86] update version --- bird/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/version.py b/bird/version.py index 75d8e719..9cdbdc6f 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.19" +__version__ = "0.0.21" From fd69f6b1d2a21dfb0297774618fff78c7b534dd9 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 8 Jul 2025 16:59:38 -0600 Subject: [PATCH 47/86] remove config files --- bird/version.py | 2 +- setup.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bird/version.py b/bird/version.py index 9cdbdc6f..a4ff1ec3 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.21" +__version__ = "0.0.22" diff --git a/setup.py b/setup.py index 57b409d7..3d32deab 100644 --- a/setup.py +++ b/setup.py @@ -35,9 +35,6 @@ package_data={ "": [ "*requirements.txt", - "*.json", - "*.yaml", - "*.dat", ] }, extras_require={ From bac6e157240a3a080a39ec867d0d836b59c8e089 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 08:02:47 -0600 Subject: [PATCH 48/86] remove unused imports and format --- bird/utilities/stl_plotting.py | 1 - tests/io/test_case.py | 1 - tests/io/test_read_foam_fields.py | 1 - tests/meshing/test_block_cyl_mesh.py | 62 +++++++++++++++++++++++++- tests/meshing/test_block_rect_mesh.py | 22 +++++++-- tests/meshing/test_stirred_tank.py | 10 ++++- tests/preprocess/test_dynamic_mixer.py | 20 ++++++++- tests/preprocess/test_stl_patch.py | 36 ++++++++++++++- 8 files changed, 142 insertions(+), 11 deletions(-) diff --git a/bird/utilities/stl_plotting.py b/bird/utilities/stl_plotting.py index 6f120894..19fdaabb 100644 --- a/bird/utilities/stl_plotting.py +++ b/bird/utilities/stl_plotting.py @@ -3,7 +3,6 @@ def plotSTL(stl_file): - import matplotlib.pyplot as plt from mpl_toolkits import mplot3d from stl import mesh diff --git a/tests/io/test_case.py b/tests/io/test_case.py index 95c23416..96ebb30e 100644 --- a/tests/io/test_case.py +++ b/tests/io/test_case.py @@ -2,7 +2,6 @@ from pathlib import Path import numpy as np -from prettyPlot.plotting import plt, pretty_labels from bird.utilities.ofio import getCaseTimes diff --git a/tests/io/test_read_foam_fields.py b/tests/io/test_read_foam_fields.py index efa398ab..46e49525 100644 --- a/tests/io/test_read_foam_fields.py +++ b/tests/io/test_read_foam_fields.py @@ -2,7 +2,6 @@ from pathlib import Path import numpy as np -from prettyPlot.plotting import plt, pretty_labels from bird.utilities.ofio import readOF, readOFScal, readOFVec diff --git a/tests/meshing/test_block_cyl_mesh.py b/tests/meshing/test_block_cyl_mesh.py index 1847dbc8..394da6ce 100644 --- a/tests/meshing/test_block_cyl_mesh.py +++ b/tests/meshing/test_block_cyl_mesh.py @@ -1,9 +1,9 @@ import os import sys +from pathlib import Path import numpy as np -from bird import BIRD_BLOCK_CYL_MESH_TEMP_DIR from bird.meshing.block_cyl_mesh import ( assemble_geom, assemble_mesh, @@ -19,6 +19,14 @@ def base_mesh(input_file, topo_file, output_folder): def test_side_sparger(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "sideSparger/input.json" ) @@ -30,6 +38,14 @@ def test_side_sparger(): def test_flat_donut(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "flatDonut/input.json" ) @@ -41,6 +57,14 @@ def test_flat_donut(): def test_base_column(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "baseColumn/input.json" ) @@ -52,6 +76,14 @@ def test_base_column(): def test_base_column_refine(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "baseColumn_refineSparg/input.json" ) @@ -63,6 +95,14 @@ def test_base_column_refine(): def test_base_column_projected(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "baseColumn_projected/input.json" ) @@ -74,6 +114,14 @@ def test_base_column_projected(): def test_multiring(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "multiRing_simple/input.json" ) @@ -85,6 +133,14 @@ def test_multiring(): def test_multiring_coarse(): + BIRD_BLOCK_CYL_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_cyl_mesh_templates", + ) input_file = os.path.join( BIRD_BLOCK_CYL_MESH_TEMP_DIR, "multiRing_coarse/input.json" ) @@ -93,3 +149,7 @@ def test_multiring_coarse(): ) output_folder = "system_tmp" base_mesh(input_file, topo_file, output_folder) + + +if __name__ == "__main__": + test_multiring_coarse() diff --git a/tests/meshing/test_block_rect_mesh.py b/tests/meshing/test_block_rect_mesh.py index 9cc0f00b..e3deb9b5 100644 --- a/tests/meshing/test_block_rect_mesh.py +++ b/tests/meshing/test_block_rect_mesh.py @@ -1,9 +1,9 @@ import os import sys +from pathlib import Path import numpy as np -from bird import BIRD_BLOCK_RECT_MESH_TEMP_DIR from bird.meshing.block_rect_mesh import ( assemble_geom, assemble_mesh, @@ -19,16 +19,32 @@ def base_mesh(input_file, output_folder): def test_loop_reactor(): + BIRD_BLOCK_RECT_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_rect_mesh_templates", + ) input_file = os.path.join( - BIRD_BLOCK_RECT_MESH_TEMP_DIR, "loopReactor/input.json" + BIRD_BLOCK_RECT_MESH_TEMP_DIR, "loopReactor", "input.json" ) output_folder = "system_tmp" base_mesh(input_file, output_folder) def test_subblock_reactor(): + BIRD_BLOCK_RECT_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "block_rect_mesh_templates", + ) input_file = os.path.join( - BIRD_BLOCK_RECT_MESH_TEMP_DIR, "sub_blocks/input.json" + BIRD_BLOCK_RECT_MESH_TEMP_DIR, "sub_blocks", "input.json" ) output_folder = "system_tmp" base_mesh(input_file, output_folder) diff --git a/tests/meshing/test_stirred_tank.py b/tests/meshing/test_stirred_tank.py index 73d23a46..2dacfc36 100644 --- a/tests/meshing/test_stirred_tank.py +++ b/tests/meshing/test_stirred_tank.py @@ -1,10 +1,10 @@ import argparse import os import sys +from pathlib import Path import numpy as np -from bird import BIRD_STIRRED_TANK_MESH_TEMP_DIR from bird.meshing.stirred_tank_mesh import ( get_reactor_geom, write_blocks, @@ -16,6 +16,14 @@ def test_stirred_tank(): + BIRD_STIRRED_TANK_MESH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "meshing", + "stirred_tank_mesh_templates", + ) inp = os.path.join( BIRD_STIRRED_TANK_MESH_TEMP_DIR, "base_tank", "tank_par.yaml" ) diff --git a/tests/preprocess/test_dynamic_mixer.py b/tests/preprocess/test_dynamic_mixer.py index 47668528..b2722c2d 100644 --- a/tests/preprocess/test_dynamic_mixer.py +++ b/tests/preprocess/test_dynamic_mixer.py @@ -1,13 +1,22 @@ import os +from pathlib import Path import numpy as np -from bird import BIRD_PRE_DYNMIX_TEMP_DIR from bird.meshing._mesh_tools import parseJsonFile from bird.preprocess.dynamic_mixer.mixing_fvModels import * def test_expl_list(): + BIRD_PRE_DYNMIX_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "dynamic_mixer", + "mixing_template", + ) input_dict = parseJsonFile( os.path.join(BIRD_PRE_DYNMIX_TEMP_DIR, "expl_list", "mixers.json") ) @@ -15,6 +24,15 @@ def test_expl_list(): def test_loop_list(): + BIRD_PRE_DYNMIX_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "dynamic_mixer", + "mixing_template", + ) input_dict = parseJsonFile( os.path.join( BIRD_PRE_DYNMIX_TEMP_DIR, "loop_reactor_list", "mixers.json" diff --git a/tests/preprocess/test_stl_patch.py b/tests/preprocess/test_stl_patch.py index 6ed831cf..ff6f326e 100644 --- a/tests/preprocess/test_stl_patch.py +++ b/tests/preprocess/test_stl_patch.py @@ -1,14 +1,25 @@ import os +from pathlib import Path import numpy as np +from prettyPlot.plotting import pretty_labels -from bird import BIRD_PRE_PATCH_TEMP_DIR from bird.meshing._mesh_tools import parseJsonFile from bird.preprocess.stl_patch.stl_bc import write_boundaries -from bird.utilities.stl_plotting import plotSTL, pretty_labels +from bird.utilities.stl_plotting import plotSTL def test_spider_sparger(): + BIRD_PRE_PATCH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "stl_patch", + "bc_patch_mesh_template", + ) + input_dict = parseJsonFile( os.path.join(BIRD_PRE_PATCH_TEMP_DIR, "spider_spg/inlets_outlets.json") ) @@ -19,6 +30,15 @@ def test_spider_sparger(): def test_loop_reactor(): + BIRD_PRE_PATCH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "stl_patch", + "bc_patch_mesh_template", + ) input_dict = parseJsonFile( os.path.join( BIRD_PRE_PATCH_TEMP_DIR, "loop_reactor_expl/inlets_outlets.json" @@ -31,6 +51,15 @@ def test_loop_reactor(): def test_loop_reactor_branch(): + BIRD_PRE_PATCH_TEMP_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "stl_patch", + "bc_patch_mesh_template", + ) input_dict = parseJsonFile( os.path.join( BIRD_PRE_PATCH_TEMP_DIR, "loop_reactor_branch/inlets_outlets.json" @@ -43,4 +72,7 @@ def test_loop_reactor_branch(): if __name__ == "__main__": + from prettyPlot.plotting import plt + test_spider_sparger() + plt.show() From e5f7f1053bff4e78aa5b98d008f1853e7068ee94 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 08:28:38 -0600 Subject: [PATCH 49/86] docstring for tests --- tests/io/test_case.py | 3 +++ tests/io/test_read_foam_dict.py | 12 ++++++++++++ tests/io/test_read_foam_fields.py | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/tests/io/test_case.py b/tests/io/test_case.py index 96ebb30e..cbb55537 100644 --- a/tests/io/test_case.py +++ b/tests/io/test_case.py @@ -7,6 +7,9 @@ def test_case_time(): + """ + Test for listing all time folders in a case + """ caseFolder = os.path.join( Path(__file__).parent, "..", diff --git a/tests/io/test_read_foam_dict.py b/tests/io/test_read_foam_dict.py index adf15227..6b539d92 100644 --- a/tests/io/test_read_foam_dict.py +++ b/tests/io/test_read_foam_dict.py @@ -7,6 +7,9 @@ def test_read_phaseProperties(): + """ + Test for reading content of `constant/phaseProperties` + """ const_folder = os.path.join( Path(__file__).parent, "..", @@ -39,6 +42,9 @@ def test_read_phaseProperties(): def test_read_thermophysicalProperties(): + """ + Test for reading content of `constant/thermophysicalProperties` + """ const_folder = os.path.join( Path(__file__).parent, "..", @@ -60,6 +66,9 @@ def test_read_thermophysicalProperties(): def test_read_momentumTransport(): + """ + Test for reading content of `constant/momentumTransport` + """ const_folder = os.path.join( Path(__file__).parent, "..", @@ -78,6 +87,9 @@ def test_read_momentumTransport(): def test_read_controlDict(): + """ + Test for reading content of `system/controlDict` + """ syst_folder = os.path.join( Path(__file__).parent, "..", diff --git a/tests/io/test_read_foam_fields.py b/tests/io/test_read_foam_fields.py index 46e49525..b293fc66 100644 --- a/tests/io/test_read_foam_fields.py +++ b/tests/io/test_read_foam_fields.py @@ -7,6 +7,9 @@ def test_read_nonunif_scal(): + """ + Test for reading non uniform scalarField + """ caseFolder = os.path.join( Path(__file__).parent, "..", @@ -32,6 +35,9 @@ def test_read_nonunif_scal(): def test_read_unif_scal(): + """ + Test for reading uniform scalarField + """ caseFolder = os.path.join( Path(__file__).parent, "..", @@ -59,6 +65,9 @@ def test_read_unif_scal(): def test_read_nonunif_vec(): + """ + Test for reading non uniform vectorField + """ caseFolder = os.path.join( Path(__file__).parent, "..", From 2511102ffaeadf9d98d98c84d01cdd97f777ed7a Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 14:24:52 -0600 Subject: [PATCH 50/86] centralize postprocess --- bird/postprocess/computeQoI/compute_QOI.py | 137 +++--- bird/postprocess/conditional_mean.py | 49 +-- bird/postprocess/post_quantities.py | 480 +++++++++++++++++++++ bird/utilities/bubble_col_util.py | 244 ----------- tests/postprocess/test_post_quantities.py | 104 +++++ 5 files changed, 649 insertions(+), 365 deletions(-) create mode 100644 bird/postprocess/post_quantities.py delete mode 100644 bird/utilities/bubble_col_util.py create mode 100644 tests/postprocess/test_post_quantities.py diff --git a/bird/postprocess/computeQoI/compute_QOI.py b/bird/postprocess/computeQoI/compute_QOI.py index e7074d12..257774d8 100644 --- a/bird/postprocess/computeQoI/compute_QOI.py +++ b/bird/postprocess/computeQoI/compute_QOI.py @@ -1,15 +1,12 @@ import argparse -import sys - -import numpy as np - -sys.path.append("../utilities") import os import pickle +import sys -from ofio import * +import numpy as np -from bird.utilities.bubble_col_util import * +from bird.postprocess.post_quantities import * +from bird.utilities.ofio import * parser = argparse.ArgumentParser( description="Compute means QoI of OpenFOAM fields" @@ -29,15 +26,14 @@ nargs="+", help="List of variables to compute", default=[ - "GH", - "GH_height", + "gh", "d", "CO2_liq", "CO_liq", "H2_liq", - "kla_CO2", - "kla_CO", - "kla_H2", + "c_CO2_liq", + "c_CO_liq", + "c_H2_liq", ], required=False, ) @@ -109,87 +105,80 @@ def get_var( localFolder = os.path.join(case_path, time_folder) localFolder_vol = os.path.join(case_path, mesh_time_str) if name.lower() == "gh": - var, val_dict = computeGH( - localFolder, localFolder_vol, nCells, cellCentres, val_dict - ) - elif name.lower() == "gh_height": - var, val_dict = computeGH_height( - localFolder, + var, val_dict = compute_gas_holdup( + case_path, + time_folder, nCells, - cellCentres, - height_liq_base=7.0, - val_dict=val_dict, + volume_time="0", + field_dict=val_dict, ) elif name.lower() == "d": - var, val_dict = computeDiam(localFolder, nCells, cellCentres, val_dict) + var, val_dict = compute_ave_bubble_diam( + case_path, + time_folder, + nCells, + volume_time="0", + field_dict=val_dict, + ) elif name.lower() == "co2_liq": - var, val_dict = computeSpec_liq( - localFolder, + var, val_dict = compute_ave_y_liq( + case_path, + time_folder, nCells, - field_name="CO2.liquid", - key="co2_liq", - cellCentres=cellCentres, - val_dict=val_dict, + volume_time="0", + spec_name="CO2", + field_dict=val_dict, ) elif name.lower() == "co_liq": - var, val_dict = computeSpec_liq( - localFolder, + var, val_dict = compute_ave_y_liq( + case_path, + time_folder, nCells, - field_name="CO.liquid", - key="co_liq", - cellCentres=cellCentres, - val_dict=val_dict, + volume_time="0", + spec_name="CO", + field_dict=val_dict, ) elif name.lower() == "h2_liq": - var, val_dict = computeSpec_liq( - localFolder, + var, val_dict = compute_ave_y_liq( + case_path, + time_folder, nCells, - field_name="H2.liquid", - key="h2_liq", - cellCentres=cellCentres, - val_dict=val_dict, + volume_time="0", + spec_name="H2", + field_dict=val_dict, ) - elif name.lower() == "kla_co": - if "D_CO" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_CO")] - else: - diff = None - var, val_dict = computeSpec_kla( - localFolder, + elif name.lower() == "c_co2_liq": + var, val_dict = compute_ave_conc_liq( + case_path, + time_folder, nCells, - key_suffix="co", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, + volume_time="0", + spec_name="CO2", + mol_weight=44.00995 * 1e-3, + field_dict=val_dict, ) - elif name.lower() == "kla_co2": - if "D_CO2" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_CO2")] - else: - diff = None - var, val_dict = computeSpec_kla( - localFolder, + elif name.lower() == "c_co_liq": + var, val_dict = compute_ave_conc_liq( + case_path, + time_folder, nCells, - key_suffix="co2", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, + volume_time="0", + spec_name="CO", + mol_weight=28.01055 * 1e-3, + field_dict=val_dict, ) - elif name.lower() == "kla_h2": - if "D_H2" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_H2")] - else: - diff = None - var, val_dict = computeSpec_kla( - localFolder, + elif name.lower() == "c_h2_liq": + var, val_dict = compute_ave_conc_liq( + case_path, + time_folder, nCells, - key_suffix="h2", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, + volume_time="0", + spec_name="H2", + mol_weight=2.01594 * 1e-3, + field_dict=val_dict, ) else: - sys.exit(f"ERROR: unknown variable {name}") + raise NotImplementedError(f"Unknown variable {name}") return var, val_dict diff --git a/bird/postprocess/conditional_mean.py b/bird/postprocess/conditional_mean.py index eb268146..5f246b66 100644 --- a/bird/postprocess/conditional_mean.py +++ b/bird/postprocess/conditional_mean.py @@ -3,7 +3,7 @@ from prettyPlot.plotting import plt -from bird.utilities.bubble_col_util import * +from bird.postprocess.post_quantities import * from bird.utilities.mathtools import * from bird.utilities.ofio import * @@ -41,54 +41,9 @@ def compute_cond_mean( for field_name in field_name_list: field_file.append(os.path.join(case_path, time_folder, field_name)) - # if os.path.isfile(d_gas_file): - # has_d = True - # else: - # has_d = False - for filename, name in zip(field_file, field_name_list): val_dict = {} - if name.lower() == "kla_co": - if "D_CO" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_CO")] - else: - diff = None - field_tmp, val_dict = computeSpec_kla_field( - os.path.join(case_path, time_folder), - nCells, - key_suffix="co", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, - ) - elif name.lower() == "kla_co2": - if "D_CO2" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_CO2")] - else: - diff = None - field_tmp, val_dict = computeSpec_kla_field( - os.path.join(case_path, time_folder), - nCells, - key_suffix="co2", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, - ) - elif name.lower() == "kla_h2": - if "D_H2" in diff_name_list: - diff = diff_val_list[diff_name_list.index("D_H2")] - else: - diff = None - field_tmp, val_dict = computeSpec_kla_field( - os.path.join(case_path, time_folder), - nCells, - key_suffix="h2", - cellCentres=cellCentres, - val_dict=val_dict, - diff=diff, - ) - else: - field_tmp = readOFScal(filename, nCells)["field"] + field_tmp = readOFScal(filename, nCells)["field"] vert_axis, field_cond_tmp = conditionalAverage( cellCentres[:, vert_ind], field_tmp, nbin=n_bins ) diff --git a/bird/postprocess/post_quantities.py b/bird/postprocess/post_quantities.py new file mode 100644 index 00000000..3b8ae5a2 --- /dev/null +++ b/bird/postprocess/post_quantities.py @@ -0,0 +1,480 @@ +import numpy as np + +from bird.utilities.ofio import * + + +def read_field( + case_folder: str, + time_folder: str, + field_name: str, + n_cells: int | None = None, + field_dict: dict = {}, +) -> dict: + """ + Read field at a given time and store it in dictionary for later reuse + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + field_name: str + Name of the field file to read + n_cells : int | None + Number of cells in the domain + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + field : np.ndarray | float + Field read + field_dict : dict + Dictionary of fields read + """ + + if not (field_name in field_dict) or field_dict[field_name] is None: + # Read field if it had not been read before + field_file = os.path.join(case_folder, time_folder, field_name) + field = readOF(field_file, n_cells=n_cells)["field"] + field_dict[field_name] = field + else: + # Get field from dict if it has been read before + field = field_dict[field_name] + + return field, field_dict + + +def get_ind_liq( + case_folder: str | None = None, + time_folder: str | None = None, + n_cells: int | None = None, + field_dict: dict = {}, +) -> tuple: + """ + Get indices of pure liquid cells (where alpha.liquid > 0.5) + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + field_name: str + Name of the field file to read + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + ind_liq : np.ndarray | float + indices of pure liquid cells + field_dict : dict + Dictionary of fields read + """ + + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + + # Compute indices of pure liquid + if not ("ind_liq" in field_dict) or field_dict["ind_liq"] is None: + alpha_liq, field_dict = read_field( + case_folder, + time_folder, + field_name="alpha.liquid", + n_cells=n_cells, + field_dict=field_dict, + ) + ind_liq = np.argwhere(alpha_liq > 0.5) + field_dict["ind_liq"] = ind_liq + else: + ind_liq = field_dict["ind_liq"] + + return ind_liq, field_dict + + +def get_ind_gas( + case_folder: str | None = None, + time_folder: str | None = None, + n_cells: int | None = None, + field_dict: dict = {}, +) -> tuple: + """ + Get indices of pure gas cells (where alpha.liquid <= 0.5) + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + field_name: str + Name of the field file to read + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + ind_gas : np.ndarray | float + indices of pure gas cells + field_dict : dict + Dictionary of fields read + """ + + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + + # Compute indices of pure liquid + if not ("ind_gas" in field_dict) or field_dict["ind_gas"] is None: + alpha_liq = read_field( + case_folder, + time_folder, + field_name="alpha.liquid", + n_cells=n_cells, + field_dict=field_dict, + ) + ind_gas = np.argwhere(alpha_liq <= 0.5) + field_dict["ind_gas"] = ind_gas + else: + ind_gas = field_dict["ind_gas"] + + return ind_gas, field_dict + + +def compute_gas_holdup( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + volume_time: str = "0", + field_dict: dict = {}, +) -> tuple: + """ + Calculate volume averaged gas hold up at a given time + $\frac{1}{V_{\rm tot}} \int_{V} (1-\alpha_{\rm liq}) dV$ + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + volume_time : str + Time folder to read to get the cell volumes + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + gas_holdup: float + Volume averaged gas holdup + field_dict : dict + Dictionary of fields read + """ + + # Read relevant fields + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + kwargs_vol = { + "case_folder": case_folder, + "time_folder": volume_time, + "n_cells": n_cells, + } + alpha_liq, field_dict = read_field( + field_name="alpha.liquid", field_dict=field_dict, **kwargs + ) + try: + cell_volume, field_dict = read_field( + field_name="V", field_dict=field_dict, **kwargs_vol + ) + except FileNotFoundError: + message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" + message += "You can generate V with\n\t" + message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" + sys.exit(message) + + # Calculate + gas_holdup = np.sum((1 - alpha_liq) * cell_volume) / np.sum(cell_volume) + + return gas_holdup, field_dict + + +def compute_ave_y_liq( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + volume_time: str = "0", + spec_name: str = "CO2", + field_dict={}, +) -> tuple: + """ + Calculate liquid volume averaged mass fraction of a species at a given time + + $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} Y dV_{\rm liq}$ + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + volume_time : str + Time folder to read to get the cell volumes + spec_name : str + Name of the species + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + liq_ave_y: float + Liquid volume averaged mass fraction + field_dict : dict + Dictionary of fields read + """ + + # Read relevant fields + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + kwargs_vol = { + "case_folder": case_folder, + "time_folder": volume_time, + "n_cells": n_cells, + } + alpha_liq, field_dict = read_field( + field_name="alpha.liquid", field_dict=field_dict, **kwargs + ) + y_liq, field_dict = read_field( + field_name=f"{spec_name}.liquid", field_dict=field_dict, **kwargs + ) + ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) + try: + cell_volume, field_dict = read_field( + field_name="V", field_dict=field_dict, **kwargs_vol + ) + except FileNotFoundError: + message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" + message += "You can generate V with\n\t" + message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" + sys.exit(message) + + # Only compute over the liquid + if isinstance(alpha_liq, np.ndarray): + alpha_liq = alpha_liq[ind_liq] + if isinstance(cell_volume, np.ndarray): + cell_volume = cell_volume[ind_liq] + if isinstance(y_liq, np.ndarray): + y_liq = y_liq[ind_liq] + + # Calculate + liq_ave_y = np.sum(alpha_liq * y_liq * cell_volume) / np.sum( + alpha_liq * cell_volume + ) + + return liq_ave_y, field_dict + + +def compute_ave_conc_liq( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + volume_time: str = "0", + spec_name: str = "CO2", + mol_weight: float = 0.04401, + rho_val: float | None = 1000, + field_dict={}, + verbose: bool = True, +) -> tuple: + """ + Calculate liquid volume averaged concentration of a species at a given time + + $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} \rho_{\rm liq} Y / W dV_{\rm liq}$ + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + volume_time : str + Time folder to read to get the cell volumes + spec_name : str + Name of the species + mol_weight : float + Molecular weight of species (kg/mol) + rho_val : float | None + Constant density not available from time folder (kg/m3) + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + verbose : bool + If true, output mol weight, species name and density + + Returns + ---------- + conc_ave: float + Liquid volume averaged species concentration + field_dict : dict + Dictionary of fields read + """ + if verbose: + print( + f"INFO: Computing concentration for {spec_name} with molecular weight {mol_weight:.4g} kg/mol" + ) + if rho_val is not None: + print(f"INFO: Assuming liquid density {rho_val} kg/m3") + + # Read relevant fields + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + kwargs_vol = { + "case_folder": case_folder, + "time_folder": volume_time, + "n_cells": n_cells, + } + alpha_liq, field_dict = read_field( + field_name="alpha.liquid", field_dict=field_dict, **kwargs + ) + y_liq, field_dict = read_field( + field_name=f"{spec_name}.liquid", field_dict=field_dict, **kwargs + ) + ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) + try: + cell_volume, field_dict = read_field( + field_name="V", field_dict=field_dict, **kwargs_vol + ) + except FileNotFoundError: + message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" + message += "You can generate V with\n\t" + message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" + sys.exit(message) + + # Density of liquid is not always printed (special case) + if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: + if rho_val is not None: + rho_liq = rho_val + field_dict["rho_liq"] = rho_val + else: + rho_liq_file = os.path.join(case_folder, time_folder, "rhom") + rho_liq = readOFScal(rho_liq_file, n_cells)["field"] + field_dict["rho_liq"] = rho_liq + else: + rho_liq = field_dict["rho_liq"] + + # Only compute over the liquid + if isinstance(y_liq, np.ndarray): + y_liq = y_liq[ind_liq] + if isinstance(alpha_liq, np.ndarray): + alpha_liq = alpha_liq[ind_liq] + if isinstance(alpha_liq, np.ndarray): + cell_volume = cell_volume[ind_liq] + if isinstance(rho_liq, np.ndarray): + rho_liq = rho_liq[ind_liq] + + conc_loc = rho_liq * y_liq / mol_weight + + conc_ave = np.sum(conc_loc * alpha_liq * cell_volume) / np.sum( + alpha_liq * cell_volume + ) + + return conc_ave, field_dict + + +def compute_ave_bubble_diam( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + volume_time: str = "0", + field_dict={}, +) -> tuple: + """ + Calculate averaged bubble diameter over the liquid volume + $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} D dV$ + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + volume_time : str + Time folder to read to get the cell volumes + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + diam: float + Volume averaged gas holdup + field_dict : dict + Dictionary of fields read + """ + + # Read relevant fields + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + kwargs_vol = { + "case_folder": case_folder, + "time_folder": volume_time, + "n_cells": n_cells, + } + alpha_liq, field_dict = read_field( + field_name="alpha.liquid", field_dict=field_dict, **kwargs + ) + d_gas, field_dict = read_field( + field_name="d.gas", field_dict=field_dict, **kwargs + ) + ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) + try: + cell_volume, field_dict = read_field( + field_name="V", field_dict=field_dict, **kwargs_vol + ) + except FileNotFoundError: + message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" + message += "You can generate V with\n\t" + message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" + sys.exit(message) + + # Only compute over the liquid + if isinstance(d_gas, np.ndarray): + d_gas = d_gas[ind_liq] + if isinstance(alpha_liq, np.ndarray): + alpha_liq = alpha_liq[ind_liq] + if isinstance(alpha_liq, np.ndarray): + cell_volume = cell_volume[ind_liq] + + # Calculate + diam = np.sum(d_gas * alpha_liq * cell_volume) / np.sum( + alpha_liq * cell_volume + ) + + return diam, val_dict diff --git a/bird/utilities/bubble_col_util.py b/bird/utilities/bubble_col_util.py deleted file mode 100644 index 068a42af..00000000 --- a/bird/utilities/bubble_col_util.py +++ /dev/null @@ -1,244 +0,0 @@ -import numpy as np - -from bird.utilities.ofio import * - - -def readFromDict(val_dict, key, read_func=None, path=None, nCells=None): - if key not in val_dict: - field = read_func(path, nCells)["field"] - val_dict[key] = field - else: - field = val_dict[key] - return field, val_dict - - -def check_indLiq(ind_liq, cellCentres): - height_liq = cellCentres[ind_liq, 1] - ind_to_remove = np.argwhere(height_liq > 9.5) - if len(ind_to_remove) > 0: - ind_liq_copy = ind_liq.copy() - n_remove = len(ind_to_remove) - print(f"ind liq found to be at high heights {n_remove} times") - ind_to_remove = list(ind_liq[ind_to_remove[:, 0]][:, 0]) - ind_liq_copy = list(set(list(ind_liq[:, 0])) - set(ind_to_remove)) - assert len(ind_liq_copy) == len(ind_liq) - n_remove - ind_liq = np.reshape(np.array(ind_liq_copy), (-1, 1)) - return ind_liq - - -def check_indHeight(ind_height, cellCentres): - height_liq = cellCentres[ind_height, 1] - ind_to_remove = np.argwhere(height_liq < 6) - if len(ind_to_remove) > 0: - ind_height_copy = ind_height.copy() - n_remove = len(ind_to_remove) - print(f"ind height found to be at low heights {n_remove} times") - ind_to_remove = list(ind_height[ind_to_remove[:, 0]][:, 0]) - ind_height_copy = list( - set(list(ind_height_copy[:, 0])) - set(ind_to_remove) - ) - assert len(ind_height_copy) == len(ind_height) - n_remove - ind_height = np.reshape(np.array(ind_height_copy), (-1, 1)) - return ind_height - - -def indLiqFromDict(val_dict, localFolder, nCells, cellCentres): - if "ind_liq" not in val_dict: - alpha_gas, val_dict = readFromDict( - val_dict=val_dict, - key="alpha_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "alpha.gas"), - nCells=nCells, - ) - ind_liq = np.argwhere(alpha_gas < 0.8)[:, 0] - ind_liq = check_indLiq(ind_liq, cellCentres) - val_dict["ind_liq"] = ind_liq - else: - ind_liq = val_dict["ind_liq"] - - return ind_liq, val_dict - - -def computeGH(localFolder, localFolder_vol, nCells, cellCentres, val_dict={}): - alpha_gas, val_dict = readFromDict( - val_dict=val_dict, - key="alpha_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "alpha.gas"), - nCells=nCells, - ) - volume, val_dict = readFromDict( - val_dict=val_dict, - key="volume", - read_func=readOFScal, - path=os.path.join(localFolder_vol, "V"), - nCells=nCells, - ) - ind_liq, val_dict = indLiqFromDict( - val_dict, localFolder, nCells, cellCentres - ) - - holdup = np.sum(alpha_gas[ind_liq] * volume[ind_liq]) / np.sum( - volume[ind_liq] - ) - return holdup, val_dict - - -def computeGH_height( - localFolder, nCells, cellCentres, height_liq_base, val_dict={} -): - alpha_gas, val_dict = readFromDict( - val_dict=val_dict, - key="alpha_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "alpha.gas"), - nCells=nCells, - ) - - tol = 1e-3 - tol_max = 0.1 - nFound = 0 - iteration = 0 - while nFound <= 10 and tol < tol_max: - ind_height = np.argwhere(abs(alpha_gas - 0.8) < tol) - ind_height = check_indHeight(ind_height, cellCentres) - nFound = len(ind_height) - tol *= 1.1 - tol = np.clip(tol, a_min=None, a_max=0.2) - iteration += 1 - - if iteration > 1: - print(f"\tChanged GH tol to {tol:.2g}") - - height_liq = np.mean(cellCentres[ind_height, 1]) - holdup = height_liq / height_liq_base - 1 - - return holdup, val_dict - - -def computeDiam(localFolder, nCells, cellCentres, val_dict={}): - d_gas, val_dict = readFromDict( - val_dict=val_dict, - key="d_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "d.gas"), - nCells=nCells, - ) - ind_liq, val_dict = indLiqFromDict( - val_dict, localFolder, nCells, cellCentres - ) - - diam = np.mean(d_gas[ind_liq]) - - return diam, val_dict - - -def computeSpec_liq( - localFolder, nCells, field_name, key, cellCentres, val_dict={} -): - species, val_dict = readFromDict( - val_dict=val_dict, - key=key, - read_func=readOFScal, - path=os.path.join(localFolder, field_name), - nCells=nCells, - ) - ind_liq, val_dict = indLiqFromDict( - val_dict, localFolder, nCells, cellCentres - ) - - species = np.mean(species[ind_liq]) - - return species, val_dict - - -def computeSpec_kla_field( - localFolder, nCells, key_suffix, cellCentres, val_dict={}, diff=None -): - if "slip_vel" not in val_dict: - u_gas, val_dict = readFromDict( - val_dict=val_dict, - key="u_gas", - read_func=readOFVec, - path=os.path.join(localFolder, "U.gas"), - nCells=nCells, - ) - u_liq, val_dict = readFromDict( - val_dict=val_dict, - key="u_liq", - read_func=readOFVec, - path=os.path.join(localFolder, "U.liquid"), - nCells=nCells, - ) - slipvel = np.linalg.norm(u_liq - u_gas, axis=1) - val_dict["slipvel"] = slipvel - - rho_gas, val_dict = readFromDict( - val_dict=val_dict, - key="rho_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "thermo:rho.gas"), - nCells=nCells, - ) - if "D_" + key_suffix not in val_dict: - if diff is None: - T_gas, val_dict = readFromDict( - val_dict=val_dict, - key="T_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "T.gas"), - nCells=nCells, - ) - mu = 1.67212e-06 * np.sqrt(T_gas) / (1 + 170.672 / T_gas) - D = mu / rho_gas / 0.7 - else: - D = np.ones(rho_gas.shape) * diff - val_dict["D_" + key_suffix] = D - else: - D = val_dict["D_" + key_suffix] - - d_gas, val_dict = readFromDict( - val_dict=val_dict, - key="d_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "d.gas"), - nCells=nCells, - ) - if "Sh_" + key_suffix not in val_dict: - # Sh = 1.12*np.sqrt(rho_gas*slipvel*d_gas/(D*0.7*rho_gas))*np.sqrt(0.7) - Sh = ( - 2.0 - + 0.552 - * np.sqrt(rho_gas * slipvel * d_gas / (D * 0.7 * rho_gas)) - * 0.8889 - ) - val_dict["Sh_" + key_suffix] = Sh - else: - Sh = val_dict["Sh_" + key_suffix] - - alpha_gas, val_dict = readFromDict( - val_dict=val_dict, - key="alpha_gas", - read_func=readOFScal, - path=os.path.join(localFolder, "alpha.gas"), - nCells=nCells, - ) - - kla = Sh * 6 * alpha_gas / d_gas / d_gas * D - - return kla, val_dict - - -def computeSpec_kla( - localFolder, nCells, key_suffix, cellCentres, val_dict={}, diff=None -): - kla, val_dict = computeSpec_kla_field( - localFolder, nCells, key_suffix, cellCentres, val_dict, diff - ) - - ind_liq, val_dict = indLiqFromDict( - val_dict, localFolder, nCells, cellCentres - ) - - return np.mean(kla[ind_liq]), val_dict diff --git a/tests/postprocess/test_post_quantities.py b/tests/postprocess/test_post_quantities.py new file mode 100644 index 00000000..9fb0bdbf --- /dev/null +++ b/tests/postprocess/test_post_quantities.py @@ -0,0 +1,104 @@ +import os +from pathlib import Path + +from prettyPlot.plotting import plt, pretty_labels + +from bird.postprocess.post_quantities import * + + +def test_compute_gh(): + """ + Test for gas holdup calculation + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + kwargs = {"case_folder": case_folder, "n_cells": None, "volume_time": "1"} + field_dict = {} + gh, field_dict = compute_gas_holdup( + time_folder="1", field_dict=field_dict, **kwargs + ) + field_dict = {} + gh, field_dict = compute_gas_holdup( + time_folder="79", field_dict=field_dict, **kwargs + ) + + +def test_ave_y_liq(): + """ + Test for liquid volume averaged species mass fraction + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + kwargs = { + "time_folder": "79", + "case_folder": case_folder, + "n_cells": None, + "volume_time": "1", + } + field_dict = {} + ave_y_co2, field_dict = compute_ave_y_liq( + spec_name="CO2", field_dict=field_dict, **kwargs + ) + ave_y_co, field_dict = compute_ave_y_liq( + spec_name="CO", field_dict=field_dict, **kwargs + ) + ave_y_h2, field_dict = compute_ave_y_liq( + spec_name="H2", field_dict=field_dict, **kwargs + ) + + +def test_ave_conc_liq(): + """ + Test for liquid volume averaged species concentration + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + kwargs = { + "time_folder": "79", + "case_folder": case_folder, + "n_cells": None, + "volume_time": "1", + } + field_dict = {} + ave_conc_co2, field_dict = compute_ave_conc_liq( + spec_name="CO2", + mol_weight=44.00995 * 1e-3, + field_dict=field_dict, + **kwargs, + ) + ave_conc_co, field_dict = compute_ave_conc_liq( + spec_name="CO", + mol_weight=28.01055 * 1e-3, + field_dict=field_dict, + **kwargs, + ) + ave_conc_h2, field_dict = compute_ave_conc_liq( + spec_name="H2", + mol_weight=2.01594 * 1e-3, + field_dict=field_dict, + **kwargs, + ) + + +if __name__ == "__main__": + test_compute_gh() + test_ave_y_liq() + test_ave_conc_liq() From d0d77b92555358a7453db33483ea67d7153965d2 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 14:30:13 -0600 Subject: [PATCH 51/86] test bubble diameter calculation --- tests/postprocess/test_post_quantities.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/postprocess/test_post_quantities.py b/tests/postprocess/test_post_quantities.py index 9fb0bdbf..5f0460d8 100644 --- a/tests/postprocess/test_post_quantities.py +++ b/tests/postprocess/test_post_quantities.py @@ -29,6 +29,29 @@ def test_compute_gh(): ) +def test_compute_diam(): + """ + Test for bubble diameter calculation + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + kwargs = {"case_folder": case_folder, "n_cells": None, "volume_time": "1"} + field_dict = {} + diam, field_dict = compute_ave_bubble_diam( + time_folder="1", field_dict=field_dict, **kwargs + ) + field_dict = {} + diam, field_dict = compute_ave_bubble_diam( + time_folder="79", field_dict=field_dict, **kwargs + ) + + def test_ave_y_liq(): """ Test for liquid volume averaged species mass fraction From 1789d0c937631a7eca635a8ddb15eb831fe9b6d3 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 14:34:50 -0600 Subject: [PATCH 52/86] remove rogue val dict --- bird/postprocess/post_quantities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/postprocess/post_quantities.py b/bird/postprocess/post_quantities.py index 3b8ae5a2..a5f7d8f6 100644 --- a/bird/postprocess/post_quantities.py +++ b/bird/postprocess/post_quantities.py @@ -477,4 +477,4 @@ def compute_ave_bubble_diam( alpha_liq * cell_volume ) - return diam, val_dict + return diam, field_dict From 1f7c87c672ba6a804b9c44055b393e9e886c5cdf Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 14:37:03 -0600 Subject: [PATCH 53/86] push to pypi to pass test --- bird/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/version.py b/bird/version.py index a4ff1ec3..bb5a3100 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.22" +__version__ = "0.0.23" From 7069e23d4dfba08c87c55ae6a98a15b6fcda641a Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 15:48:03 -0600 Subject: [PATCH 54/86] superficial velocity calculation --- bird/postprocess/post_quantities.py | 238 ++++++++++++++++++---- tests/postprocess/test_post_quantities.py | 26 +++ 2 files changed, 227 insertions(+), 37 deletions(-) diff --git a/bird/postprocess/post_quantities.py b/bird/postprocess/post_quantities.py index a5f7d8f6..16fcf7a5 100644 --- a/bird/postprocess/post_quantities.py +++ b/bird/postprocess/post_quantities.py @@ -9,7 +9,7 @@ def read_field( field_name: str, n_cells: int | None = None, field_dict: dict = {}, -) -> dict: +) -> tuple: """ Read field at a given time and store it in dictionary for later reuse @@ -46,6 +46,102 @@ def read_field( return field, field_dict +def read_cell_volume( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + field_dict: dict = {}, +) -> tuple: + """ + Read volume at a given time and store it in dictionary for later reuse + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + cell_volume : np.ndarray | float + cell volume read from file + field_dict : dict + Dictionary of fields read + """ + kwargs_vol = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + try: + cell_volume, field_dict = read_field( + field_name="V", field_dict=field_dict, **kwargs_vol + ) + except FileNotFoundError: + message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" + message += "You can generate V with\n\t" + message += f"`postProcess -func writeCellVolumes -time {time_folder} -case {case_folder}`" + sys.exit(message) + + return cell_volume, field_dict + + +def read_cell_centers( + case_folder: str, + cell_centers_file: str, + field_dict: dict = {}, +) -> tuple: + """ + Read volume at a given time and store it in dictionary for later reuse + + Parameters + ---------- + case_folder: str + Path to case folder + cell_centers_file : str + Filename of cell center data + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + cell_centers : np.ndarray + cell centers read from file + field_dict : dict + Dictionary of fields read + """ + + if ( + not ("cell_centers" in field_dict) + or field_dict["cell_centers"] is None + ): + try: + cell_centers = readMesh( + os.path.join(case_folder, cell_centers_file) + ) + field_dict["cell_centers"] = cell_centers + except FileNotFoundError: + message = f"ERROR: could not find {cell_centers_file}\n" + message += "You can generate it with\n\t" + message += f"`writeMeshObj -case {case_folder}`\n" + time_float, time_str = getCaseTimes(case_folder) + correct_path = f"meshCellCentres_{time_str[0]}.obj" + if not correct_path == cell_centers_file: + message += ( + f"And adjust the cell center file path to {correct_path}" + ) + sys.exit(message) + else: + cell_centers = field_dict["cell_centers"] + + return cell_centers, field_dict + + def get_ind_liq( case_folder: str | None = None, time_folder: str | None = None, @@ -198,15 +294,9 @@ def compute_gas_holdup( alpha_liq, field_dict = read_field( field_name="alpha.liquid", field_dict=field_dict, **kwargs ) - try: - cell_volume, field_dict = read_field( - field_name="V", field_dict=field_dict, **kwargs_vol - ) - except FileNotFoundError: - message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" - message += "You can generate V with\n\t" - message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" - sys.exit(message) + cell_volume, field_dict = read_cell_volume( + field_dict=field_dict, **kwargs_vol + ) # Calculate gas_holdup = np.sum((1 - alpha_liq) * cell_volume) / np.sum(cell_volume) @@ -214,6 +304,95 @@ def compute_gas_holdup( return gas_holdup, field_dict +def compute_superficial_velocity( + case_folder: str, + time_folder: str, + n_cells: int | None = None, + volume_time: str = "0", + direction: int = 1, + cell_centers_file: str = "meshCellCentres_0.obj", + field_dict: dict = {}, +) -> tuple: + """ + Calculate superficial velocity in a given direction at a given time + + Parameters + ---------- + case_folder: str + Path to case folder + time_folder: str + Name of time folder to analyze + n_cells : int | None + Number of cells in the domain + volume_time : str + Time folder to read to get the cell volumes + direction : int + Direction along which to calculate the superficial velocity + cell_centers_file : str + Filename of cell center data + field_dict : dict + Dictionary of fields used to avoid rereading the same fields to calculate different quantities + + Returns + ---------- + sup_vel: float + Superficial velocity + field_dict : dict + Dictionary of fields read + """ + + # Read relevant fields + kwargs = { + "case_folder": case_folder, + "time_folder": time_folder, + "n_cells": n_cells, + } + kwargs_vol = { + "case_folder": case_folder, + "time_folder": volume_time, + "n_cells": n_cells, + } + alpha_gas, field_dict = read_field( + field_name="alpha.gas", field_dict=field_dict, **kwargs + ) + U_gas, field_dict = read_field( + field_name="U.gas", field_dict=field_dict, **kwargs + ) + U_gas = U_gas[:, direction] + + cell_volume, field_dict = read_cell_volume( + field_dict=field_dict, **kwargs_vol + ) + cell_centers, field_dict = read_cell_centers( + case_folder=case_folder, + cell_centers_file=cell_centers_file, + field_dict=field_dict, + ) + + # Find all cells in the middle of the domain + max_dir = np.amax(cell_centers[:, direction]) + min_dir = np.amin(cell_centers[:, direction]) + middle_dir = (max_dir + min_dir) / 2 + tol = (max_dir - min_dir) * 0.05 + ind_middle = np.argwhere( + (cell_centers[:, direction] >= middle_dir - tol) + & (cell_centers[:, direction] <= middle_dir + tol) + ) + + # Only compute in the middle + if isinstance(alpha_gas, np.ndarray): + alpha_gas = alpha_gas[ind_middle] + if isinstance(cell_volume, np.ndarray): + cell_volume = cell_volume[ind_middle] + if isinstance(U_gas, np.ndarray): + U_gas = U_gas[ind_middle] + + # Compute + sup_vel = np.sum(U_gas * alpha_gas * cell_volume) / np.sum(cell_volume) + + return sup_vel, field_dict + + def compute_ave_y_liq( case_folder: str, time_folder: str, @@ -268,15 +447,10 @@ def compute_ave_y_liq( field_name=f"{spec_name}.liquid", field_dict=field_dict, **kwargs ) ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) - try: - cell_volume, field_dict = read_field( - field_name="V", field_dict=field_dict, **kwargs_vol - ) - except FileNotFoundError: - message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" - message += "You can generate V with\n\t" - message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" - sys.exit(message) + + cell_volume, field_dict = read_cell_volume( + field_dict=field_dict, **kwargs_vol + ) # Only compute over the liquid if isinstance(alpha_liq, np.ndarray): @@ -363,15 +537,10 @@ def compute_ave_conc_liq( field_name=f"{spec_name}.liquid", field_dict=field_dict, **kwargs ) ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) - try: - cell_volume, field_dict = read_field( - field_name="V", field_dict=field_dict, **kwargs_vol - ) - except FileNotFoundError: - message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" - message += "You can generate V with\n\t" - message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" - sys.exit(message) + + cell_volume, field_dict = read_cell_volume( + field_dict=field_dict, **kwargs_vol + ) # Density of liquid is not always printed (special case) if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: @@ -454,15 +623,10 @@ def compute_ave_bubble_diam( field_name="d.gas", field_dict=field_dict, **kwargs ) ind_liq, field_dict = get_ind_liq(field_dict=field_dict, **kwargs) - try: - cell_volume, field_dict = read_field( - field_name="V", field_dict=field_dict, **kwargs_vol - ) - except FileNotFoundError: - message = f"ERROR: could not find {os.path.join(case_folder,volume_time,'V')}\n" - message += "You can generate V with\n\t" - message += f"`postProcess -func writeCellVolumes -time {volume_time} -case {case_folder}`" - sys.exit(message) + + cell_volume, field_dict = read_cell_volume( + field_dict=field_dict, **kwargs_vol + ) # Only compute over the liquid if isinstance(d_gas, np.ndarray): diff --git a/tests/postprocess/test_post_quantities.py b/tests/postprocess/test_post_quantities.py index 5f0460d8..56b36625 100644 --- a/tests/postprocess/test_post_quantities.py +++ b/tests/postprocess/test_post_quantities.py @@ -52,6 +52,31 @@ def test_compute_diam(): ) +def test_compute_superficial_velocity(): + """ + Test for superficial velocity calculation + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + kwargs = { + "case_folder": case_folder, + "n_cells": None, + "volume_time": "1", + "direction": 1, + "cell_centers_file": "meshCellCentres_1.obj", + } + field_dict = {} + sup_vel, field_dict = compute_superficial_velocity( + time_folder="79", field_dict=field_dict, **kwargs + ) + + def test_ave_y_liq(): """ Test for liquid volume averaged species mass fraction @@ -122,6 +147,7 @@ def test_ave_conc_liq(): if __name__ == "__main__": + test_compute_superficial_velocity() test_compute_gh() test_ave_y_liq() test_ave_conc_liq() From a139d17b5056538a8e0f67e3996a634194d90372 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 15:49:40 -0600 Subject: [PATCH 55/86] update bird version --- bird/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bird/version.py b/bird/version.py index bb5a3100..07d5c654 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.23" +__version__ = "0.0.24" From 393e33875f20300502414adc32360c5cadb4693a Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 16:16:36 -0600 Subject: [PATCH 56/86] add flat panel reactor tutorial and add it to the ci --- .github/workflows/ci.yml | 5 + .../FlatPanel_250L_ASU/0.orig/T.gas | 44 +++++ .../FlatPanel_250L_ASU/0.orig/T.liquid | 44 +++++ .../FlatPanel_250L_ASU/0.orig/U.gas | 40 +++++ .../FlatPanel_250L_ASU/0.orig/U.liquid | 40 +++++ .../FlatPanel_250L_ASU/0.orig/alpha.gas.orig | 41 +++++ .../0.orig/alpha.liquid.orig | 40 +++++ .../FlatPanel_250L_ASU/0.orig/alphat.gas | 47 +++++ .../FlatPanel_250L_ASU/0.orig/alphat.liquid | 47 +++++ .../FlatPanel_250L_ASU/0.orig/epsilon.gas | 48 +++++ .../FlatPanel_250L_ASU/0.orig/epsilon.liquid | 48 +++++ .../FlatPanel_250L_ASU/0.orig/epsilonm | 48 +++++ .../FlatPanel_250L_ASU/0.orig/k.gas | 48 +++++ .../FlatPanel_250L_ASU/0.orig/k.liquid | 48 +++++ tutorial_cases/FlatPanel_250L_ASU/0.orig/km | 48 +++++ .../FlatPanel_250L_ASU/0.orig/nut.gas | 46 +++++ .../FlatPanel_250L_ASU/0.orig/nut.liquid | 46 +++++ tutorial_cases/FlatPanel_250L_ASU/0.orig/p | 39 ++++ .../FlatPanel_250L_ASU/0.orig/p_rgh | 40 +++++ tutorial_cases/FlatPanel_250L_ASU/AllmassT | 18 ++ tutorial_cases/FlatPanel_250L_ASU/README.md | 5 + tutorial_cases/FlatPanel_250L_ASU/constant/g | 21 +++ .../constant/momentumTransport.gas | 27 +++ .../constant/momentumTransport.liquid | 27 +++ .../constant/phaseProperties | 170 ++++++++++++++++++ .../constant/thermophysicalProperties.gas | 47 +++++ .../constant/thermophysicalProperties.liquid | 51 ++++++ tutorial_cases/FlatPanel_250L_ASU/presteps.sh | 4 + tutorial_cases/FlatPanel_250L_ASU/run.sh | 2 + .../FlatPanel_250L_ASU/system/blockMeshDict | 110 ++++++++++++ .../FlatPanel_250L_ASU/system/controlDict | 59 ++++++ .../FlatPanel_250L_ASU/system/controlDict~ | 59 ++++++ .../system/decomposeParDict | 45 +++++ .../FlatPanel_250L_ASU/system/fvConstraints | 23 +++ .../FlatPanel_250L_ASU/system/fvSchemes | 65 +++++++ .../FlatPanel_250L_ASU/system/fvSolution | 84 +++++++++ .../FlatPanel_250L_ASU/system/panel.m4 | 110 ++++++++++++ .../FlatPanel_250L_ASU/system/setFieldsDict | 37 ++++ 38 files changed, 1771 insertions(+) create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/T.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/T.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/U.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/U.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.gas.orig create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.liquid.orig create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilonm create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/k.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/k.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/km create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/p create mode 100644 tutorial_cases/FlatPanel_250L_ASU/0.orig/p_rgh create mode 100644 tutorial_cases/FlatPanel_250L_ASU/AllmassT create mode 100644 tutorial_cases/FlatPanel_250L_ASU/README.md create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/g create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/phaseProperties create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.gas create mode 100644 tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.liquid create mode 100644 tutorial_cases/FlatPanel_250L_ASU/presteps.sh create mode 100644 tutorial_cases/FlatPanel_250L_ASU/run.sh create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/blockMeshDict create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/controlDict create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/controlDict~ create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/decomposeParDict create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/fvConstraints create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/fvSchemes create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/fvSolution create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 create mode 100644 tutorial_cases/FlatPanel_250L_ASU/system/setFieldsDict diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55a639ba..05effe9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -176,4 +176,9 @@ jobs: cd tutorial_cases/airlift_40m bash run.sh cd ../../ + - name: Run flat panel reactor tutorial + run: | + cd tutorial_cases/FlatPanel_250L_ASU + bash run.sh + cd ../../ diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.gas new file mode 100644 index 00000000..1d515f11 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.gas @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 308; + +boundaryField +{ + "wall_.*" + { + type zeroGradient; + } + outlet + { + type inletOutlet; + phi phi.gas; + inletValue $internalField; + value $internalField; + } + inlet + { + type fixedValue; + value $internalField; + } + frontAndBackPlanes + { + type empty; + } +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.liquid new file mode 100644 index 00000000..2d4e3a4d --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/T.liquid @@ -0,0 +1,44 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object T.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 1 0 0 0]; + +internalField uniform 308; + +boundaryField +{ + "wall_.*" + { + type zeroGradient; + } + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue $internalField; + value $internalField; + } + inlet + { + type fixedValue; + value $internalField; + } + frontAndBackPlanes + { + type empty; + } +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.gas new file mode 100644 index 00000000..808205dc --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.gas @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format binary; + class volVectorField; + object U.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +internalField uniform (0 0.1 0); + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + outlet + { + type pressureInletOutletVelocity; + phi phi.gas; + value $internalField; + } + "wall_.*" + { + type fixedValue; + value uniform (0 0 0); + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.liquid new file mode 100644 index 00000000..84b3317a --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/U.liquid @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format binary; + class volVectorField; + object U.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -1 0 0 0 0]; + +internalField uniform (0 0 0); + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + outlet + { + type pressureInletOutletVelocity; + phi phi.liquid; + value $internalField; + } + "wall_.*" + { + type fixedValue; + value uniform (0 0 0); + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.gas.orig b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.gas.orig new file mode 100644 index 00000000..e827a9ed --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.gas.orig @@ -0,0 +1,41 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + location "0"; + object alpha.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform 0.5; + } + outlet + { + type inletOutlet; + phi phi.gas; + inletValue uniform 1; + value uniform 1; + } + "wall_.*" + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.liquid.orig b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.liquid.orig new file mode 100644 index 00000000..c2531311 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alpha.liquid.orig @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alpha.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 0 0 0 0 0 0]; + +internalField uniform 1; + +boundaryField +{ + inlet + { + type fixedValue; + value uniform 0.5; + } + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue uniform 0; + value uniform 0; + } + "wall_.*" + { + type zeroGradient; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.gas new file mode 100644 index 00000000..8d53c794 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + "wall_.*" + { + type compressible::alphatWallFunction; + Prt 0.85; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.liquid new file mode 100644 index 00000000..4e503479 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/alphat.liquid @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object alphat.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -1 0 0 0 0]; + +internalField uniform 0; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + "wall_.*" + { + type compressible::alphatWallFunction; + Prt 0.85; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.gas new file mode 100644 index 00000000..24d4f17e --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +internalField uniform 1.5e-4; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.gas; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type epsilonWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.liquid new file mode 100644 index 00000000..c307061f --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilon.liquid @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilon.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +internalField uniform 1.5e-4; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type epsilonWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilonm b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilonm new file mode 100644 index 00000000..cc6f87e6 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/epsilonm @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object epsilonm; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -3 0 0 0 0]; + +internalField uniform 1.5e-4; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phim; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type zeroGradient; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.gas new file mode 100644 index 00000000..0a844f7a --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.gas @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +internalField uniform 3.75e-5; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.gas; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type kqRWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.liquid new file mode 100644 index 00000000..86ac25af --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/k.liquid @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object k.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +internalField uniform 3.75e-5; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phi.liquid; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type kqRWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/km b/tutorial_cases/FlatPanel_250L_ASU/0.orig/km new file mode 100644 index 00000000..6d8fb00f --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/km @@ -0,0 +1,48 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object km; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -2 0 0 0 0]; + +internalField uniform 3.75e-5; + +boundaryField +{ + inlet + { + type fixedValue; + value $internalField; + } + + outlet + { + type inletOutlet; + phi phim; + inletValue $internalField; + value $internalField; + } + + "wall_.*" + { + type zeroGradient; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.gas b/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.gas new file mode 100644 index 00000000..d2208625 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.gas @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-8; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + "wall_.*" + { + type nutkWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.liquid b/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.liquid new file mode 100644 index 00000000..f08faba7 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/nut.liquid @@ -0,0 +1,46 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object nut.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 2 -1 0 0 0 0]; + +internalField uniform 1e-8; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + + outlet + { + type calculated; + value $internalField; + } + + "wall_.*" + { + type nutkWallFunction; + value $internalField; + } + + defaultFaces + { + type empty; + } +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/p b/tutorial_cases/FlatPanel_250L_ASU/0.orig/p new file mode 100644 index 00000000..46b3fa42 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/p @@ -0,0 +1,39 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 1e5; + +boundaryField +{ + inlet + { + type calculated; + value $internalField; + } + outlet + { + type calculated; + value $internalField; + } + "wall_.*" + { + type calculated; + value $internalField; + } +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/tutorial_cases/FlatPanel_250L_ASU/0.orig/p_rgh b/tutorial_cases/FlatPanel_250L_ASU/0.orig/p_rgh new file mode 100644 index 00000000..c66435ba --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/0.orig/p_rgh @@ -0,0 +1,40 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class volScalarField; + object p_rgh; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [1 -1 -2 0 0 0 0]; + +internalField uniform 1e5; + +boundaryField +{ + inlet + { + type fixedFluxPressure; + value $internalField; + } + outlet + { + type prghPressure; + p $internalField; + value $internalField; + } + "wall_.*" + { + type fixedFluxPressure; + value $internalField; + } +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/tutorial_cases/FlatPanel_250L_ASU/AllmassT b/tutorial_cases/FlatPanel_250L_ASU/AllmassT new file mode 100644 index 00000000..84a47c33 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/AllmassT @@ -0,0 +1,18 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +# Source tutorial run functions +. $WM_PROJECT_DIR/bin/tools/RunFunctions + +mv constant/NofvModels constant/fvModels + +echo -e " - Manipulate default specie" +sed 's|^defaultSpecie.*|defaultSpecie air;|' -i constant/thermophysicalProperties.gas + +echo -e " - Manipulate controlDict" +sed 's|^endTime.*|endTime 160;|' -i system/controlDict +sed 's|^deltaT.*|deltaT 0.0001;|' -i system/controlDict +sed 's|^adjustTimeStep.*|adjustTimeStep no;//yes;//|' -i system/controlDict + +## runApplication $(getApplication) +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/FlatPanel_250L_ASU/README.md b/tutorial_cases/FlatPanel_250L_ASU/README.md new file mode 100644 index 00000000..7ab83e55 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/README.md @@ -0,0 +1,5 @@ +### Flat Panel reactor + +Single core exec + +1. `bash run.sh` diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/g b/tutorial_cases/FlatPanel_250L_ASU/constant/g new file mode 100644 index 00000000..2c53688b --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/g @@ -0,0 +1,21 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class uniformDimensionedVectorField; + location "constant"; + object g; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +dimensions [0 1 -2 0 0 0 0]; +value (0 -9.81 0); + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.gas b/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.gas new file mode 100644 index 00000000..e73147a8 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.gas @@ -0,0 +1,27 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +simulationType RAS; + +RAS +{ + model mixtureKEpsilon; // continuousGasKEpsilon; + + turbulence on; + printCoeffs on; +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.liquid b/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.liquid new file mode 100644 index 00000000..f3205aa6 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/momentumTransport.liquid @@ -0,0 +1,27 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object momentumTransport.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +simulationType RAS; + +RAS +{ + model mixtureKEpsilon; // LaheyKEpsilon; + + turbulence on; + printCoeffs on; +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/phaseProperties b/tutorial_cases/FlatPanel_250L_ASU/constant/phaseProperties new file mode 100644 index 00000000..3d165947 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/phaseProperties @@ -0,0 +1,170 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object phaseProperties; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +type basicMultiphaseSystem; + +phases (gas liquid); + +gas +{ + type purePhaseModel; + diameterModel isothermal; + isothermalCoeffs + { + d0 3e-3; + p0 1e5; + } + + residualAlpha 1e-6; +} + +liquid +{ + type purePhaseModel; + diameterModel constant; + constantCoeffs + { + d 1e-4; + } + + residualAlpha 1e-6; +} + +blending +{ + default + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.3; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.3; + } + + drag + { + type linear; + minFullyContinuousAlpha.gas 0.7; + minPartlyContinuousAlpha.gas 0.5; + minFullyContinuousAlpha.liquid 0.7; + minPartlyContinuousAlpha.liquid 0.5; + } +} + +surfaceTension +( + (gas and liquid) + { + type constant; + sigma 0.07; + } +); + +aspectRatio +( + (gas in liquid) + { + type constant; + E0 1.0; + } + + (liquid in gas) + { + type constant; + E0 1.0; + } +); + +drag +( + (gas in liquid) + { + type SchillerNaumann; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } + + (liquid in gas) + { + type SchillerNaumann; + residualRe 1e-3; + swarmCorrection + { + type none; + } + } + + (gas and liquid) + { + type segregated; + m 0.5; + n 8; + swarmCorrection + { + type none; + } + } +); + +virtualMass +( + (gas in liquid) + { + type constantCoefficient; + Cvm 0.5; + } + + (liquid in gas) + { + type constantCoefficient; + Cvm 0.5; + } +); + +heatTransfer +( + (gas in liquid) + { + type RanzMarshall; + residualAlpha 1e-4; + } + + (liquid in gas) + { + type RanzMarshall; + residualAlpha 1e-4; + } +); + +phaseTransfer +(); + +lift +(); + +wallLubrication +(); + +turbulentDispersion +(); + +interfaceCompression +(); + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.gas b/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.gas new file mode 100644 index 00000000..28d92618 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.gas @@ -0,0 +1,47 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.gas; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +thermoType +{ + type heRhoThermo; + mixture pureMixture; + transport const; + thermo hConst; + equationOfState perfectGas; + specie specie; + energy sensibleInternalEnergy; +} + +mixture +{ + specie + { + molWeight 28.9; + } + thermodynamics + { + Cp 1007; + Hf 0; + } + transport + { + mu 1.84e-05; + Pr 0.7; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.liquid b/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.liquid new file mode 100644 index 00000000..afb88b7e --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/constant/thermophysicalProperties.liquid @@ -0,0 +1,51 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "constant"; + object thermophysicalProperties.liquid; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +thermoType +{ + type heRhoThermo; + mixture pureMixture; + transport const; + thermo eConst; + equationOfState rPolynomial; + specie specie; + energy sensibleInternalEnergy; +} + +mixture +{ + specie + { + molWeight 18; + } + equationOfState + { + C (0.001278 -2.1055e-06 3.9689e-09 4.3772e-13 -2.0225e-16); + } + thermodynamics + { + Cv 4195; + Hf 0; + } + transport + { + mu 3.645e-4; + Pr 2.289; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/presteps.sh b/tutorial_cases/FlatPanel_250L_ASU/presteps.sh new file mode 100644 index 00000000..238ba587 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/presteps.sh @@ -0,0 +1,4 @@ +cp -r 0.orig 0 +m4 ./system/panel.m4 > ./system/blockMeshDict +blockMesh +setFields diff --git a/tutorial_cases/FlatPanel_250L_ASU/run.sh b/tutorial_cases/FlatPanel_250L_ASU/run.sh new file mode 100644 index 00000000..bef3c727 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/run.sh @@ -0,0 +1,2 @@ +bash presteps.sh +birdmultiphaseEulerFoam diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/blockMeshDict b/tutorial_cases/FlatPanel_250L_ASU/system/blockMeshDict new file mode 100644 index 00000000..d2203a4e --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/blockMeshDict @@ -0,0 +1,110 @@ +//--------------------------------*- C++ -*---------------------------------- +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object blockMeshDict; +} +// ************************************ + + + + + + +convertToMeters 1; + + //wall-sparge distance + // sparger diameter + // total lenght + // total depth +//define(H, 0.9144) //total height + //total height + + + + + + +vertices +( + ( 0 0 0 ) // Vertex block0_0 = 0 + ( 1.1176 0 0 ) // Vertex block0_1 = 1 + ( 1.1176 1.1 0 ) // Vertex block0_2 = 2 + ( 0 1.1 0 ) // Vertex block0_3 = 3 + ( 0.02925 0 0.02925 ) // Vertex block0_4 = 4 + ( 1.08835 0 0.02925 ) // Vertex block0_5 = 5 + ( 1.08835 1.1 0.02925 ) // Vertex block0_6 = 6 + ( 0.02925 1.1 0.02925 ) // Vertex block0_7 = 7 + ( 0.02925 0 0.03425) // Vertex block0_8 = 8 + ( 1.08835 0 0.03425) // Vertex block0_9 = 9 + ( 1.08835 1.1 0.03425) // Vertex block0_10 = 10 + ( 0.02925 1.1 0.03425) // Vertex block0_11 = 11 + ( 0 0 0.0635 ) // Vertex block0_12 = 12 + ( 1.1176 0 0.0635 ) // Vertex block0_13 = 13 + ( 1.1176 1.1 0.0635 ) // Vertex block0_14 = 14 + ( 0 1.1 0.0635 ) // Vertex block0_15 = 15 + +); + +blocks +( + //block 0 + hex ( 0 1 2 3 4 5 6 7 ) (100 100 10) simpleGrading (1 1 1) + //block 1 + hex ( 5 1 2 6 9 13 14 10) ( 10 100 10 ) simpleGrading (1 1 1) + //block 2 + hex ( 8 9 10 11 12 13 14 15) ( 100 100 10) simpleGrading (1 1 1) + //block 3 + hex ( 0 4 7 3 12 8 11 15 ) ( 10 100 10 ) simpleGrading (1 1 1) + //block 4 + hex (4 5 6 7 8 9 10 11) ( 100 100 10 ) simpleGrading (1 1 1) +); + +edges +( +); + +patches +( + patch inlet + ( + ( 4 5 9 8 ) + ) + + patch outlet + ( + ( 3 2 6 7 ) + ( 11 10 14 15 ) + ( 3 7 11 15 ) + ( 2 14 10 6 ) + ( 7 6 10 11) + ) + + wall wall_sides + ( + ( 0 3 15 12 ) + ( 1 13 14 2 ) + ) + + wall wall_frontback + ( + ( 12 15 14 13 ) + ( 0 1 2 3 ) + ) + + wall wall_botttom + ( + ( 0 1 5 4 ) + ( 8 9 13 12 ) + ( 0 4 8 12 ) + ( 1 13 9 5) + ) +); + +mergePatchPairs +( +); + diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/controlDict b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict new file mode 100644 index 00000000..1ae159fc --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict @@ -0,0 +1,59 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object controlDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +application multiphaseEulerFoam; + +startFrom startTime; + +startTime 0; + +stopAt endTime; + +endTime 100; + +deltaT 0.0005; + +writeControl adjustableRunTime; + +writeInterval 0.5; + +purgeWrite 0; + +writeFormat ascii; + +writePrecision 6; + +writeCompression off; + +timeFormat general; + +timePrecision 6; + +runTimeModifiable yes; + +adjustTimeStep yes;//no; + +maxCo 0.3; + +maxDeltaT 1; + +functions +{ + #includeFunc fieldAverage(U.gas, U.liquid, alpha.gas, p) +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/controlDict~ b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict~ new file mode 100644 index 00000000..9e045ee7 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict~ @@ -0,0 +1,59 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object controlDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +application multiphaseEulerFoam; + +startFrom startTime; + +startTime 0; + +stopAt endTime; + +endTime 100; + +deltaT 0.0005; + +writeControl runTime; + +writeInterval 1; + +purgeWrite 0; + +writeFormat ascii; + +writePrecision 6; + +writeCompression off; + +timeFormat general; + +timePrecision 6; + +runTimeModifiable yes; + +adjustTimeStep no; + +maxCo 0.5; + +maxDeltaT 1; + +functions +{ + #includeFunc fieldAverage(U.gas, U.liquid, alpha.gas, p) +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/decomposeParDict b/tutorial_cases/FlatPanel_250L_ASU/system/decomposeParDict new file mode 100644 index 00000000..a4095548 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/decomposeParDict @@ -0,0 +1,45 @@ +/*--------------------------------*- C++ -*----------------------------------*\ +| ========= | | +| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | +| \\ / O peration | Version: 2.4.0 | +| \\ / A nd | Web: www.OpenFOAM.org | +| \\/ M anipulation | | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + location "system"; + object decomposeParDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +numberOfSubdomains 32; // running the case + +method hierarchical; + +simpleCoeffs +{ + n ( 2 16 1 ); + delta 0.001; +} + +hierarchicalCoeffs +{ + n ( 2 16 1 ); + delta 0.001; + order xyz; +} + +manualCoeffs +{ + dataFile ""; +} + +distributed no; + +roots ( ); + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/fvConstraints b/tutorial_cases/FlatPanel_250L_ASU/system/fvConstraints new file mode 100644 index 00000000..dbf1d468 --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/fvConstraints @@ -0,0 +1,23 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + object fvConstraints; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +limitp +{ + type limitPressure; + + min 1e4; +} + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/fvSchemes b/tutorial_cases/FlatPanel_250L_ASU/system/fvSchemes new file mode 100644 index 00000000..21904d1e --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/fvSchemes @@ -0,0 +1,65 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSchemes; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +ddtSchemes +{ + default Euler; +} + +gradSchemes +{ + default Gauss linear; +} + +divSchemes +{ + default none; + + div(phi,alpha.gas) Gauss vanLeer; + div(phi,alpha.liquid) Gauss vanLeer; + div(phir,alpha.liquid,alpha.gas) Gauss vanLeer; + div(phir,alpha.gas,alpha.liquid) Gauss vanLeer; + + "div\(alphaRhoPhi.*,U.*\)" Gauss limitedLinearV 1; + "div\(phi.*,U.*\)" Gauss limitedLinearV 1; + + "div\(alphaRhoPhi.*,(h|e).*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,K.*\)" Gauss limitedLinear 1; + "div\(alphaRhoPhi.*,\(p\|thermo:rho.*\)\)" Gauss limitedLinear 1; + + "div\(alphaRhoPhi.*,(k|epsilon).*\)" Gauss limitedLinear 1; + "div\(phim,(k|epsilon)m\)" Gauss limitedLinear 1; + + "div\(\(\(\(alpha.*\*thermo:rho.*\)*nuEff.*\)\*dev2\(T\(grad\(U.*\)\)\)\)\)" Gauss linear; +} + +laplacianSchemes +{ + default Gauss linear uncorrected; +} + +interpolationSchemes +{ + default linear; +} + +snGradSchemes +{ + default uncorrected; +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/fvSolution b/tutorial_cases/FlatPanel_250L_ASU/system/fvSolution new file mode 100644 index 00000000..f080b7ff --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/fvSolution @@ -0,0 +1,84 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object fvSolution; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +solvers +{ + "alpha.*" + { + nAlphaCorr 1; + nAlphaSubCycles 2; + } + + p_rgh + { + solver GAMG; + smoother DIC; + tolerance 1e-8; + relTol 0; + } + + p_rghFinal + { + $p_rgh; + relTol 0; + } + + "U.*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-7; + relTol 0; + minIter 1; + } + + "e.*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-7; + relTol 0; + minIter 1; + } + + "(k|epsilon).*" + { + solver smoothSolver; + smoother symGaussSeidel; + tolerance 1e-7; + relTol 0; + minIter 1; + } +} + +PIMPLE +{ + nOuterCorrectors 3; + nCorrectors 1; + nNonOrthogonalCorrectors 0; + +} + +relaxationFactors +{ + equations + { + ".*" 1; + } +} + + +// ************************************************************************* // diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 b/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 new file mode 100644 index 00000000..e3e9ccdc --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 @@ -0,0 +1,110 @@ +//--------------------------------*- C++ -*---------------------------------- +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object blockMeshDict; +} +// ************************************ +changecom(//)changequote([,]) +define(calc, [esyscmd(perl -e 'printf ($1)')]) +define(calcint, [esyscmd(perl -e 'printf int($1)')]) +define(VCOUNT, 0) +define(vlabel, [[// ]Vertex $1 = VCOUNT define($1, VCOUNT)define([VCOUNT], incr(VCOUNT))]) + +convertToMeters 1; + +define(h, 0.02925) //wall-sparge distance +define(b, 0.005) // sparger diameter +define(L, 1.1176) // total lenght +define(D, 0.0635) // total depth +//define(H, 0.9144) //total height +define(H, 1.1) //total height +define(Lmh, calc(L-h)) +define(hpb, calc(h+b)) +define(NPX, 100) +define(NPZ, 10) +define(NPY, 100) + +vertices +( + ( 0 0 0 ) vlabel(block0_0) + ( L 0 0 ) vlabel(block0_1) + ( L H 0 ) vlabel(block0_2) + ( 0 H 0 ) vlabel(block0_3) + ( h 0 h ) vlabel(block0_4) + ( Lmh 0 h ) vlabel(block0_5) + ( Lmh H h ) vlabel(block0_6) + ( h H h ) vlabel(block0_7) + ( h 0 hpb) vlabel(block0_8) + ( Lmh 0 hpb) vlabel(block0_9) + ( Lmh H hpb) vlabel(block0_10) + ( h H hpb) vlabel(block0_11) + ( 0 0 D ) vlabel(block0_12) + ( L 0 D ) vlabel(block0_13) + ( L H D ) vlabel(block0_14) + ( 0 H D ) vlabel(block0_15) + +); + +blocks +( + //block 0 + hex ( block0_0 block0_1 block0_2 block0_3 block0_4 block0_5 block0_6 block0_7 ) (NPX NPY NPZ) simpleGrading (1 1 1) + //block 1 + hex ( block0_5 block0_1 block0_2 block0_6 block0_9 block0_13 block0_14 block0_10) ( NPZ NPY NPZ ) simpleGrading (1 1 1) + //block 2 + hex ( block0_8 block0_9 block0_10 block0_11 block0_12 block0_13 block0_14 block0_15) ( NPX NPY NPZ) simpleGrading (1 1 1) + //block 3 + hex ( block0_0 block0_4 block0_7 block0_3 block0_12 block0_8 block0_11 block0_15 ) ( NPZ NPY NPZ ) simpleGrading (1 1 1) + //block 4 + hex (block0_4 block0_5 block0_6 block0_7 block0_8 block0_9 block0_10 block0_11) ( NPX NPY NPZ ) simpleGrading (1 1 1) +); + +edges +( +); + +patches +( + patch inlet + ( + ( block0_4 block0_5 block0_9 block0_8 ) + ) + + patch outlet + ( + ( block0_3 block0_2 block0_6 block0_7 ) + ( block0_11 block0_10 block0_14 block0_15 ) + ( block0_3 block0_7 block0_11 block0_15 ) + ( block0_2 block0_14 block0_10 block0_6 ) + ( block0_7 block0_6 block0_10 block0_11) + ) + + wall wall_sides + ( + ( block0_0 block0_3 block0_15 block0_12 ) + ( block0_1 block0_13 block0_14 block0_2 ) + ) + + wall wall_frontback + ( + ( block0_12 block0_15 block0_14 block0_13 ) + ( block0_0 block0_1 block0_2 block0_3 ) + ) + + wall wall_botttom + ( + ( block0_0 block0_1 block0_5 block0_4 ) + ( block0_8 block0_9 block0_13 block0_12 ) + ( block0_0 block0_4 block0_8 block0_12 ) + ( block0_1 block0_13 block0_9 block0_5) + ) +); + +mergePatchPairs +( +); + diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/setFieldsDict b/tutorial_cases/FlatPanel_250L_ASU/system/setFieldsDict new file mode 100644 index 00000000..ce17d73c --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/system/setFieldsDict @@ -0,0 +1,37 @@ +/*--------------------------------*- C++ -*----------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | Website: https://openfoam.org + \\ / A nd | Version: 9 + \\/ M anipulation | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + format ascii; + class dictionary; + location "system"; + object setFieldsDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +defaultFieldValues +( + volScalarFieldValue alpha.gas 1 + volScalarFieldValue alpha.liquid 0 +); + +regions +( + boxToCell + { + box (0 0 0) (1.2 0.914 0.1); + fieldValues + ( + volScalarFieldValue alpha.gas 0 + volScalarFieldValue alpha.liquid 1 + ); + } +); + + +// ************************************************************************* // From 4706d99bcb9ef668a6ce74d1e018e2a289d24ab6 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 9 Jul 2025 16:33:30 -0600 Subject: [PATCH 57/86] adding ref and only run 1 timestep --- tutorial_cases/FlatPanel_250L_ASU/README.md | 10 ++++++++++ tutorial_cases/FlatPanel_250L_ASU/system/controlDict | 2 +- tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 | 6 +++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tutorial_cases/FlatPanel_250L_ASU/README.md b/tutorial_cases/FlatPanel_250L_ASU/README.md index 7ab83e55..c3c0d170 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/README.md +++ b/tutorial_cases/FlatPanel_250L_ASU/README.md @@ -1,5 +1,15 @@ ### Flat Panel reactor +``` +@inproceedings{parra2022fluid, + title={Fluid-Dynamic Simulation of a Flat-Panel Bioreactor with Emphasis on Mixing, Mass Transfer and Inorganic CO 2 Chemistry}, + author={Parra-Alvarez, John and Lua, Mauro and Laurens, Lieve and Sitaraman, Hari and Stickel, Jonathan and Mark, Heinnickel and Troy, Paddock}, + booktitle={2022 AIChE Annual Meeting}, + year={2022}, + organization={AIChE} +} +``` + Single core exec 1. `bash run.sh` diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/controlDict b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict index 1ae159fc..baa693c3 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/system/controlDict +++ b/tutorial_cases/FlatPanel_250L_ASU/system/controlDict @@ -20,7 +20,7 @@ startFrom startTime; startTime 0; -stopAt endTime; +stopAt writeNow;//endTime; endTime 100; diff --git a/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 b/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 index e3e9ccdc..ba92dc02 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 +++ b/tutorial_cases/FlatPanel_250L_ASU/system/panel.m4 @@ -24,9 +24,9 @@ define(D, 0.0635) // total depth define(H, 1.1) //total height define(Lmh, calc(L-h)) define(hpb, calc(h+b)) -define(NPX, 100) -define(NPZ, 10) -define(NPY, 100) +define(NPX, 50) +define(NPZ, 5) +define(NPY, 50) vertices ( From bdda423816416dac801576cf163cf4b6c45cbc07 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 11:56:08 -0600 Subject: [PATCH 58/86] add a better contributing guideline --- docs/source/contribute.rst | 31 +++++++++++++++++++--- tutorial_cases/runall.sh | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tutorial_cases/runall.sh diff --git a/docs/source/contribute.rst b/docs/source/contribute.rst index 54c1fd74..147e399c 100644 --- a/docs/source/contribute.rst +++ b/docs/source/contribute.rst @@ -1,7 +1,7 @@ Contributing ===== -We welcome pull requests from anybody! +We welcome pull requests from anyone! Formatting ------------ @@ -16,8 +16,33 @@ You can automatically enforce the formatting guidelines with bash fixFormat.sh -Test +Tests ------------ -Please ensure your contribution passes the tests in ``tests`` +Please ensure your contribution passes the tests in the CI (``.github/worklows/ci.yml``) +To run the unit tests +.. code-block:: console + + conda activate bird + pip install pytest + BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` + cd ${BIRD_HOME}/../ + pytest . + +To run the regression tests + +.. code-block:: console + + source /etc/rc + conda activate bird + pip install pytest + BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` + cd ${BIRD_HOME}/../tutorial_cases + bash run_all.sh + +Demonstrating and documenting your contribution +------------ +We prefer the use of docstrings and type hinting. A good example to follow are functions in ``bird/utilities/ofio.py``. + +If you add a new capability, please make sure to add relevant unit tests in the ``tests/`` folder. A good example to follow are tests ``tests/io`` diff --git a/tutorial_cases/runall.sh b/tutorial_cases/runall.sh new file mode 100644 index 00000000..c58bc65f --- /dev/null +++ b/tutorial_cases/runall.sh @@ -0,0 +1,53 @@ +#Compile solver + +conda activate bird +BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` +cd ${BIRD_HOME}/../OFsolvers/birdmultiphaseEulerFoam +export WM_COMPILE_OPTION=Debug +./Allwmake +cd ../../ + +# Run all tests + +## Run deckwer17 PBE +cd experimental_cases/deckwer17 +bash run.sh +cd ../../ +## Run deckwer17 constantD +cd experimental_cases/deckwer17 +cp constant/phaseProperties_constantd constant/phaseProperties +bash run.sh +cd ../../ +## Run deckwer19 PBE +cd experimental_cases/deckwer19 +bash run.sh +cd ../../ +## Run side sparger tutorial +cd tutorial_cases/side_sparger +bash run.sh +cd ../../ +## Run bubble column tutorial +cd tutorial_cases/bubble_column_20L +bash run.sh +cd ../../ +## Run stirred-tank tutorial +cd tutorial_cases/stirred_tank +bash run.sh +cd ../../ +## Run reactive loop reactor tutorial +cd tutorial_cases/loop_reactor_reacting +bash run.sh +cd ../../ +## Run mixing loop reactor tutorial +cd tutorial_cases/loop_reactor_mixing +bash run.sh +cd ../../ +## Run airlift reactor tutorial +cd tutorial_cases/airlift_40m +bash run.sh +cd ../../ +## Run flat panel reactor tutorial +cd tutorial_cases/FlatPanel_250L_ASU +bash run.sh +cd ../../ + From dc55a82466dde79b3172f7a4a515512ad792ba44 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 11:56:39 -0600 Subject: [PATCH 59/86] add description of python interface --- docs/source/index.rst | 1 + docs/source/python_interface.rst | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 docs/source/python_interface.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 7f315c91..ad8f278b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,6 +27,7 @@ We provide a solver ``birdmultiphaseEulerFoam`` that contains custom models adde meshing preprocess postprocess + python_interface tutorials contribute references diff --git a/docs/source/python_interface.rst b/docs/source/python_interface.rst new file mode 100644 index 00000000..9f721c50 --- /dev/null +++ b/docs/source/python_interface.rst @@ -0,0 +1,68 @@ +Python interface to OpenFOAM +===== + +We provide a simple python interface for reading OpenFOAM case results and dictionaries. +A more comprehensive interface is available through ``pyFoam`` although we found it difficult to use and without recent support, which motivated the implementation of a new python interface. + + +Read fields +------------ + +Internal scalar and vector fields +~~~~~~~~~~~~~~~~~~~~ + +Currently internal scalar and vector fields can be read using the python interface. In particular, note that +#. 1. We do not support reading tensor fields for now. +#. 2. We do not support reading boundary fields for now. +#. 3. We only read reconstructed files + +We are open to implementing 1. and 2. if need be. Implementing 3. is possible but will be more involved. + +The main function interface to read openFOAM fields is ``readOF`` in ``bird.utilities.ofio`` + +The function only reads fields written in ASCII format and decided based on the header whether the field is a scalar or a vector. In the case of scalar, ``readOF`` returns a (N,) numpy array, where N is the number of computational cells. In the case of a vector, ``readOF`` returns a (N,3) numpy array. + +If a uniform field is read, the number of cells may not be available from the field file and the function returns a float (equal to the uniform internal field value). If a numpy array is needed, the user can specify the number of cells in the field as an input. + +Reuse instead of re-read +~~~~~~~~~~~~~~~~~~~~ + +The ``read_field`` function uses ``readOF`` and takes a dictionary ``field_dict`` as input which is used to avoid reading multiple times the same field. For example, if one wants to compute the reactor-averaged concentration of a species, and then the reactor-averaged mass fraction of a species, the same mass fraction field will be used in both cases. As fields are read, ``field_dict`` will store the mass fraction field and recognize that the same field is needed. + +It is up to the user to reinitialize ``field_dict``. For example, if the reactor-averaged mass fraction needs to be computed at time ``1`` and then at time ``2``, the user needs to pass an empty dictionary (or nothing) to ``read_field`` before reading the fields at time ``2``. Otherwise, ``read_field`` will assume that the mass fraction field is the same between time ``1`` and time ``2``. + + +Read mesh-related fields +~~~~~~~~~~~~~~~~~~~~ + +We rely on OpenFOAM utilities to provide mesh-based fields. The results of the OpenFOAM utilities can still be processed using ``bird`` functions. + + +Reading cell volumes +^^^^^^^^^^^^^^^ + +A cell volume field can be generated using the following OpenFOAM command ``postProcess -func writeCellVolumes -time {time_folder} -case {case_folder}`` +It will generate a file ``{time_folder}/V`` which can be read with the ``readOF`` function of ``bird``. +This workflow is used in ``bird.postprocess.post_quantitities``, for example in the ``compute_gas_holdup`` function. + + +Reading cell centers +^^^^^^^^^^^^^^^ + +A mesh object file can be generated with the OpenFOAM command ``writeMeshObj -case {case_folder}`` +The file can then be read with the function ``readMesh`` from ``bird.utilities.ofio``. +Again, this is used in ``bird.postprocess.post_quantities`` in the ``compute_superficial_velocity`` function. + + + + +Read dictionaries +------------ + +We provide a function ``parse_openfoam_dict`` in ``bird.utilities.ofio`` that can parse OpenFOAM dictionaries. The function requires a lot of special characters handling but works for processing basic dictionaries needed to manage OpenFOAM cases (``controlDict``, ``setFieldsDict``, ``phaseProperties``, ``thermophysicalProperties``, ``momentumTransport``, ...) + + +Generate cases +------------ + +(to be added based on the reactor optimization work) From 483b731ce7f1aaca0ed52bdbdc59c8516f7213cc Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 11:57:32 -0600 Subject: [PATCH 60/86] fix display of python versions --- bird/version.py | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bird/version.py b/bird/version.py index 07d5c654..d01886b9 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.24" +__version__ = "0.0.25" diff --git a/setup.py b/setup.py index 3d32deab..d98a2918 100644 --- a/setup.py +++ b/setup.py @@ -29,8 +29,7 @@ "License :: OSI Approved :: BSD License", "Natural Language :: English", "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], package_data={ "": [ From 5b3edf8ef883cdcefe9bebae0c74bdd2bbf0cc96 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 15:12:57 -0600 Subject: [PATCH 61/86] remove useless hidden files --- .gitignore | 1 + .../.vim/.netrwhist | 3 --- .../.vim/.netrwhist | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist diff --git a/.gitignore b/.gitignore index 4e84ce28..75376bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,4 @@ dmypy.json *.stl *.swp +.vim diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist deleted file mode 100644 index b9691c3b..00000000 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist +++ /dev/null @@ -1,3 +0,0 @@ -let g:netrw_dirhistmax =10 -let g:netrw_dirhistcnt =1 -let g:netrw_dirhist_1='/home/openfoam/postProcessing/patchIntegrate(patch=inlet,field=alpha.gas)/0' diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist deleted file mode 100644 index b9691c3b..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/.vim/.netrwhist +++ /dev/null @@ -1,3 +0,0 @@ -let g:netrw_dirhistmax =10 -let g:netrw_dirhistcnt =1 -let g:netrw_dirhist_1='/home/openfoam/postProcessing/patchIntegrate(patch=inlet,field=alpha.gas)/0' From f605affdedad1a20e25c2a544d315b8e006648e5 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 15:17:15 -0600 Subject: [PATCH 62/86] remove old job script --- tutorial_cases/airlift_40m/submitjob | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 tutorial_cases/airlift_40m/submitjob diff --git a/tutorial_cases/airlift_40m/submitjob b/tutorial_cases/airlift_40m/submitjob deleted file mode 100644 index 9dffb4d0..00000000 --- a/tutorial_cases/airlift_40m/submitjob +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# #SBATCH --qos=high -#SBATCH --job-name=bio-airlift -#SBATCH --partition=standard -#SBATCH --nodes=4 -#SBATCH --ntasks-per-node=36 -#SBATCH --time=48:00:00 -#SBATCH --account=bpms -#SBATCH --output=log.out - -module purge -ml PrgEnv-cray -source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc - -module purge -module load openmpi/1.10.7/gcc-7.3.0 -module load gcc -source /projects/bpms/openfoam/OpenFOAM-dev/etc/bashrc -. ./presteps.sh -srun -n 144 birdmultiphaseEulerFoam -parallel From eb07db5ac4142ea7509590ef819b7924a9d562e3 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 15:22:45 -0600 Subject: [PATCH 63/86] remove duplicated case --- .../0.orig/CO2.gas | 47 -- .../0.orig/CO2.liquid | 42 - .../0.orig/H2.gas | 47 -- .../0.orig/H2.liquid | 42 - .../0.orig/N2.gas | 47 -- .../0.orig/T.gas | 44 -- .../0.orig/T.liquid | 43 - .../0.orig/U.gas | 47 -- .../0.orig/U.liquid | 46 -- .../0.orig/Ydefault.gas | 42 - .../0.orig/Ydefault.liquid | 42 - .../0.orig/alpha.gas | 43 - .../0.orig/alpha.liquid | 40 - .../0.orig/alphat.gas | 46 -- .../0.orig/alphat.liquid | 44 -- .../0.orig/epsilon.gas | 48 -- .../0.orig/epsilon.liquid | 43 - .../0.orig/f.gas | 41 - .../0.orig/k.gas | 43 - .../0.orig/k.liquid | 44 -- .../0.orig/nut.gas | 48 -- .../0.orig/nut.liquid | 43 - .../0.orig/p | 39 - .../0.orig/p_rgh | 43 - .../Allclean | 18 - .../computeQOI.sh | 13 - .../constant/dynamicMix_util.H | 37 - .../constant/fvModels | 128 --- .../constant/g | 21 - .../constant/globalVars | 83 -- .../constant/globalVars_temp | 83 -- .../constant/momentumTransport.gas | 26 - .../constant/momentumTransport.liquid | 27 - .../constant/phaseProperties | 295 ------- .../constant/phaseProperties_constantd | 261 ------ .../constant/phaseProperties_pbe | 295 ------- .../constant/thermophysicalProperties.gas | 142 ---- .../constant/thermophysicalProperties.liquid | 108 --- .../get_qoi.py | 183 ----- .../presteps.sh | 70 -- .../read_history.py | 243 ------ .../run.sh | 5 - .../script | 14 - .../script_post | 10 - .../system/blockMeshDict | 746 ------------------ .../system/controlDict | 67 -- .../system/decomposeParDict | 30 - .../system/fvConstraints | 56 -- .../system/fvSchemes | 70 -- .../system/fvSolution | 120 --- .../system/inlets_outlets.json | 28 - .../system/mesh.json | 26 - .../system/mixers.json | 29 - .../system/setFieldsDict | 37 - .../writeGlobalVars.py | 47 -- 55 files changed, 4372 deletions(-) delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh delete mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/fvModels delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh delete mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script delete mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict delete mode 100755 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict delete mode 100644 tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas deleted file mode 100644 index e4165b1a..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.gas +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object CO2.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -dimensions [0 0 0 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform 0; - - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform $f_CO2; - } - - outlet - { - //type inletOutlet; - //phi phi.gas; - //inletValue $f_CO2; - //value $f_CO2; - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid deleted file mode 100644 index 4b8ea6a0..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/CO2.liquid +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object CO2.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -internalField uniform 0.0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type zeroGradient; - //type fixedValue; - //value uniform 0.0; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas deleted file mode 100644 index 9f66b2d2..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.gas +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object H2.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -dimensions [0 0 0 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform 0; - - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform $f_H2; - } - - outlet - { - //type inletOutlet; - //phi phi.gas; - //inletValue $f_H2; - //value $f_H2; - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid deleted file mode 100644 index 65ae8d34..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/H2.liquid +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object H2.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -internalField uniform 0.0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type zeroGradient; - //type fixedValue; - //value uniform 0.0; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas deleted file mode 100644 index c1d7225f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/N2.gas +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object N2.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -dimensions [0 0 0 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform 1; - - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform $f_N2; - } - - outlet - { - //type inletOutlet; - //phi phi.gas; - //inletValue $f_N2; - //value $f_N2; - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas deleted file mode 100644 index bf0199a0..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.gas +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object T.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 1 0 0 0]; - -internalField uniform 300; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value $internalField; - } - - outlet - { - type inletOutlet; - phi phi.gas; - inletValue $internalField; - value $internalField; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid deleted file mode 100644 index 7101ea31..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/T.liquid +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object T.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 1 0 0 0]; - -internalField uniform 300; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - outlet - { - type inletOutlet; - phi phi.liquid; - inletValue $internalField; - value $internalField; - } - inlet - { - type fixedValue; - value $internalField; - } - defaultFaces - { - type zeroGradient; - } - -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas deleted file mode 100644 index e696566f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.gas +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volVectorField; - object U.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 1 -1 0 0 0 0]; - -internalField uniform (0 0.0 0); - -#include "${FOAM_CASE}/constant/globalVars" - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - //type flowRateInletVelocity; - //massFlowRate $mflowRateGas; - //rho thermo:rho.gas; - //value $internalField; - type fixedValue; - value uniform (0 $uGasPhase 0); - } - outlet - { - type pressureInletOutletVelocity; - phi phi.gas; - value $internalField; - } - defaultFaces - { - type slip; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid deleted file mode 100644 index 1879e020..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/U.liquid +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volVectorField; - object U.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 1 -1 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform (0 0 0); - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - //type flowRateInletVelocity; - //massFlowRate $mflowRateLiq; - //rho thermo:rho.liquid; - //value $internalField; - type fixedValue; - value uniform (0 0 0); - } - outlet - { - type noSlip; - } - defaultFaces - { - type noSlip; - } - -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas deleted file mode 100644 index fba2945d..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.gas +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object Ydefault.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -internalField uniform 0.0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform 0.0; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid deleted file mode 100644 index a5108564..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/Ydefault.liquid +++ /dev/null @@ -1,42 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object Ydefault.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -internalField uniform 1.0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform 1.0; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas deleted file mode 100644 index 1e303fbe..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.gas +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - location "0"; - object alpha.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform $alphaGas; - -boundaryField -{ - inlet - { - type fixedValue; - value uniform $alphaGas; - } - outlet - { - type inletOutlet; - phi phi.gas; - inletValue uniform 1; - value uniform 1; - } - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid deleted file mode 100644 index 5c92070b..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alpha.liquid +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object alpha.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform 1; - -boundaryField -{ - inlet - { - type fixedValue; - value uniform $alphaLiq; - } - outlet - { - type fixedValue; - value uniform 0; - } - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas deleted file mode 100644 index b867958f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.gas +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object alphat.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [1 -1 -1 0 0 0 0]; - -internalField uniform 0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type calculated; - value $internalField; - } - - outlet - { - type calculated; - value $internalField; - } - - defaultFaces - { - type calculated; - value $internalField; - //type compressible::alphatWallFunction; - //Prt 0.85; - //value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid deleted file mode 100644 index 2569c3ee..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/alphat.liquid +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object alphat.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [1 -1 -1 0 0 0 0]; - -internalField uniform 0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type calculated; - value $internalField; - } - - outlet - { - type calculated; - value $internalField; - } - - defaultFaces - { - type compressible::alphatWallFunction; - Prt 0.85; - value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas deleted file mode 100644 index 707a1cda..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.gas +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object epsilon.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -3 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform $eps_inlet_gas; - -boundaryField -{ - inlet - { - type fixedValue; - value uniform $eps_inlet_gas; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - //type epsilonWallFunction; - //value $internalField; - } - - // defaultFaces - // { - // type empty; - // } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid deleted file mode 100644 index 0a4236fd..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/epsilon.liquid +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object epsilon.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -3 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform $eps_inlet_liq; - -boundaryField -{ - inlet - { - type fixedValue; - value uniform $eps_inlet_liq; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type epsilonWallFunction; - value $internalField; - } - -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas deleted file mode 100644 index 76ee77a9..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/f.gas +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object f.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 0 0 0 0 0 0]; - -internalField uniform 1.0; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform 1.0; //$internalField; // - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas deleted file mode 100644 index 4a3d44ca..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.gas +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object k.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -2 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform $k_inlet_gas; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform $k_inlet_gas; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type zeroGradient; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid deleted file mode 100644 index cde8f6c1..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/k.liquid +++ /dev/null @@ -1,44 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object k.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -2 0 0 0 0]; - -#include "${FOAM_CASE}/constant/globalVars" - -internalField uniform $k_inlet_liq; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type fixedValue; - value uniform $k_inlet_liq; - } - - outlet - { - type zeroGradient; - } - - defaultFaces - { - type kqRWallFunction; - value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas deleted file mode 100644 index ba16dd4c..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.gas +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object nut.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -1 0 0 0 0]; - -internalField uniform 1e-8; - -boundaryField -{ - inlet - { - type calculated; - value $internalField; - } - - outlet - { - type calculated; - value $internalField; - } - - defaultFaces - { - //type nutkWallFunction; - //value $internalField; - type calculated; - value $internalField; - } - - // defaultFaces - // { - // type empty; - // } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid deleted file mode 100644 index 1442e07f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/nut.liquid +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object nut.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 2 -1 0 0 0 0]; - -internalField uniform 1e-4; - -boundaryField -{ - #includeEtc "caseDicts/setConstraintTypes" - - inlet - { - type calculated; - value $internalField; - } - - outlet - { - type calculated; - value $internalField; - } - - defaultFaces - { - type nutkWallFunction; - value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p deleted file mode 100644 index b3a295fb..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object p; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [1 -1 -2 0 0 0 0]; - -internalField uniform 101325; - -boundaryField -{ - inlet - { - type calculated; - value $internalField; - } - outlet - { - type calculated; - value $internalField; - } - defaultFaces - { - type calculated; - value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh deleted file mode 100644 index 88ee7d80..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/0.orig/p_rgh +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class volScalarField; - object p_rgh; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [1 -1 -2 0 0 0 0]; - -internalField uniform 101325; - -boundaryField -{ - inlet - { - type fixedFluxPressure; - value $internalField; - } - outlet - { - type prghTotalPressure; - p0 $internalField; - U U.gas; - phi phi.gas; - rho thermo:rho.gas; - value $internalField; - } - defaultFaces - { - type fixedFluxPressure; - value $internalField; - } -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean deleted file mode 100755 index f55e0ec9..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/Allclean +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -cd ${0%/*} || exit 1 # Run from this directory - -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions - -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase - -#rm *.obj -#rm *.stl - -#------------------------------------------------------------------------------ diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh deleted file mode 100644 index 3756ed7f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/computeQOI.sh +++ /dev/null @@ -1,13 +0,0 @@ -if [ ! -f qoi.txt ]; then - # Reconstruct if needed - source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc - reconstructPar -newTimes - module load anaconda3/2023 - conda activate /projects/gas2fuels/conda_env/bird - python read_history.py -cr .. -cn local -df data - python get_qoi.py - conda deactivate -else - echo "WARNING: QOI already computed" -fi - diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H deleted file mode 100644 index 8fa7daf7..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/dynamicMix_util.H +++ /dev/null @@ -1,37 +0,0 @@ -#include - -double gradfunMix(double V1, double V2){ - return 3.0*V2*V2 + 2.0*V1*V2 - V1*V1; -} - -double funMix(double V1, double V2, double P, double rhoL, double A){ - return V2*V2*V2 + V1*V2*V2 - V2*V1*V1 - V1*V1*V1 - 4.0*P/(rhoL * A); -} - -double findV2(double P, double rhoL, double A, double V1) { - int newton_iter = 100; - double V2 = 2*V1; - double V2_old; - double V2_new; - if (std::abs(V1) < 1e-12) { - V2=std::pow((4.0*P/rhoL/A),0.333333); - V2_new = V2; - V2_old = V2; - } else { - for (int i=0; i("thermo:rho.liquid"); - const volScalarField& alphaL = - mesh().lookupObject("alpha.liquid"); - const volVectorField& UL = - mesh().lookupObject("U.liquid"); - double pi=3.141592654; - double source_pt_x=13.807637692813548; - double source_pt_y=1.3807637692813548; - double source_pt_z=12.426873923532193; - double disk_rad=0.9665346384969483; - double disk_area=pi*disk_rad*disk_rad; - double power=7626.034346240216; - double smear_factor=3.0; - const scalar startTime = 1; - if (time.value() > startTime) - { - // Get V1 - double source_sign_factor = 1.0; - double V1 = 0; - double V2 = 0; - double rhoV; - double dist_tol = disk_rad*5; - - double dist_n; - double upV = 0; - double uprhoV = 0; - double upVvol = 0; - double downV = 0; - double downrhoV = 0; - double downVvol = 0; - double dist2; - forAll(C,i) - { - dist2 = (C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); - dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); - dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); - - dist_n = (C[i].x()-source_pt_x); - - if (dist2 < dist_tol*dist_tol && dist_n < -dist_tol/2) { - upVvol += V[i] * alphaL[i]; - upV += V[i] * alphaL[i] * UL[i][0]; - uprhoV += V[i] * alphaL[i] * rhoL[i]; - } - if (dist2 < dist_tol*dist_tol && dist_n > dist_tol/2) { - downVvol += V[i] * alphaL[i]; - downV += V[i] * alphaL[i] * UL[i][0]; - downrhoV += V[i] * alphaL[i] * rhoL[i]; - } - } - - reduce(uprhoV, sumOp()); - reduce(downrhoV, sumOp()); - reduce(upV, sumOp()); - reduce(downV, sumOp()); - reduce(downVvol, sumOp()); - reduce(upVvol, sumOp()); - - downV /= downVvol; - upV /= upVvol; - downrhoV /= downVvol; - uprhoV /= upVvol; - - if (upV <= 0 && downV <= 0) { - source_sign_factor = -1.0; - V1 = std::abs(upV); - rhoV = uprhoV; - } else if (upV >= 0 && downV >= 0) { - source_sign_factor = 1.0; - V1 = std::abs(downV); - rhoV = downrhoV; - } else { - V1 = 0.0; - source_sign_factor = -1.0; - rhoV = uprhoV; - Foam::Info << "[BIRD:DYNMIX WARN] " << "upV = " << upV << " downV = " << downV << " for source at " << source_pt_x << ", " << source_pt_y << ", " << source_pt_z << endl; - } - Foam::Info << "[BIRD:DYNMIX INFO] V1 = " << V1 << endl; - - // Get V2 - V2 = findV2(power, rhoV, disk_area, V1); - - forAll(C,i) - { - double Thrust=0.5*rhoL[i]*(V2*V2 - V1*V1)*disk_area; - double dist2=(C[i].x()-source_pt_x)*(C[i].x()-source_pt_x); - dist2 += (C[i].y()-source_pt_y)*(C[i].y()-source_pt_y); - dist2 += (C[i].z()-source_pt_z)*(C[i].z()-source_pt_z); - double epsilon=pow(V[i],0.33333)*smear_factor; - double sourceterm=alphaL[i]*(Thrust/pow(pi,1.5)/pow(epsilon,3.0))* - exp(-dist2/(epsilon*epsilon)); - Usource[i][0] -= source_sign_factor*sourceterm*V[i]; - } - } - #}; -}; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g deleted file mode 100644 index 770a5619..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/g +++ /dev/null @@ -1,21 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class uniformDimensionedVectorField; - location "constant"; - object g; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -dimensions [0 1 -2 0 0 0 0]; -value (0 -9.81 0); - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars deleted file mode 100644 index 1a0ce724..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars +++ /dev/null @@ -1,83 +0,0 @@ -T0 300; //initial T(K) which stays constant -VVM 1.6; -//****water Liquid properties************** -CpMixLiq 4181; -muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) -kThermLiq 0.62; // W/m-K -rho0MixLiq 1000; // kg/m^3 -sigmaLiq 0.07; //surface tension N/m -//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) -WC_psi 2.6; -WC_M 18; // kg/kmol -WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) -WC_V_H2 14.3e-3; -WC_V_CO2 34e-3; -WC_V_CO 30.7e-3; -WC_V_N2 31.2e-3; -WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) -//****** diffusion coeff *********** -D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; -D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; -D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; -D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; -D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; -//****** Henry coeff *************** -H_O2_298 0.032; -DH_O2 1700; -H_CO2_298 0.83; -DH_CO2 2400; -H_CO_298 0.023; -DH_CO 1300; -H_H2_298 0.019; -DH_H2 500; -H_CH4_298 0.032; -DH_CH4 1900; -H_N2_298 0.015; -DH_N2 1300; -He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; -He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; -He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; -He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; -He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; -//*******inlet gas frac************* -f_H2 0.1; -f_CO2 0.9; -f_N2 0.0; -//*******inlet gas frac************* -inletA 11.8966; -liqVol 606.514; -alphaGas 1; -alphaLiq 0; -uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; -//********************************* -LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; -LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; -LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 -LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; -LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; -LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; -PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; -//********************************* -kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; - -kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; - -kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; - -kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; - -kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; -//********************************* -l_scale 0.5; -intensity 0.05; -k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; -k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; -eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; -eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; -omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; -omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp deleted file mode 100644 index fcb076a7..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/globalVars_temp +++ /dev/null @@ -1,83 +0,0 @@ -T0 300; //initial T(K) which stays constant -VVM 0.2; -//****water Liquid properties************** -CpMixLiq 4181; -muMixLiq #calc "2.414e-5 * pow(10,247.8/($T0 - 140.0))"; //viscosity (Pa.s) of water as a function of T(K) -kThermLiq 0.62; // W/m-K -rho0MixLiq 1000; // kg/m^3 -sigmaLiq 0.07; //surface tension N/m -//Wilke-Chang params for diffusion coefficient of a given solute in water (solvent) -WC_psi 2.6; -WC_M 18; // kg/kmol -WC_V_O2 25.6e-3; // m3/kmol molar volume at normal boiling temperature (Treybal 1968) -WC_V_H2 14.3e-3; -WC_V_CO2 34e-3; -WC_V_CO 30.7e-3; -WC_V_N2 31.2e-3; -WC_V_CH4 35e-3; // V_b[cm3/mol]=0.285*V_critical^1.048 (Tyn and Calus; ESTIMATING LIQUID MOLAL VOLUME; Processing, Volume 21, Issue 4, Pages 16 - 17) -//****** diffusion coeff *********** -D_H2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_H2,0.6)"; -D_CO2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO2,0.6)"; -D_CO #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CO,0.6)"; -D_CH4 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_CH4,0.6)"; -D_N2 #calc "1.173e-16 * pow($WC_psi * $WC_M,0.5) * $T0 / $muMixLiq / pow($WC_V_N2,0.6)"; -//****** Henry coeff *************** -H_O2_298 0.032; -DH_O2 1700; -H_CO2_298 0.83; -DH_CO2 2400; -H_CO_298 0.023; -DH_CO 1300; -H_H2_298 0.019; -DH_H2 500; -H_CH4_298 0.032; -DH_CH4 1900; -H_N2_298 0.015; -DH_N2 1300; -He_H2 #calc "$H_H2_298 * exp($DH_H2 *(1. / $T0 - 1./298.15))"; -He_CO #calc "$H_CO_298 * exp($DH_CO *(1. / $T0 - 1./298.15))"; -He_CO2 #calc "$H_CO2_298 * exp($DH_CO2 *(1. / $T0 - 1./298.15))"; -He_CH4 #calc "$H_CH4_298 * exp($DH_CH4 *(1. / $T0 - 1./298.15))"; -He_N2 #calc "$H_N2_298 * exp($DH_N2 *(1. / $T0 - 1./298.15))"; -//*******inlet gas frac************* -f_H2 0.1; -f_CO2 0.9; -f_N2 0.0; -//*******inlet gas frac************* -inletA ; -liqVol ; -alphaGas 1; -alphaLiq 0; -uGasPhase #calc "$liqVol * $VVM / (60 * $inletA * $alphaGas)"; -//********************************* -LeLiqH2 #calc "$kThermLiq / $rho0MixLiq / $D_H2 / $CpMixLiq"; -LeLiqCO #calc "$kThermLiq / $rho0MixLiq / $D_CO / $CpMixLiq"; -LeLiqCO2 #calc "$kThermLiq / $rho0MixLiq / $D_CO2 / $CpMixLiq"; // = 74 -LeLiqCH4 #calc "$kThermLiq / $rho0MixLiq / $D_CH4 / $CpMixLiq"; -LeLiqN2 #calc "$kThermLiq / $rho0MixLiq / $D_N2 / $CpMixLiq"; -LeLiqMix #calc "$f_CO2*$LeLiqCO2+$f_H2*$LeLiqH2"; -PrMixLiq #calc "$CpMixLiq * $muMixLiq / $kThermLiq"; -//********************************* -kH2 #calc "$D_H2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrH2 #calc "$muMixLiq*$CpMixLiq / $kH2"; - -kCO #calc "$D_CO*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCO #calc "$muMixLiq*$CpMixLiq / $kCO"; - -kCO2 #calc "$D_CO2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCO2 #calc "$muMixLiq*$CpMixLiq / $kCO2"; - -kCH4 #calc "$D_CH4*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrCH4 #calc "$muMixLiq*$CpMixLiq / $kCH4"; - -kN2 #calc "$D_N2*$rho0MixLiq*$CpMixLiq*$LeLiqMix"; -PrN2 #calc "$muMixLiq*$CpMixLiq / $kN2"; -//********************************* -l_scale 0.5; -intensity 0.05; -k_inlet_gas #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; -k_inlet_liq #calc "1.5 * Foam::pow(($uGasPhase), 2) * Foam::pow($intensity, 2)"; -eps_inlet_gas #calc "pow(0.09,0.75) * Foam::pow($k_inlet_gas, 1.5) / ($l_scale * 0.07)"; -eps_inlet_liq #calc "pow(0.09,0.75) * Foam::pow($k_inlet_liq, 1.5) / ($l_scale * 0.07)"; -omega_inlet_gas #calc "pow(0.09,-0.25) * pow($k_inlet_gas,0.5) / ($l_scale * 0.07)"; -omega_inlet_liq #calc "pow(0.09,-0.25) * pow($k_inlet_liq,0.5) / ($l_scale * 0.07)"; diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas deleted file mode 100644 index ca916714..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.gas +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "constant"; - object momentumTransport.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -//simulationType laminar; -simulationType RAS; -RAS -{ - model mixtureKEpsilon; - turbulence on; - printCoeff on; -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid deleted file mode 100644 index 2063de0d..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/momentumTransport.liquid +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "constant"; - object momentumTransport.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -//simulationType laminar; -simulationType RAS; - -RAS -{ - model mixtureKEpsilon; - turbulence on; - printCoeffs on; -} - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties deleted file mode 100644 index a3c90f5a..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties +++ /dev/null @@ -1,295 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - object phaseProperties; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#include "$FOAM_CASE/constant/globalVars" - -type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; - -phases (gas liquid); - -populationBalances (bubbles); - -gas -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel velocityGroup; - - velocityGroupCoeffs - { - populationBalance bubbles; - - shapeModel spherical; - - sizeGroups - ( - f1 {dSph 1.4e-3; value 0.0;} - f2 {dSph 1.8e-3; value 0.0;} - f3 {dSph 2.2e-3; value 0.0;} - f4 {dSph 2.6e-3; value 0.0;} - f5 {dSph 3e-3; value 1.0;} - f6 {dSph 3.4e-3; value 0.0;} - f7 {dSph 3.8e-3; value 0.0;} - f8 {dSph 4.2e-3; value 0.0;} - f9 {dSph 4.6e-3; value 0.0;} - f10 {dSph 5.0e-3; value 0.0;} - ); - } - - residualAlpha 1e-6; - - Sc 0.7; -} - -liquid -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel constant; - - constantCoeffs - { - d 1e-4; - } - Sc #codeStream - { - code - #{ - os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); - #}; - }; - - residualAlpha 1e-6; -} - -populationBalanceCoeffs -{ - bubbles - { - continuousPhase liquid; - - coalescenceModels - ( - LehrMilliesMewes{ - efficiency 4.695; - uCrit 0.08; - alphaMax 0.6; - } - ); - - binaryBreakupModels - (); - - breakupModels - ( - Laakkonen { - efficiency 13.83; - daughterSizeDistributionModel Laakkonen; - } - - ); - - driftModels - ( - densityChange{} - ); - - nucleationModels - (); - } -} - -blending -{ - default - { - type linear; - minFullyContinuousAlpha.gas 0.7; - minPartlyContinuousAlpha.gas 0.3; - minFullyContinuousAlpha.liquid 0.7; - minPartlyContinuousAlpha.liquid 0.3; - } - heatTransfer - { - type linear; - minFullyContinuousAlpha.gas 1; - minPartlyContinuousAlpha.gas 0; - minFullyContinuousAlpha.liquid 1; - minPartlyContinuousAlpha.liquid 0; - } - massTransfer - { - $heatTransfer; - } -} - -surfaceTension -( - (gas and liquid) - { - type constant; - sigma $sigmaLiq; - } -); - -interfaceCompression -(); - -aspectRatio -( - (gas in liquid) - { - type Wellek; - } -); - - -drag -( - (gas in liquid) - { - type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type IshiiZuber; - residualRe 1e-3; - swarmCorrection - { - type none; - } - } -); - -virtualMass -( - (gas in liquid) - { - type constantCoefficient; - Cvm 0.5; - } -); - -// heatTransfer -// (); - -heatTransfer.gas -( - (gas in liquid) - { - type spherical; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type RanzMarshall; - residualAlpha 1e-4; - } -); - -heatTransfer.liquid -( - (gas in liquid) - { - type RanzMarshall; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type spherical; - residualAlpha 1e-4; - } -); - -interfaceComposition.gas -(); - -interfaceComposition.liquid -( - (liquid and gas) - { - type Henry; - species ( CO2 H2 ); - k ( $He_CO2 $He_H2 ); - Le $LeLiqMix; - } -); - -diffusiveMassTransfer.gas -(); - -diffusiveMassTransfer.liquid -( - (gas in liquid) - { - type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type Frossling; - Le $LeLiqMix; - } - - (liquid in gas) - { - type spherical; - Le 1.0; //not used for spherical - } -); - -phaseTransfer -(); - -lift -( - (gas in liquid) - { - type wallDamped; - - wallDamping - { - type cosine; - Cd 3.0; - } - - lift - { - type Tomiyama; - - swarmCorrection - { - type none; - } - } - } - -); - -wallLubrication -( - (gas in liquid) - { - type Antal; - Cw1 -0.01; - Cw2 0.05; - } -); - -turbulentDispersion -( - (gas in liquid) - { - type Burns; - sigma 0.9; - } -); - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd deleted file mode 100644 index e029df99..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_constantd +++ /dev/null @@ -1,261 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - object phaseProperties; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#include "$FOAM_CASE/constant/globalVars" - -type interfaceCompositionPhaseChangeMultiphaseSystem; - -phases (gas liquid); - -gas -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel constant; - - constantCoeffs - { - d 3e-3; - } - residualAlpha 1e-6; - Sc 0.7; -} - -liquid -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel constant; - - constantCoeffs - { - d 1e-4; - } - Sc #codeStream - { - code - #{ - os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); - #}; - }; - - residualAlpha 1e-6; -} - -populationBalanceCoeffs -{ - bubbles - { - continuousPhase liquid; - - coalescenceModels - (); - - binaryBreakupModels - (); - - breakupModels - (); - - driftModels - (); - - nucleationModels - (); - } -} - -blending -{ - default - { - type linear; - minFullyContinuousAlpha.gas 0.7; - minPartlyContinuousAlpha.gas 0.3; - minFullyContinuousAlpha.liquid 0.7; - minPartlyContinuousAlpha.liquid 0.3; - } - heatTransfer - { - type linear; - minFullyContinuousAlpha.gas 1; - minPartlyContinuousAlpha.gas 0; - minFullyContinuousAlpha.liquid 1; - minPartlyContinuousAlpha.liquid 0; - } - massTransfer - { - $heatTransfer; - } -} - -surfaceTension -( - (gas and liquid) - { - type constant; - sigma $sigmaLiq; - } -); - -interfaceCompression -(); - -aspectRatio -( - (gas in liquid) - { - type Wellek; - } -); - - -drag -( - (gas in liquid) - { - type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type IshiiZuber; - residualRe 1e-3; - swarmCorrection - { - type none; - } - } -); - -virtualMass -( - (gas in liquid) - { - type constantCoefficient; - Cvm 0.5; - } -); - -// heatTransfer -// (); - -heatTransfer.gas -( - (gas in liquid) - { - type spherical; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type RanzMarshall; - residualAlpha 1e-4; - } -); - -heatTransfer.liquid -( - (gas in liquid) - { - type RanzMarshall; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type spherical; - residualAlpha 1e-4; - } -); - -interfaceComposition.gas -(); - -interfaceComposition.liquid -( - (liquid and gas) - { - type Henry; - species ( CO2 H2 ); - k ( $He_CO2 $He_H2 ); - Le $LeLiqMix; - } -); - -diffusiveMassTransfer.gas -(); - -diffusiveMassTransfer.liquid -( - (gas in liquid) - { - type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type Frossling; - Le $LeLiqMix; - } - - (liquid in gas) - { - type spherical; - Le 1.0; //not used for spherical - } -); - -phaseTransfer -(); - -lift -( - (gas in liquid) - { - type wallDamped; - - wallDamping - { - type cosine; - Cd 3.0; - } - - lift - { - type Tomiyama; - - swarmCorrection - { - type none; - } - } - } - -); - -wallLubrication -( - (gas in liquid) - { - type Antal; - Cw1 -0.01; - Cw2 0.05; - } -); - -turbulentDispersion -( - (gas in liquid) - { - type Burns; - sigma 0.9; - } -); - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe deleted file mode 100644 index a3c90f5a..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/phaseProperties_pbe +++ /dev/null @@ -1,295 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: 9 - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - object phaseProperties; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#include "$FOAM_CASE/constant/globalVars" - -type interfaceCompositionPhaseChangePopulationBalanceMultiphaseSystem; - -phases (gas liquid); - -populationBalances (bubbles); - -gas -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel velocityGroup; - - velocityGroupCoeffs - { - populationBalance bubbles; - - shapeModel spherical; - - sizeGroups - ( - f1 {dSph 1.4e-3; value 0.0;} - f2 {dSph 1.8e-3; value 0.0;} - f3 {dSph 2.2e-3; value 0.0;} - f4 {dSph 2.6e-3; value 0.0;} - f5 {dSph 3e-3; value 1.0;} - f6 {dSph 3.4e-3; value 0.0;} - f7 {dSph 3.8e-3; value 0.0;} - f8 {dSph 4.2e-3; value 0.0;} - f9 {dSph 4.6e-3; value 0.0;} - f10 {dSph 5.0e-3; value 0.0;} - ); - } - - residualAlpha 1e-6; - - Sc 0.7; -} - -liquid -{ - type multiComponentPhaseModel;//pureIsothermalPhaseModel; - - diameterModel constant; - - constantCoeffs - { - d 1e-4; - } - Sc #codeStream - { - code - #{ - os << ($LeLiqMix * $CpMixLiq * $muMixLiq / $kThermLiq); - #}; - }; - - residualAlpha 1e-6; -} - -populationBalanceCoeffs -{ - bubbles - { - continuousPhase liquid; - - coalescenceModels - ( - LehrMilliesMewes{ - efficiency 4.695; - uCrit 0.08; - alphaMax 0.6; - } - ); - - binaryBreakupModels - (); - - breakupModels - ( - Laakkonen { - efficiency 13.83; - daughterSizeDistributionModel Laakkonen; - } - - ); - - driftModels - ( - densityChange{} - ); - - nucleationModels - (); - } -} - -blending -{ - default - { - type linear; - minFullyContinuousAlpha.gas 0.7; - minPartlyContinuousAlpha.gas 0.3; - minFullyContinuousAlpha.liquid 0.7; - minPartlyContinuousAlpha.liquid 0.3; - } - heatTransfer - { - type linear; - minFullyContinuousAlpha.gas 1; - minPartlyContinuousAlpha.gas 0; - minFullyContinuousAlpha.liquid 1; - minPartlyContinuousAlpha.liquid 0; - } - massTransfer - { - $heatTransfer; - } -} - -surfaceTension -( - (gas and liquid) - { - type constant; - sigma $sigmaLiq; - } -); - -interfaceCompression -(); - -aspectRatio -( - (gas in liquid) - { - type Wellek; - } -); - - -drag -( - (gas in liquid) - { - type Grace; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type IshiiZuber; - residualRe 1e-3; - swarmCorrection - { - type none; - } - } -); - -virtualMass -( - (gas in liquid) - { - type constantCoefficient; - Cvm 0.5; - } -); - -// heatTransfer -// (); - -heatTransfer.gas -( - (gas in liquid) - { - type spherical; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type RanzMarshall; - residualAlpha 1e-4; - } -); - -heatTransfer.liquid -( - (gas in liquid) - { - type RanzMarshall; - residualAlpha 1e-4; - } - - (liquid in gas) - { - type spherical; - residualAlpha 1e-4; - } -); - -interfaceComposition.gas -(); - -interfaceComposition.liquid -( - (liquid and gas) - { - type Henry; - species ( CO2 H2 ); - k ( $He_CO2 $He_H2 ); - Le $LeLiqMix; - } -); - -diffusiveMassTransfer.gas -(); - -diffusiveMassTransfer.liquid -( - (gas in liquid) - { - type Higbie; // Need to install the model available at https://github.com/NREL/BioReactorDesign - //type Frossling; - Le $LeLiqMix; - } - - (liquid in gas) - { - type spherical; - Le 1.0; //not used for spherical - } -); - -phaseTransfer -(); - -lift -( - (gas in liquid) - { - type wallDamped; - - wallDamping - { - type cosine; - Cd 3.0; - } - - lift - { - type Tomiyama; - - swarmCorrection - { - type none; - } - } - } - -); - -wallLubrication -( - (gas in liquid) - { - type Antal; - Cw1 -0.01; - Cw2 0.05; - } -); - -turbulentDispersion -( - (gas in liquid) - { - type Burns; - sigma 0.9; - } -); - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas deleted file mode 100644 index 11b1c4b9..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.gas +++ /dev/null @@ -1,142 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "constant"; - object thermophysicalProperties.gas; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -thermoType -{ - type heRhoThermo; - mixture multiComponentMixture; - transport sutherland; - thermo janaf; - equationOfState perfectGas; - specie specie; - energy sensibleInternalEnergy; - //energy sensibleEnthalpy; -} - - -species -( - H2 - CO2 - N2 -); - -defaultSpecie N2; - -CO2 -{ - specie - { - molWeight 44.00995; - } - thermodynamics - { - Tlow 200; - Thigh 3500; - Tcommon 1000; - highCpCoeffs ( 3.85746029 0.00441437026 -2.21481404e-06 5.23490188e-10 -4.72084164e-14 -48759.166 2.27163806 ); - lowCpCoeffs ( 2.35677352 0.00898459677 -7.12356269e-06 2.45919022e-09 -1.43699548e-13 -48371.9697 9.90105222 ); - } - transport - { - As 1.572e-06; - Ts 240; - } - elements - { - C 1; - O 2; - } -} - -water -{ - specie - { - molWeight 18.01534; - } - thermodynamics - { - Tlow 200; - Thigh 3500; - Tcommon 1000; - highCpCoeffs ( 3.03399249 0.00217691804 -1.64072518e-07 -9.7041987e-11 1.68200992e-14 -30004.2971 4.9667701 ); - lowCpCoeffs ( 4.19864056 -0.0020364341 6.52040211e-06 -5.48797062e-09 1.77197817e-12 -30293.7267 -0.849032208 ); - } - transport - { - As 1.512e-06; - Ts 120; - } - elements - { - H 2; - O 1; - } -} - -N2 -{ - specie - { - molWeight 28.0134; - } - thermodynamics - { - Tlow 250; - Thigh 5000; - Tcommon 1000; - highCpCoeffs ( 2.92664 0.0014879768 -5.68476e-07 1.0097038e-10 -6.753351e-15 -922.7977 5.980528 ); - lowCpCoeffs ( 3.298677 0.0014082404 -3.963222e-06 5.641515e-09 -2.444854e-12 -1020.8999 3.950372 ); - } - transport - { - As 1.512e-06; - Ts 120; - } - elements - { - N 2; - } -} - -H2 -{ - specie - { - molWeight 2.01594; - } - thermodynamics - { - Tlow 200; - Thigh 3500; - Tcommon 1000; - highCpCoeffs ( 3.3372792 -4.94024731e-05 4.99456778e-07 -1.79566394e-10 2.00255376e-14 -950.158922 -3.20502331 ); - lowCpCoeffs ( 2.34433112 0.00798052075 -1.9478151e-05 2.01572094e-08 -7.37611761e-12 -917.935173 0.683010238 ); - } - transport - { - As 6.362e-07; - Ts 72; - } - elements - { - H 2; - } -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid deleted file mode 100644 index d324ec51..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/constant/thermophysicalProperties.liquid +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "constant"; - object thermophysicalProperties.liquid; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#include "$FOAM_CASE/constant/globalVars" - -thermoType -{ - type heRhoThermo; - mixture multiComponentMixture; - transport const; - thermo hConst; - equationOfState rhoConst;//rPolynomial; - specie specie; - energy sensibleInternalEnergy; - //energy sensibleEnthalpy; -} - -species -( - CO2 - water - H2 -); - -inertSpecie water; - -water -{ - specie - { - molWeight 18.0153; - } - equationOfState - { - rho $rho0MixLiq; - } - thermodynamics - { - Cp $CpMixLiq; - Hf -1.5879e+07; - } - transport - { - mu $muMixLiq; - Pr $PrMixLiq; - } -} - -CO2 -{ - specie - { - molWeight 44.00995; - } - equationOfState - { - rho $rho0MixLiq; - } - thermodynamics - { - Cp $CpMixLiq; - Hf -1.5879e+07; - } - transport - { - mu $muMixLiq; - Pr $PrCO2; - } -} - -H2 -{ - specie - { - molWeight 2.01594; - } - equationOfState - { - rho $rho0MixLiq; - } - thermodynamics - { - Cp $CpMixLiq; - Hf -1.5879e+07;//-9402451; - } - transport - { - mu $muMixLiq; - Pr $PrH2; - } -} - - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py deleted file mode 100644 index 83932c5b..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/get_qoi.py +++ /dev/null @@ -1,183 +0,0 @@ -import json -import os -import pickle as pkl - -import matplotlib as mpl -import numpy as np -from prettyPlot.plotting import * -from scipy.optimize import curve_fit - - -def get_sim_folds(path): - folds = os.listdir(path) - sim_folds = [] - for fold in folds: - if fold.startswith("loop"): - sim_folds.append(fold) - return sim_folds - - -def func(t, cstar, kla): - t = t - t0 = 0 - c0 = 0 - return (cstar - c0) * (1 - np.exp(-kla * (t - t0))) + c0 - - -def get_vl(verb=False): - filename = os.path.join("constant", "globalVars") - with open(filename, "r+") as f: - lines = f.readlines() - for line in lines: - if line.startswith("liqVol"): - vol = float(line.split()[-1][:-1]) - break - if verb: - print(f"Read liqVol = {vol}m3") - return vol - - -def get_vvm(verb=False): - filename = os.path.join("constant", "globalVars") - with open(filename, "r+") as f: - lines = f.readlines() - for line in lines: - if line.startswith("VVM"): - vvm = float(line.split()[-1][:-1]) - break - if verb: - print(f"Read VVM = {vvm} [-]") - return vvm - - -def get_As(verb=False): - filename = os.path.join("constant", "globalVars") - with open(filename, "r+") as f: - lines = f.readlines() - for line in lines: - if line.startswith("inletA"): - As = float(line.split()[-1][:-1]) - break - if verb: - print(f"Read As = {As}m2") - return As - - -def get_pmix(verb=False): - with open("system/mixers.json", "r+") as f: - data = json.load(f) - mixer_list = data["mixers"] - pmix = 0 - for mix in mixer_list: - pmix += mix["power"] / 1000 - if verb: - print(f"Read Mixing power = {pmix}kW") - return pmix - - -def get_lh(verb=False): - filename = os.path.join("system", "setFieldsDict") - with open(filename, "r+") as f: - lines = f.readlines() - for line in lines: - if "box (-1.0 -1.0 -1.0)" in line: - height = float(line.split("(")[2].split()[1]) - break - if verb: - print(f"Read Height = {height}m") - return height - - -def get_pinj(vvm, Vl, As, lh): - rhog = 1.25 # kg /m3 - Vg = Vl * vvm / (60 * As * 1) # m/s - Ptank = 101325 # Pa - # Ptank = 0 # Pa - rhoL = 1000 # kg / m3 - Pl = 101325 + rhoL * 9.8 * lh # Pa - # W - P1 = rhog * As * Vg**3 - # W - P2 = (Pl - Ptank) * As * Vg - # kg /s - MF = rhog * Vg * As - # kwh / kg - e_m = (P1 + P2) / (3600 * 1000 * MF) - - # returns kW - return (P1 + P2) * 1e-3 - - -def get_qoi(kla_co2, cs_co2, kla_h2, cs_h2, verb=False): - vvm = get_vvm(verb) - As = get_As(verb) - V_l = get_vl(verb) - liqh = get_lh(verb) - P_inj = get_pinj(vvm, V_l, As, liqh) - P_mix = get_pmix(verb) - - qoi_co2 = kla_co2 * cs_co2 * V_l * 0.04401 / (P_mix / 3600 + P_inj / 3600) - qoi_h2 = kla_h2 * cs_h2 * V_l * 0.002016 / (P_mix / 3600 + P_inj / 3600) - return qoi_co2 * qoi_h2 - - -def get_qoi_uq(kla_co2, cs_co2, kla_h2, cs_h2): - qoi = [] - for i in range(len(kla_co2)): - if i == 0: - verb = True - else: - verb = False - qoi.append(get_qoi(kla_co2[i], cs_co2[i], kla_h2[i], cs_h2[i], verb)) - qoi = np.array(qoi) - return np.mean(qoi), np.std(qoi) - - -os.makedirs("Figures", exist_ok=True) - -dataFolder = "data" -fold = "local" - -nuq = 100 -mean_cstar_co2 = np.random.uniform(14, 16.9, nuq) -mean_cstar_h2 = np.random.uniform(1.04, 1.19, nuq) - - -tmp_cs_h2 = [] -tmp_cs_co2 = [] -tmp_kla_h2 = [] -tmp_kla_co2 = [] -cs_co2 = mean_cstar_co2 -cs_h2 = mean_cstar_h2 - -a = np.load(os.path.join(dataFolder, fold, "conv.npz")) -endindex = -1 -if ( - "c_h2" in a - and "c_co2" in a - and len(a["time"][:endindex] > 0) - and (a["time"][:endindex][-1] > 95) -): - for i in range(nuq): - fitparamsH2, _ = curve_fit( - func, - np.array(a["time"][:endindex]), - np.array(a["c_h2"][:endindex]), - bounds=[(cs_h2[i] - 1e-6, 0), (cs_h2[i] + 1e-6, 1)], - ) - fitparamsCO2, _ = curve_fit( - func, - np.array(a["time"][:endindex]), - np.array(a["c_co2"][:endindex]), - bounds=[(cs_co2[i] - 1e-6, 0), (cs_co2[i] + 1e-6, 1)], - ) - tmp_kla_co2.append(fitparamsCO2[1]) - tmp_kla_h2.append(fitparamsH2[1]) - tmp_cs_h2.append(cs_h2[i]) - tmp_cs_co2.append(cs_co2[i]) - -qoi_m, qoi_s = get_qoi_uq(tmp_kla_co2, tmp_cs_co2, tmp_kla_h2, tmp_cs_h2) - - -with open("qoi.txt", "w+") as f: - f.write(f"{qoi_m},{qoi_s}\n") diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh deleted file mode 100644 index 77fa6d6f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/presteps.sh +++ /dev/null @@ -1,70 +0,0 @@ -# Clean case -module load conda -conda activate /projects/gas2fuels/conda_env/bird_wf -source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc -./Allclean - -BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` - -echo PRESTEP 1 -# Generate blockmeshDict -python ${BIRD_HOME}/../applications/write_block_rect_mesh.py -i system/mesh.json -o system - -# Generate boundary stl -python ${BIRD_HOME}/../applications/write_stl_patch.py -i system/inlets_outlets.json - -# Generate mixers -python ${BIRD_HOME}/../applications/write_dynMix_fvModels.py -fs -i system/mixers.json -o constant - -echo PRESTEP 2 -# Mesh gen -blockMesh -dict system/blockMeshDict - -# Inlet BC -surfaceToPatch -tol 1e-3 inlets.stl -export newmeshdir=$(foamListTimes -latestTime) -rm -rf constant/polyMesh/ -cp -r $newmeshdir/polyMesh ./constant -rm -rf $newmeshdir -cp constant/polyMesh/boundary /tmp -sed -i -e 's/inlets\.stl/inlet/g' /tmp/boundary -cat /tmp/boundary > constant/polyMesh/boundary - -# Outlet BC -surfaceToPatch -tol 1e-3 outlets.stl -export newmeshdir=$(foamListTimes -latestTime) -rm -rf constant/polyMesh/ -cp -r $newmeshdir/polyMesh ./constant -rm -rf $newmeshdir -cp constant/polyMesh/boundary /tmp -sed -i -e 's/outlets\.stl/outlet/g' /tmp/boundary -cat /tmp/boundary > constant/polyMesh/boundary - - -# Scale -transformPoints "scale=(2.7615275385627096 2.7615275385627096 2.7615275385627096)" - - -# setup IC -cp -r 0.orig 0 -setFields - -# Setup mass flow rate -# Get inlet area -postProcess -func 'patchIntegrate(patch="inlet", field="alpha.gas")' -postProcess -func writeCellVolumes -writeMeshObj - -echo PRESTEP 3 -python writeGlobalVars.py -cp constant/phaseProperties_constantd constant/phaseProperties - -conda deactivate - -if [ -f qoi.txt ]; then - rm qoi.txt -fi -if [ -f data/local/conv.npz ]; then - rm data/local/conv.npz -fi - diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py deleted file mode 100644 index be754f6c..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py +++ /dev/null @@ -1,243 +0,0 @@ -import argparse -import os -import sys - -import numpy as np -from prettyPlot.plotting import plt, pretty_labels - -from bird.utilities.ofio import * - - -def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - alpha_liq = field_dict["alpha_liq"] - volume = field_dict["volume"] - holdup = np.sum((1 - alpha_liq) * volume) / np.sum(volume) - return holdup, field_dict - - -def co2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - met = np.sum( - alpha_liq[indliq] * co2_liq[indliq] * volume[indliq] - ) / np.sum(volume[indliq]) - return met, field_dict - - -def cliq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: - rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") - rho_liq = readOFScal(rho_liq_file, nCells) - field_dict["rho_liq"] = rho_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - h2_liq = field_dict["h2_liq"] - rho_liq = field_dict["rho_liq"] - - # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - - c_h2 = 1000 * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * co2_liq[indliq] / 0.04401 - - c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - - return c_co2, c_h2, field_dict - - -def h2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - h2_liq = field_dict["h2_liq"] - met = np.sum(alpha_liq[indliq] * h2_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return met, field_dict - - -def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - volume = field_dict["volume"] - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.0) - liqvol = np.sum(alpha_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return liqvol, field_dict - - -parser = argparse.ArgumentParser(description="Convergence of GH") -parser.add_argument( - "-cn", - "--case_name", - type=str, - metavar="", - required=True, - help="Case name", -) -parser.add_argument( - "-df", - "--data_folder", - type=str, - metavar="", - required=False, - help="data folder name", - default="data", -) - -args, unknown = parser.parse_known_args() - - -case_root = "." # "../" -case_name = args.case_name # "12_hole_sparger_snappyRefine_700rpm_opt_coeff" -case_path = "." -dataFolder = args.data_folder - -if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): - sys.exit("WARNING: History already created, Skipping") - -time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) -cellCentres = readMesh(os.path.join(case_path, f"meshCellCentres_0.obj")) -nCells = len(cellCentres) - - -co2_history = np.zeros(len(time_str_sorted)) -c_co2_history = np.zeros(len(time_str_sorted)) -h2_history = np.zeros(len(time_str_sorted)) -c_h2_history = np.zeros(len(time_str_sorted)) -gh_history = np.zeros(len(time_str_sorted)) -liqvol_history = np.zeros(len(time_str_sorted)) -print(f"case_path = {case_path}") -field_dict = {} -for itime, time in enumerate(time_float_sorted): - time_folder = time_str_sorted[itime] - print(f"\tTime : {time_folder}") - if not field_dict == {}: - new_field_dict = {} - if "volume" in field_dict: - new_field_dict["volume"] = field_dict["volume"] - field_dict = new_field_dict - gh_history[itime], field_dict = compute_gas_holdup( - case_path, time_str_sorted[itime], nCells, field_dict - ) - co2_history[itime], field_dict = co2liq( - case_path, time_str_sorted[itime], nCells, field_dict - ) - h2_history[itime], field_dict = h2liq( - case_path, time_str_sorted[itime], nCells, field_dict - ) - liqvol_history[itime], field_dict = vol_liq( - case_path, time_str_sorted[itime], nCells, field_dict - ) - c_co2_history[itime], c_h2_history[itime], field_dict = cliq( - case_path, time_str_sorted[itime], nCells, field_dict - ) - - -os.makedirs(dataFolder, exist_ok=True) -os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) -np.savez( - os.path.join(dataFolder, case_name, "conv.npz"), - time=np.array(time_float_sorted), - gh=gh_history, - co2=co2_history, - h2=h2_history, - vol_liq=liqvol_history, - c_h2=c_h2_history, - c_co2=c_co2_history, -) diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh deleted file mode 100644 index 99da4760..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -birdmultiphaseEulerFoam - - - - diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script deleted file mode 100755 index efe675ff..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -#SBATCH --qos=high -#SBATCH --job-name=val2 -##SBATCH --partition=debug -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=16 -#SBATCH --time=07:59:00 -#SBATCH --account=co2snow - -bash presteps.sh -source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc -decomposePar -fileHandler collated -srun -n 16 birdmultiphaseEulerFoam -parallel -fileHandler collated -reconstructPar -newTimes diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post deleted file mode 100755 index aabbc33e..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/script_post +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -#SBATCH --qos=high -#SBATCH --job-name=val2 -##SBATCH --partition=debug -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=16 -#SBATCH --time=00:59:00 -#SBATCH --account=co2snow - -bash computeQOI.sh diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict deleted file mode 100644 index 1183d262..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/blockMeshDict +++ /dev/null @@ -1,746 +0,0 @@ -FoamFile -{ - version 2.0; - format ascii; - class dictionary; - object blockMeshDict; -} - -convertToMeters 1.0; - - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -vertices -( -( 0.0 0.0 0.0) -( 1.0 0.0 0.0) -( 2.0 0.0 0.0) -( 3.0 0.0 0.0) -( 4.0 0.0 0.0) -( 5.0 0.0 0.0) -( 6.0 0.0 0.0) -( 7.0 0.0 0.0) -( 8.0 0.0 0.0) -( 9.0 0.0 0.0) -( 10.0 0.0 0.0) -( 0.0 1.0 0.0) -( 1.0 1.0 0.0) -( 2.0 1.0 0.0) -( 3.0 1.0 0.0) -( 4.0 1.0 0.0) -( 5.0 1.0 0.0) -( 6.0 1.0 0.0) -( 7.0 1.0 0.0) -( 8.0 1.0 0.0) -( 9.0 1.0 0.0) -( 10.0 1.0 0.0) -( 0.0 2.0 0.0) -( 1.0 2.0 0.0) -( 2.0 2.0 0.0) -( 3.0 2.0 0.0) -( 4.0 2.0 0.0) -( 5.0 2.0 0.0) -( 6.0 2.0 0.0) -( 7.0 2.0 0.0) -( 8.0 2.0 0.0) -( 9.0 2.0 0.0) -( 10.0 2.0 0.0) -( 0.0 3.0 0.0) -( 1.0 3.0 0.0) -( 2.0 3.0 0.0) -( 3.0 3.0 0.0) -( 4.0 3.0 0.0) -( 5.0 3.0 0.0) -( 6.0 3.0 0.0) -( 7.0 3.0 0.0) -( 8.0 3.0 0.0) -( 9.0 3.0 0.0) -( 10.0 3.0 0.0) -( 0.0 4.0 0.0) -( 1.0 4.0 0.0) -( 2.0 4.0 0.0) -( 3.0 4.0 0.0) -( 4.0 4.0 0.0) -( 5.0 4.0 0.0) -( 6.0 4.0 0.0) -( 7.0 4.0 0.0) -( 8.0 4.0 0.0) -( 9.0 4.0 0.0) -( 10.0 4.0 0.0) -( 0.0 5.0 0.0) -( 1.0 5.0 0.0) -( 2.0 5.0 0.0) -( 3.0 5.0 0.0) -( 4.0 5.0 0.0) -( 5.0 5.0 0.0) -( 6.0 5.0 0.0) -( 7.0 5.0 0.0) -( 8.0 5.0 0.0) -( 9.0 5.0 0.0) -( 10.0 5.0 0.0) -( 0.0 6.0 0.0) -( 1.0 6.0 0.0) -( 2.0 6.0 0.0) -( 3.0 6.0 0.0) -( 4.0 6.0 0.0) -( 5.0 6.0 0.0) -( 6.0 6.0 0.0) -( 7.0 6.0 0.0) -( 8.0 6.0 0.0) -( 9.0 6.0 0.0) -( 10.0 6.0 0.0) -( 0.0 7.0 0.0) -( 1.0 7.0 0.0) -( 2.0 7.0 0.0) -( 3.0 7.0 0.0) -( 4.0 7.0 0.0) -( 5.0 7.0 0.0) -( 6.0 7.0 0.0) -( 7.0 7.0 0.0) -( 8.0 7.0 0.0) -( 9.0 7.0 0.0) -( 10.0 7.0 0.0) -( 0.0 0.0 1.0) -( 1.0 0.0 1.0) -( 2.0 0.0 1.0) -( 3.0 0.0 1.0) -( 4.0 0.0 1.0) -( 5.0 0.0 1.0) -( 6.0 0.0 1.0) -( 7.0 0.0 1.0) -( 8.0 0.0 1.0) -( 9.0 0.0 1.0) -( 10.0 0.0 1.0) -( 0.0 1.0 1.0) -( 1.0 1.0 1.0) -( 2.0 1.0 1.0) -( 3.0 1.0 1.0) -( 4.0 1.0 1.0) -( 5.0 1.0 1.0) -( 6.0 1.0 1.0) -( 7.0 1.0 1.0) -( 8.0 1.0 1.0) -( 9.0 1.0 1.0) -( 10.0 1.0 1.0) -( 0.0 2.0 1.0) -( 1.0 2.0 1.0) -( 2.0 2.0 1.0) -( 3.0 2.0 1.0) -( 4.0 2.0 1.0) -( 5.0 2.0 1.0) -( 6.0 2.0 1.0) -( 7.0 2.0 1.0) -( 8.0 2.0 1.0) -( 9.0 2.0 1.0) -( 10.0 2.0 1.0) -( 0.0 3.0 1.0) -( 1.0 3.0 1.0) -( 2.0 3.0 1.0) -( 3.0 3.0 1.0) -( 4.0 3.0 1.0) -( 5.0 3.0 1.0) -( 6.0 3.0 1.0) -( 7.0 3.0 1.0) -( 8.0 3.0 1.0) -( 9.0 3.0 1.0) -( 10.0 3.0 1.0) -( 0.0 4.0 1.0) -( 1.0 4.0 1.0) -( 2.0 4.0 1.0) -( 3.0 4.0 1.0) -( 4.0 4.0 1.0) -( 5.0 4.0 1.0) -( 6.0 4.0 1.0) -( 7.0 4.0 1.0) -( 8.0 4.0 1.0) -( 9.0 4.0 1.0) -( 10.0 4.0 1.0) -( 0.0 5.0 1.0) -( 1.0 5.0 1.0) -( 2.0 5.0 1.0) -( 3.0 5.0 1.0) -( 4.0 5.0 1.0) -( 5.0 5.0 1.0) -( 6.0 5.0 1.0) -( 7.0 5.0 1.0) -( 8.0 5.0 1.0) -( 9.0 5.0 1.0) -( 10.0 5.0 1.0) -( 0.0 6.0 1.0) -( 1.0 6.0 1.0) -( 2.0 6.0 1.0) -( 3.0 6.0 1.0) -( 4.0 6.0 1.0) -( 5.0 6.0 1.0) -( 6.0 6.0 1.0) -( 7.0 6.0 1.0) -( 8.0 6.0 1.0) -( 9.0 6.0 1.0) -( 10.0 6.0 1.0) -( 0.0 7.0 1.0) -( 1.0 7.0 1.0) -( 2.0 7.0 1.0) -( 3.0 7.0 1.0) -( 4.0 7.0 1.0) -( 5.0 7.0 1.0) -( 6.0 7.0 1.0) -( 7.0 7.0 1.0) -( 8.0 7.0 1.0) -( 9.0 7.0 1.0) -( 10.0 7.0 1.0) -( 0.0 0.0 2.0) -( 1.0 0.0 2.0) -( 2.0 0.0 2.0) -( 3.0 0.0 2.0) -( 4.0 0.0 2.0) -( 5.0 0.0 2.0) -( 6.0 0.0 2.0) -( 7.0 0.0 2.0) -( 8.0 0.0 2.0) -( 9.0 0.0 2.0) -( 10.0 0.0 2.0) -( 0.0 1.0 2.0) -( 1.0 1.0 2.0) -( 2.0 1.0 2.0) -( 3.0 1.0 2.0) -( 4.0 1.0 2.0) -( 5.0 1.0 2.0) -( 6.0 1.0 2.0) -( 7.0 1.0 2.0) -( 8.0 1.0 2.0) -( 9.0 1.0 2.0) -( 10.0 1.0 2.0) -( 0.0 2.0 2.0) -( 1.0 2.0 2.0) -( 2.0 2.0 2.0) -( 3.0 2.0 2.0) -( 4.0 2.0 2.0) -( 5.0 2.0 2.0) -( 6.0 2.0 2.0) -( 7.0 2.0 2.0) -( 8.0 2.0 2.0) -( 9.0 2.0 2.0) -( 10.0 2.0 2.0) -( 0.0 3.0 2.0) -( 1.0 3.0 2.0) -( 2.0 3.0 2.0) -( 3.0 3.0 2.0) -( 4.0 3.0 2.0) -( 5.0 3.0 2.0) -( 6.0 3.0 2.0) -( 7.0 3.0 2.0) -( 8.0 3.0 2.0) -( 9.0 3.0 2.0) -( 10.0 3.0 2.0) -( 0.0 4.0 2.0) -( 1.0 4.0 2.0) -( 2.0 4.0 2.0) -( 3.0 4.0 2.0) -( 4.0 4.0 2.0) -( 5.0 4.0 2.0) -( 6.0 4.0 2.0) -( 7.0 4.0 2.0) -( 8.0 4.0 2.0) -( 9.0 4.0 2.0) -( 10.0 4.0 2.0) -( 0.0 5.0 2.0) -( 1.0 5.0 2.0) -( 2.0 5.0 2.0) -( 3.0 5.0 2.0) -( 4.0 5.0 2.0) -( 5.0 5.0 2.0) -( 6.0 5.0 2.0) -( 7.0 5.0 2.0) -( 8.0 5.0 2.0) -( 9.0 5.0 2.0) -( 10.0 5.0 2.0) -( 0.0 6.0 2.0) -( 1.0 6.0 2.0) -( 2.0 6.0 2.0) -( 3.0 6.0 2.0) -( 4.0 6.0 2.0) -( 5.0 6.0 2.0) -( 6.0 6.0 2.0) -( 7.0 6.0 2.0) -( 8.0 6.0 2.0) -( 9.0 6.0 2.0) -( 10.0 6.0 2.0) -( 0.0 7.0 2.0) -( 1.0 7.0 2.0) -( 2.0 7.0 2.0) -( 3.0 7.0 2.0) -( 4.0 7.0 2.0) -( 5.0 7.0 2.0) -( 6.0 7.0 2.0) -( 7.0 7.0 2.0) -( 8.0 7.0 2.0) -( 9.0 7.0 2.0) -( 10.0 7.0 2.0) -( 0.0 0.0 3.0) -( 1.0 0.0 3.0) -( 2.0 0.0 3.0) -( 3.0 0.0 3.0) -( 4.0 0.0 3.0) -( 5.0 0.0 3.0) -( 6.0 0.0 3.0) -( 7.0 0.0 3.0) -( 8.0 0.0 3.0) -( 9.0 0.0 3.0) -( 10.0 0.0 3.0) -( 0.0 1.0 3.0) -( 1.0 1.0 3.0) -( 2.0 1.0 3.0) -( 3.0 1.0 3.0) -( 4.0 1.0 3.0) -( 5.0 1.0 3.0) -( 6.0 1.0 3.0) -( 7.0 1.0 3.0) -( 8.0 1.0 3.0) -( 9.0 1.0 3.0) -( 10.0 1.0 3.0) -( 0.0 2.0 3.0) -( 1.0 2.0 3.0) -( 2.0 2.0 3.0) -( 3.0 2.0 3.0) -( 4.0 2.0 3.0) -( 5.0 2.0 3.0) -( 6.0 2.0 3.0) -( 7.0 2.0 3.0) -( 8.0 2.0 3.0) -( 9.0 2.0 3.0) -( 10.0 2.0 3.0) -( 0.0 3.0 3.0) -( 1.0 3.0 3.0) -( 2.0 3.0 3.0) -( 3.0 3.0 3.0) -( 4.0 3.0 3.0) -( 5.0 3.0 3.0) -( 6.0 3.0 3.0) -( 7.0 3.0 3.0) -( 8.0 3.0 3.0) -( 9.0 3.0 3.0) -( 10.0 3.0 3.0) -( 0.0 4.0 3.0) -( 1.0 4.0 3.0) -( 2.0 4.0 3.0) -( 3.0 4.0 3.0) -( 4.0 4.0 3.0) -( 5.0 4.0 3.0) -( 6.0 4.0 3.0) -( 7.0 4.0 3.0) -( 8.0 4.0 3.0) -( 9.0 4.0 3.0) -( 10.0 4.0 3.0) -( 0.0 5.0 3.0) -( 1.0 5.0 3.0) -( 2.0 5.0 3.0) -( 3.0 5.0 3.0) -( 4.0 5.0 3.0) -( 5.0 5.0 3.0) -( 6.0 5.0 3.0) -( 7.0 5.0 3.0) -( 8.0 5.0 3.0) -( 9.0 5.0 3.0) -( 10.0 5.0 3.0) -( 0.0 6.0 3.0) -( 1.0 6.0 3.0) -( 2.0 6.0 3.0) -( 3.0 6.0 3.0) -( 4.0 6.0 3.0) -( 5.0 6.0 3.0) -( 6.0 6.0 3.0) -( 7.0 6.0 3.0) -( 8.0 6.0 3.0) -( 9.0 6.0 3.0) -( 10.0 6.0 3.0) -( 0.0 7.0 3.0) -( 1.0 7.0 3.0) -( 2.0 7.0 3.0) -( 3.0 7.0 3.0) -( 4.0 7.0 3.0) -( 5.0 7.0 3.0) -( 6.0 7.0 3.0) -( 7.0 7.0 3.0) -( 8.0 7.0 3.0) -( 9.0 7.0 3.0) -( 10.0 7.0 3.0) -( 0.0 0.0 4.0) -( 1.0 0.0 4.0) -( 2.0 0.0 4.0) -( 3.0 0.0 4.0) -( 4.0 0.0 4.0) -( 5.0 0.0 4.0) -( 6.0 0.0 4.0) -( 7.0 0.0 4.0) -( 8.0 0.0 4.0) -( 9.0 0.0 4.0) -( 10.0 0.0 4.0) -( 0.0 1.0 4.0) -( 1.0 1.0 4.0) -( 2.0 1.0 4.0) -( 3.0 1.0 4.0) -( 4.0 1.0 4.0) -( 5.0 1.0 4.0) -( 6.0 1.0 4.0) -( 7.0 1.0 4.0) -( 8.0 1.0 4.0) -( 9.0 1.0 4.0) -( 10.0 1.0 4.0) -( 0.0 2.0 4.0) -( 1.0 2.0 4.0) -( 2.0 2.0 4.0) -( 3.0 2.0 4.0) -( 4.0 2.0 4.0) -( 5.0 2.0 4.0) -( 6.0 2.0 4.0) -( 7.0 2.0 4.0) -( 8.0 2.0 4.0) -( 9.0 2.0 4.0) -( 10.0 2.0 4.0) -( 0.0 3.0 4.0) -( 1.0 3.0 4.0) -( 2.0 3.0 4.0) -( 3.0 3.0 4.0) -( 4.0 3.0 4.0) -( 5.0 3.0 4.0) -( 6.0 3.0 4.0) -( 7.0 3.0 4.0) -( 8.0 3.0 4.0) -( 9.0 3.0 4.0) -( 10.0 3.0 4.0) -( 0.0 4.0 4.0) -( 1.0 4.0 4.0) -( 2.0 4.0 4.0) -( 3.0 4.0 4.0) -( 4.0 4.0 4.0) -( 5.0 4.0 4.0) -( 6.0 4.0 4.0) -( 7.0 4.0 4.0) -( 8.0 4.0 4.0) -( 9.0 4.0 4.0) -( 10.0 4.0 4.0) -( 0.0 5.0 4.0) -( 1.0 5.0 4.0) -( 2.0 5.0 4.0) -( 3.0 5.0 4.0) -( 4.0 5.0 4.0) -( 5.0 5.0 4.0) -( 6.0 5.0 4.0) -( 7.0 5.0 4.0) -( 8.0 5.0 4.0) -( 9.0 5.0 4.0) -( 10.0 5.0 4.0) -( 0.0 6.0 4.0) -( 1.0 6.0 4.0) -( 2.0 6.0 4.0) -( 3.0 6.0 4.0) -( 4.0 6.0 4.0) -( 5.0 6.0 4.0) -( 6.0 6.0 4.0) -( 7.0 6.0 4.0) -( 8.0 6.0 4.0) -( 9.0 6.0 4.0) -( 10.0 6.0 4.0) -( 0.0 7.0 4.0) -( 1.0 7.0 4.0) -( 2.0 7.0 4.0) -( 3.0 7.0 4.0) -( 4.0 7.0 4.0) -( 5.0 7.0 4.0) -( 6.0 7.0 4.0) -( 7.0 7.0 4.0) -( 8.0 7.0 4.0) -( 9.0 7.0 4.0) -( 10.0 7.0 4.0) -( 0.0 0.0 5.0) -( 1.0 0.0 5.0) -( 2.0 0.0 5.0) -( 3.0 0.0 5.0) -( 4.0 0.0 5.0) -( 5.0 0.0 5.0) -( 6.0 0.0 5.0) -( 7.0 0.0 5.0) -( 8.0 0.0 5.0) -( 9.0 0.0 5.0) -( 10.0 0.0 5.0) -( 0.0 1.0 5.0) -( 1.0 1.0 5.0) -( 2.0 1.0 5.0) -( 3.0 1.0 5.0) -( 4.0 1.0 5.0) -( 5.0 1.0 5.0) -( 6.0 1.0 5.0) -( 7.0 1.0 5.0) -( 8.0 1.0 5.0) -( 9.0 1.0 5.0) -( 10.0 1.0 5.0) -( 0.0 2.0 5.0) -( 1.0 2.0 5.0) -( 2.0 2.0 5.0) -( 3.0 2.0 5.0) -( 4.0 2.0 5.0) -( 5.0 2.0 5.0) -( 6.0 2.0 5.0) -( 7.0 2.0 5.0) -( 8.0 2.0 5.0) -( 9.0 2.0 5.0) -( 10.0 2.0 5.0) -( 0.0 3.0 5.0) -( 1.0 3.0 5.0) -( 2.0 3.0 5.0) -( 3.0 3.0 5.0) -( 4.0 3.0 5.0) -( 5.0 3.0 5.0) -( 6.0 3.0 5.0) -( 7.0 3.0 5.0) -( 8.0 3.0 5.0) -( 9.0 3.0 5.0) -( 10.0 3.0 5.0) -( 0.0 4.0 5.0) -( 1.0 4.0 5.0) -( 2.0 4.0 5.0) -( 3.0 4.0 5.0) -( 4.0 4.0 5.0) -( 5.0 4.0 5.0) -( 6.0 4.0 5.0) -( 7.0 4.0 5.0) -( 8.0 4.0 5.0) -( 9.0 4.0 5.0) -( 10.0 4.0 5.0) -( 0.0 5.0 5.0) -( 1.0 5.0 5.0) -( 2.0 5.0 5.0) -( 3.0 5.0 5.0) -( 4.0 5.0 5.0) -( 5.0 5.0 5.0) -( 6.0 5.0 5.0) -( 7.0 5.0 5.0) -( 8.0 5.0 5.0) -( 9.0 5.0 5.0) -( 10.0 5.0 5.0) -( 0.0 6.0 5.0) -( 1.0 6.0 5.0) -( 2.0 6.0 5.0) -( 3.0 6.0 5.0) -( 4.0 6.0 5.0) -( 5.0 6.0 5.0) -( 6.0 6.0 5.0) -( 7.0 6.0 5.0) -( 8.0 6.0 5.0) -( 9.0 6.0 5.0) -( 10.0 6.0 5.0) -( 0.0 7.0 5.0) -( 1.0 7.0 5.0) -( 2.0 7.0 5.0) -( 3.0 7.0 5.0) -( 4.0 7.0 5.0) -( 5.0 7.0 5.0) -( 6.0 7.0 5.0) -( 7.0 7.0 5.0) -( 8.0 7.0 5.0) -( 9.0 7.0 5.0) -( 10.0 7.0 5.0) -); - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -blocks -( - - //block 0 -hex (0 1 12 11 88 89 100 99 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 1 -hex (1 2 13 12 89 90 101 100 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 2 -hex (2 3 14 13 90 91 102 101 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 3 -hex (3 4 15 14 91 92 103 102 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 4 -hex (4 5 16 15 92 93 104 103 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 5 -hex (5 6 17 16 93 94 105 104 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 6 -hex (6 7 18 17 94 95 106 105 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 7 -hex (7 8 19 18 95 96 107 106 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 8 -hex (8 9 20 19 96 97 108 107 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 9 -hex (9 10 21 20 97 98 109 108 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 10 -hex (97 98 109 108 185 186 197 196 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 11 -hex (185 186 197 196 273 274 285 284 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 12 -hex (273 274 285 284 361 362 373 372 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 13 -hex (361 362 373 372 449 450 461 460 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 14 -hex (360 361 372 371 448 449 460 459 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 15 -hex (359 360 371 370 447 448 459 458 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 16 -hex (358 359 370 369 446 447 458 457 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 17 -hex (357 358 369 368 445 446 457 456 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 18 -hex (356 357 368 367 444 445 456 455 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 19 -hex (355 356 367 366 443 444 455 454 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 20 -hex (354 355 366 365 442 443 454 453 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 21 -hex (353 354 365 364 441 442 453 452 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 22 -hex (352 353 364 363 440 441 452 451 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 23 -hex (363 364 375 374 451 452 463 462 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 24 -hex (374 375 386 385 462 463 474 473 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 25 -hex (385 386 397 396 473 474 485 484 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 26 -hex (396 397 408 407 484 485 496 495 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 27 -hex (407 408 419 418 495 496 507 506 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 28 -hex (418 419 430 429 506 507 518 517 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 29 -hex (308 309 320 319 396 397 408 407 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 30 -hex (220 221 232 231 308 309 320 319 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 31 -hex (132 133 144 143 220 221 232 231 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 32 -hex (44 45 56 55 132 133 144 143 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 33 -hex (55 56 67 66 143 144 155 154 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 34 -hex (66 67 78 77 154 155 166 165 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 35 -hex (33 34 45 44 121 122 133 132 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 36 -hex (22 23 34 33 110 111 122 121 ) -( 10 10 10 ) -SimpleGrading (1 1 1) - - //block 37 -hex (11 12 23 22 99 100 111 110 ) -( 10 10 10 ) -SimpleGrading (1 1 1) -); - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -defaultPatch -{ type wall;} - -patches -( -); diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict deleted file mode 100644 index 6f803fd2..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/controlDict +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "system"; - object controlDict; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -application birdmultiphaseEulerFoam; - -startFrom latestTime;//startTime; - -startTime 0; - -stopAt endTime; - -endTime 200; - -deltaT 0.0001; - -writeControl adjustableRunTime; - -writeInterval 2; - -purgeWrite 0; - -writeFormat ascii; - -writePrecision 6; - -writeCompression off; - -timeFormat general; - -timePrecision 6; - -runTimeModifiable yes; - -adjustTimeStep yes; - -maxCo 0.5; - -maxDeltaT 0.01; - - -functions -{ - - #includeFunc writeObjects(d.gas) - #includeFunc writeObjects(thermo:rho.gas) - #includeFunc writeObjects(thermo:rho.liquid) -} -//functions -//{ -// #includeFunc fieldAverage(U.air, U.water, alpha.air, p) -//} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict deleted file mode 100755 index f8397e73..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/decomposeParDict +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ -| ========= | | -| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | -| \\ / O peration | Version: 3.0.x | -| \\ / A nd | Web: www.OpenFOAM.org | -| \\/ M anipulation | | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - version 2.0; - format ascii; - class dictionary; - object decomposeParDict; -} - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -numberOfSubdomains 16; - -method scotch; - -hierarchicalCoeffs -{ - n (4 4 1); - delta 0.001; - order xyz; -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints deleted file mode 100644 index 334f1c8f..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvConstraints +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - object fvConstraints; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -limitp -{ - type limitPressure; - - min 1e4; -} -limitUliq -{ - type limitVelocity; - active yes; - U U.liquid; - selectionMode all; - max 1e1; -} -limitUgas -{ - type limitVelocity; - active yes; - U U.gas; - selectionMode all; - max 2e1; -} -limitTgas -{ - type limitTemperature; - selectionMode all; - min 290; - max 310; - phase gas; -} -limitTliq -{ - type limitTemperature; - selectionMode all; - min 290; - max 310; - phase liquid; -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes deleted file mode 100644 index 52e6e13a..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSchemes +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "system"; - object fvSchemes; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -ddtSchemes -{ - default Euler; -} - -gradSchemes -{ - default Gauss linear; - limited cellLimited Gauss linear 1; -} - -divSchemes -{ - default none; - - "div\(phi,alpha.*\)" Gauss vanLeer; - - "div\(phir,alpha.*,alpha.*\)" Gauss vanLeer; - - "div\(alphaRhoPhi.*,U.*\)" Gauss limitedLinearV 1; - "div\(phi.*,U.*\)" Gauss limitedLinearV 1; - "div\(alphaRhoPhi.*,Yi\)" Gauss limitedLinear 1; - "div\(alphaRhoPhi.*,(h|e).*\)" Gauss limitedLinear 1; - "div\(alphaRhoPhi.*,(K|k|epsilon|omega).*\)" Gauss limitedLinear 1; - "div\(alphaPhi.*,f.*\)" Gauss limitedLinear 1; - "div\(alphaRhoPhi.*,\(p\|thermo:rho.*\)\)" Gauss limitedLinear 1; - - "div\(phim,(k|epsilon)m\)" Gauss upwind; - "div\(\(\(\(alpha.*\*thermo:rho.*\)*nuEff.*\)*dev2\(T\(grad\(U.*\)\)\)\)\)" Gauss linear; -} - -laplacianSchemes -{ - default Gauss linear corrected; -} - -interpolationSchemes -{ - default linear; -} - -snGradSchemes -{ - default uncorrected; -} - -wallDist -{ - method Poisson; - nRequired true; -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution deleted file mode 100644 index 2e69fdfa..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/fvSolution +++ /dev/null @@ -1,120 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "system"; - object fvSolution; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -solvers -{ - "alpha.*" - { - nAlphaCorr 2; - nAlphaSubCycles 5; - } - - bubbles - { - nCorr 1; - tolerance 1e-4; - scale true; - solveOnFinalIterOnly true; - sourceUpdateInterval 1; - } - - p_rgh - { - solver GAMG; - smoother DIC; - tolerance 1e-7; - relTol 0; - } - - p_rghFinal - { - $p_rgh; - relTol 0; - } - - "(k|omega).*" - { - solver smoothSolver; - smoother symGaussSeidel; - tolerance 1e-7; - relTol 0; - minIter 1; - } - - "(e|h).*" - { - solver smoothSolver; - smoother symGaussSeidel; - tolerance 1e-8; - relTol 0; - minIter 0; - maxIter 3; - } - - "f.*" - { - solver PBiCGStab; - preconditioner DILU; - tolerance 1e-6; - relTol 0; - } - - "Yi.*" - { - solver PBiCGStab; - preconditioner DILU; - tolerance 1e-12; - relTol 0; - residualAlpha 1e-8; - } - - "U.*" - { - solver smoothSolver; - smoother symGaussSeidel; - tolerance 1e-5; - relTol 0; - minIter 1; - } - - yPsi - { - solver PCG; - preconditioner DIC; - tolerance 1e-10; - relTol 0; - } - -} - -PIMPLE -{ - nOuterCorrectors 3; - nCorrectors 1; - nNonOrthogonalCorrectors 0; - -} - -relaxationFactors -{ - equations - { - ".*" 1; - } -} - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json deleted file mode 100644 index 2b53da73..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/inlets_outlets.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "Geometry": { - "OverallDomain": { - "x" : {"nblocks": 10, "size_per_block": 1.0}, - "y" : {"nblocks": 11, "size_per_block": 1.0}, - "z" : {"nblocks": 5, "size_per_block": 1.0} - }, - "Fluids": [ - [ [0,0,0], [9,0,0] ], - [ [9,0,0], [9,0,4] ], - [ [9,0,4], [0,0,4] ], - [ [0,1,4], [0,4,4] ], - [ [0,4,4], [0,10,4] ], - [ [0,4,4], [0,4,0] ], - [ [0,4,0], [0,10,0] ], - [ [0,4,0], [0,1,0] ] - ] - }, - "inlets": [ - {"branch_id": 0, "type": "circle", "frac_space": 0.2222222222222222, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "bottom"}, - {"branch_id": 0, "type": "circle", "frac_space": 0.5, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"}, - {"branch_id": 0, "type": "circle", "frac_space": 0.7777777777777778, "radius": 0.4, "normal_dir": 1,"nelements": 50, "block_pos": "bottom"} - ], - "outlets": [ - {"branch_id": 6, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"}, - {"branch_id": 4, "type": "circle", "frac_space": 1, "normal_dir": 1, "radius": 0.4, "nelements": 50, "block_pos": "top"} - ] -} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json deleted file mode 100644 index 29841d7e..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mesh.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "Meshing": { - "Blockwise": { - "x" : 10, - "y" : 10, - "z" : 10 - } - }, - "Geometry": { - "OverallDomain": { - "x" : {"nblocks": 10, "size_per_block": 1.0}, - "y" : {"nblocks": 11, "size_per_block": 1.0}, - "z" : {"nblocks": 5, "size_per_block": 1.0} - }, - "Fluids": [ - [ [0,0,0], [9,0,0] ], - [ [9,0,0], [9,0,4] ], - [ [9,0,4], [0,0,4] ], - [ [0,1,4], [0,4,4] ], - [ [0,4,4], [0,10,4] ], - [ [0,4,4], [0,4,0] ], - [ [0,4,0], [0,10,0] ], - [ [0,4,0], [0,1,0] ] - ] - } -} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json deleted file mode 100644 index b6224fb7..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/mixers.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "Meshing": { - "Blockwise": { - "x" : 10, - "y" : 10, - "z" : 10 - } - }, - "Geometry": { - "OverallDomain": { - "x" : {"nblocks": 10, "size_per_block": 1.0, "rescale": 2.7615275385627096}, - "y" : {"nblocks": 11, "size_per_block": 1.0, "rescale": 2.7615275385627096}, - "z" : {"nblocks": 5, "size_per_block": 1.0, "rescale": 2.7615275385627096} - }, - "Fluids": [ - [ [0,0,0], [9,0,0] ], - [ [9,0,0], [9,0,4] ], - [ [9,0,4], [0,0,4] ], - [ [0,1,4], [0,4,4] ], - [ [0,4,4], [0,10,4] ], - [ [0,4,4], [0,4,0] ], - [ [0,4,0], [0,10,0] ], - [ [0,4,0], [0,1,0] ] - ] - }, - "mixers": [ - {"branch_id": 2, "frac_space": 0.5, "start_time": 4, "power": 1500, "sign": "+"} - ] -} diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict deleted file mode 100644 index 89a797b9..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/system/setFieldsDict +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------*- C++ -*----------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | Website: https://openfoam.org - \\ / A nd | Version: dev - \\/ M anipulation | -\*---------------------------------------------------------------------------*/ -FoamFile -{ - format ascii; - class dictionary; - location "system"; - object setFieldsDict; -} -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -defaultFieldValues -( - volScalarFieldValue alpha.gas 0.99 - volScalarFieldValue alpha.liquid 0.01 -); - -regions -( - boxToCell - { - box (-1.0 -1.0 -1.0) (552.3 11.046 552.3); - fieldValues - ( - volScalarFieldValue alpha.gas 0.01 - volScalarFieldValue alpha.liquid 0.99 - ); - } -); - - -// ************************************************************************* // diff --git a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py b/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py deleted file mode 100644 index 0594eccc..00000000 --- a/tutorial_cases/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py +++ /dev/null @@ -1,47 +0,0 @@ -import os - -import numpy as np - -from bird.utilities.ofio import * - - -def writeGvars(inletA, liqVol): - filename_tmp = os.path.join("constant", "globalVars_temp") - with open(filename_tmp, "r+") as f: - lines = f.readlines() - filename = os.path.join("constant", "globalVars") - with open(filename, "w+") as f: - for line in lines: - if line.startswith("inletA"): - f.write(f"inletA\t{inletA:g};\n") - elif line.startswith("liqVol"): - f.write(f"liqVol\t{liqVol:g};\n") - else: - f.write(line) - - -def readInletArea(): - filename = os.path.join( - "postProcessing", - "patchIntegrate(patch=inlet,field=alpha.gas)", - "0", - "surfaceFieldValue.dat", - ) - with open(filename, "r+") as f: - lines = f.readlines() - return float(lines[4].split()[-1]) - - -def getLiqVol(): - cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) - alpha_field = readOFScal( - os.path.join("0", "alpha.liquid"), len(cellCentres) - ) - return np.sum(volume_field * alpha_field) - - -if __name__ == "__main__": - A = readInletArea() - V = getLiqVol() - writeGvars(A, V) From 4af29c536c9f02d5cee616353d70f8878923aa5c Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 15:23:38 -0600 Subject: [PATCH 64/86] use path lib instead of in package data --- tests/preprocess/test_case_gen.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 29729d46..1637d140 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -1,16 +1,23 @@ import os import pickle import shutil - +from pathlib import Path import numpy as np -from bird import BIRD_CASE_GEN_DATA_DIR from bird.preprocess.json_gen.design_io import * from bird.preprocess.json_gen.generate_designs import * def test_continuous_loop(): + BIRD_CASE_GEN_DATA_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "data_case_gen", + ) generate_single_scaledup_reactor_sparger_cases( sparger_locs=[0.3, 0.5, 1.4], sim_id=0, @@ -37,6 +44,16 @@ def test_continuous_loop(): def test_discrete_loop(): + + BIRD_CASE_GEN_DATA_DIR = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "preprocess", + "data_case_gen", + ) + def optimization_setup(): # spots on the branches where we can place sparger or mixers branchcom_spots = {} From 27c1b431c2b4e43f917fe6df145c356aa5c566a9 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 16:27:15 -0600 Subject: [PATCH 65/86] use post_quantities functions for the history --- bird/postprocess/post_quantities.py | 14 +- .../read_history.py | 209 ++++-------------- .../loop_reactor_mixing/read_history.py | 182 ++------------- 3 files changed, 64 insertions(+), 341 deletions(-) diff --git a/bird/postprocess/post_quantities.py b/bird/postprocess/post_quantities.py index 16fcf7a5..0b23a658 100644 --- a/bird/postprocess/post_quantities.py +++ b/bird/postprocess/post_quantities.py @@ -257,7 +257,9 @@ def compute_gas_holdup( ) -> tuple: """ Calculate volume averaged gas hold up at a given time - $\frac{1}{V_{\rm tot}} \int_{V} (1-\alpha_{\rm liq}) dV$ + + .. math:: + \frac{1}{V_{\rm tot}} \int_{V} (1-\alpha_{\rm liq}) dV Parameters ---------- @@ -404,7 +406,8 @@ def compute_ave_y_liq( """ Calculate liquid volume averaged mass fraction of a species at a given time - $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} Y dV_{\rm liq}$ + .. math:: + \frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} Y dV_{\rm liq} Parameters ---------- @@ -482,7 +485,8 @@ def compute_ave_conc_liq( """ Calculate liquid volume averaged concentration of a species at a given time - $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} \rho_{\rm liq} Y / W dV_{\rm liq}$ + .. math:: + \frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} \rho_{\rm liq} Y / W dV_{\rm liq} Parameters ---------- @@ -582,7 +586,9 @@ def compute_ave_bubble_diam( ) -> tuple: """ Calculate averaged bubble diameter over the liquid volume - $\frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} D dV$ + + .. math:: + \frac{1}{V_{\rm liq, tot}} \int_{V_{\rm liq}} D dV Parameters ---------- diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py index be754f6c..8d92ac65 100644 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py +++ b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -5,162 +5,9 @@ import numpy as np from prettyPlot.plotting import plt, pretty_labels +from bird.postprocess.post_quantities import * from bird.utilities.ofio import * - -def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - alpha_liq = field_dict["alpha_liq"] - volume = field_dict["volume"] - holdup = np.sum((1 - alpha_liq) * volume) / np.sum(volume) - return holdup, field_dict - - -def co2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - met = np.sum( - alpha_liq[indliq] * co2_liq[indliq] * volume[indliq] - ) / np.sum(volume[indliq]) - return met, field_dict - - -def cliq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: - rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") - rho_liq = readOFScal(rho_liq_file, nCells) - field_dict["rho_liq"] = rho_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells) - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - h2_liq = field_dict["h2_liq"] - rho_liq = field_dict["rho_liq"] - - # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - - c_h2 = 1000 * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * co2_liq[indliq] / 0.04401 - - c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - - return c_co2, c_h2, field_dict - - -def h2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells) - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - h2_liq = field_dict["h2_liq"] - met = np.sum(alpha_liq[indliq] * h2_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return met, field_dict - - -def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells) - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells) - # print("reading Volume") - field_dict["volume"] = volume - volume = field_dict["volume"] - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.0) - liqvol = np.sum(alpha_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return liqvol, field_dict - - parser = argparse.ArgumentParser(description="Convergence of GH") parser.add_argument( "-cn", @@ -188,7 +35,7 @@ def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): case_path = "." dataFolder = args.data_folder -if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): +if os.path.isfile(os.path.join(dataFolder, case_name, "conv2.npz")): sys.exit("WARNING: History already created, Skipping") time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) @@ -209,35 +56,59 @@ def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): print(f"\tTime : {time_folder}") if not field_dict == {}: new_field_dict = {} - if "volume" in field_dict: - new_field_dict["volume"] = field_dict["volume"] + if "V" in field_dict: + new_field_dict["V"] = field_dict["V"] field_dict = new_field_dict gh_history[itime], field_dict = compute_gas_holdup( - case_path, time_str_sorted[itime], nCells, field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + field_dict=field_dict, ) - co2_history[itime], field_dict = co2liq( - case_path, time_str_sorted[itime], nCells, field_dict + co2_history[itime], field_dict = compute_ave_y_liq( + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="CO2", + field_dict=field_dict, ) - h2_history[itime], field_dict = h2liq( - case_path, time_str_sorted[itime], nCells, field_dict + h2_history[itime], field_dict = compute_ave_y_liq( + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="H2", + field_dict=field_dict, ) - liqvol_history[itime], field_dict = vol_liq( - case_path, time_str_sorted[itime], nCells, field_dict + c_co2_history[itime], field_dict = compute_ave_conc_liq( + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="CO2", + mol_weight=0.04401, + field_dict=field_dict, ) - c_co2_history[itime], c_h2_history[itime], field_dict = cliq( - case_path, time_str_sorted[itime], nCells, field_dict + c_h2_history[itime], field_dict = compute_ave_conc_liq( + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="H2", + mol_weight=0.002016, + field_dict=field_dict, ) - os.makedirs(dataFolder, exist_ok=True) os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) np.savez( - os.path.join(dataFolder, case_name, "conv.npz"), + os.path.join(dataFolder, case_name, "conv2.npz"), time=np.array(time_float_sorted), gh=gh_history, co2=co2_history, h2=h2_history, - vol_liq=liqvol_history, c_h2=c_h2_history, c_co2=c_co2_history, ) diff --git a/tutorial_cases/loop_reactor_mixing/read_history.py b/tutorial_cases/loop_reactor_mixing/read_history.py index f872f3e6..8246ed46 100644 --- a/tutorial_cases/loop_reactor_mixing/read_history.py +++ b/tutorial_cases/loop_reactor_mixing/read_history.py @@ -6,159 +6,7 @@ from prettyPlot.plotting import plt, pretty_labels from bird.utilities.ofio import * - - -def compute_gas_holdup(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells)["field"] - # print("reading Volume") - field_dict["volume"] = volume - alpha_liq = field_dict["alpha_liq"] - volume = field_dict["volume"] - holdup = np.sum((1 - alpha_liq) * volume) / np.sum(volume) - return holdup, field_dict - - -def co2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells)["field"] - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells)["field"] - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - met = np.sum( - alpha_liq[indliq] * co2_liq[indliq] * volume[indliq] - ) / np.sum(volume[indliq]) - return met, field_dict - - -def cliq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("rho_liq" in field_dict) or field_dict["rho_liq"] is None: - rho_liq_file = os.path.join(caseFolder, timeFolder, "rhom") - rho_liq = readOFScal(rho_liq_file, nCells)["field"] - field_dict["rho_liq"] = rho_liq - if not ("co2_liq" in field_dict) or field_dict["co2_liq"] is None: - co2_liq_file = os.path.join(caseFolder, timeFolder, "CO2.liquid") - co2_liq = readOFScal(co2_liq_file, nCells)["field"] - # print("computing co2 liq") - field_dict["co2_liq"] = co2_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells)["field"] - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells)["field"] - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - co2_liq = field_dict["co2_liq"] - h2_liq = field_dict["h2_liq"] - rho_liq = field_dict["rho_liq"] - - # c_h2 = rho_liq[indliq] * alpha_liq[indliq] * h2_liq[indliq] / 0.002016 - # c_co2 = rho_liq[indliq] * alpha_liq[indliq] * co2_liq[indliq] / 0.04401 - - c_h2 = 1000 * h2_liq[indliq] / 0.002016 - c_co2 = 1000 * co2_liq[indliq] / 0.04401 - - c_h2 = np.sum(c_h2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - c_co2 = np.sum(c_co2 * volume[indliq] * alpha_liq[indliq]) / np.sum( - volume[indliq] * alpha_liq[indliq] - ) - - return c_co2, c_h2, field_dict - - -def h2liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("h2_liq" in field_dict) or field_dict["h2_liq"] is None: - h2_liq_file = os.path.join(caseFolder, timeFolder, "H2.liquid") - h2_liq = readOFScal(h2_liq_file, nCells)["field"] - # print("computing h2 liq") - field_dict["h2_liq"] = h2_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells)["field"] - # print("reading Volume") - field_dict["volume"] = volume - if not ("indliq" in field_dict) or field_dict["indliq"] is None: - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.5) - # print("computing indliq") - field_dict["indliq"] = indliq - volume = field_dict["volume"] - indliq = field_dict["indliq"] - alpha_liq = field_dict["alpha_liq"] - h2_liq = field_dict["h2_liq"] - met = np.sum(alpha_liq[indliq] * h2_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return met, field_dict - - -def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): - if not ("alpha_liq" in field_dict) or field_dict["alpha_liq"] is None: - alpha_liq_file = os.path.join(caseFolder, timeFolder, "alpha.liquid") - alpha_liq = readOFScal(alpha_liq_file, nCells)["field"] - # print("reading alpha_liq") - field_dict["alpha_liq"] = alpha_liq - if not ("volume" in field_dict) or field_dict["volume"] is None: - volume_file = os.path.join(caseFolder, "0", "V") - volume = readOFScal(volume_file, nCells)["field"] - # print("reading Volume") - field_dict["volume"] = volume - volume = field_dict["volume"] - alpha_liq = field_dict["alpha_liq"] - indliq = np.argwhere(alpha_liq > 0.0) - liqvol = np.sum(alpha_liq[indliq] * volume[indliq]) / np.sum( - volume[indliq] - ) - return liqvol, field_dict +from bird.postprocess.post_quantities import * parser = argparse.ArgumentParser(description="Convergence of GH") @@ -188,7 +36,7 @@ def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): case_path = "." dataFolder = args.data_folder -if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): +if os.path.isfile(os.path.join(dataFolder, case_name, "conv2.npz")): sys.exit("WARNING: History already created, Skipping") time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) @@ -209,35 +57,33 @@ def vol_liq(caseFolder, timeFolder, nCells, field_dict={}): print(f"\tTime : {time_folder}") if not field_dict == {}: new_field_dict = {} - if "volume" in field_dict: - new_field_dict["volume"] = field_dict["volume"] + if "V" in field_dict: + new_field_dict["V"] = field_dict["V"] field_dict = new_field_dict gh_history[itime], field_dict = compute_gas_holdup( - case_path, time_str_sorted[itime], nCells, field_dict + case_path, time_str_sorted[itime], nCells, volume_time="0", field_dict=field_dict ) - co2_history[itime], field_dict = co2liq( - case_path, time_str_sorted[itime], nCells, field_dict + co2_history[itime], field_dict = compute_ave_y_liq( + case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="CO2", field_dict=field_dict ) - h2_history[itime], field_dict = h2liq( - case_path, time_str_sorted[itime], nCells, field_dict + h2_history[itime], field_dict = compute_ave_y_liq( + case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="H2", field_dict=field_dict ) - liqvol_history[itime], field_dict = vol_liq( - case_path, time_str_sorted[itime], nCells, field_dict + c_co2_history[itime], field_dict = compute_ave_conc_liq( + case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="CO2", mol_weight=0.04401, field_dict=field_dict ) - c_co2_history[itime], c_h2_history[itime], field_dict = cliq( - case_path, time_str_sorted[itime], nCells, field_dict + c_h2_history[itime], field_dict = compute_ave_conc_liq( + case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="H2", mol_weight=0.002016, field_dict=field_dict ) - os.makedirs(dataFolder, exist_ok=True) os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) np.savez( - os.path.join(dataFolder, case_name, "conv.npz"), + os.path.join(dataFolder, case_name, "conv2.npz"), time=np.array(time_float_sorted), gh=gh_history, co2=co2_history, h2=h2_history, - vol_liq=liqvol_history, c_h2=c_h2_history, c_co2=c_co2_history, ) From 4eff33d2e7e28ecedd3addfb75f2a6dc08e49719 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 16:34:04 -0600 Subject: [PATCH 66/86] format --- .../writeGlobalVars.py | 6 ++- fixFormat.sh | 3 +- tests/preprocess/test_case_gen.py | 6 +-- .../loop_reactor_mixing/read_history.py | 39 +++++++++++++++---- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py index 0594eccc..3445bdb8 100644 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py +++ b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/writeGlobalVars.py @@ -34,10 +34,12 @@ def readInletArea(): def getLiqVol(): cellCentres = readMesh(os.path.join(".", f"meshCellCentres_0.obj")) - volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres)) + volume_field = readOFScal(os.path.join("0", "V"), len(cellCentres))[ + "field" + ] alpha_field = readOFScal( os.path.join("0", "alpha.liquid"), len(cellCentres) - ) + )["field"] return np.sum(volume_field * alpha_field) diff --git a/fixFormat.sh b/fixFormat.sh index 9f62dae5..26867ddc 100644 --- a/fixFormat.sh +++ b/fixFormat.sh @@ -1,5 +1,4 @@ source .github/linters/formatting.sh -format bird -format applications +format . diff --git a/tests/preprocess/test_case_gen.py b/tests/preprocess/test_case_gen.py index 1637d140..dc83b37b 100644 --- a/tests/preprocess/test_case_gen.py +++ b/tests/preprocess/test_case_gen.py @@ -2,6 +2,7 @@ import pickle import shutil from pathlib import Path + import numpy as np from bird.preprocess.json_gen.design_io import * @@ -17,7 +18,7 @@ def test_continuous_loop(): "bird", "preprocess", "data_case_gen", - ) + ) generate_single_scaledup_reactor_sparger_cases( sparger_locs=[0.3, 0.5, 1.4], sim_id=0, @@ -44,7 +45,6 @@ def test_continuous_loop(): def test_discrete_loop(): - BIRD_CASE_GEN_DATA_DIR = os.path.join( Path(__file__).parent, "..", @@ -52,7 +52,7 @@ def test_discrete_loop(): "bird", "preprocess", "data_case_gen", - ) + ) def optimization_setup(): # spots on the branches where we can place sparger or mixers diff --git a/tutorial_cases/loop_reactor_mixing/read_history.py b/tutorial_cases/loop_reactor_mixing/read_history.py index 8246ed46..8d92ac65 100644 --- a/tutorial_cases/loop_reactor_mixing/read_history.py +++ b/tutorial_cases/loop_reactor_mixing/read_history.py @@ -5,9 +5,8 @@ import numpy as np from prettyPlot.plotting import plt, pretty_labels -from bird.utilities.ofio import * from bird.postprocess.post_quantities import * - +from bird.utilities.ofio import * parser = argparse.ArgumentParser(description="Convergence of GH") parser.add_argument( @@ -61,19 +60,45 @@ new_field_dict["V"] = field_dict["V"] field_dict = new_field_dict gh_history[itime], field_dict = compute_gas_holdup( - case_path, time_str_sorted[itime], nCells, volume_time="0", field_dict=field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + field_dict=field_dict, ) co2_history[itime], field_dict = compute_ave_y_liq( - case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="CO2", field_dict=field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="CO2", + field_dict=field_dict, ) h2_history[itime], field_dict = compute_ave_y_liq( - case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="H2", field_dict=field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="H2", + field_dict=field_dict, ) c_co2_history[itime], field_dict = compute_ave_conc_liq( - case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="CO2", mol_weight=0.04401, field_dict=field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="CO2", + mol_weight=0.04401, + field_dict=field_dict, ) c_h2_history[itime], field_dict = compute_ave_conc_liq( - case_path, time_str_sorted[itime], nCells, volume_time="0", spec_name="H2", mol_weight=0.002016, field_dict=field_dict + case_path, + time_str_sorted[itime], + nCells, + volume_time="0", + spec_name="H2", + mol_weight=0.002016, + field_dict=field_dict, ) os.makedirs(dataFolder, exist_ok=True) From 025be6aca5495a729e2e87124f2319b1508ac67d Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 16:42:22 -0600 Subject: [PATCH 67/86] test everything --- .github/workflows/ci.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05effe9c..452df070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,15 +67,8 @@ jobs: pip install --upgrade pip pip install . pip install pytest - - name: Test preprocess - run: | - pytest tests/preprocess - - name: Test mesh - run: | - pytest tests/meshing - - name: Test postprocessing - run: | - pytest tests/postprocess + - name: Test + run: pytest . Test-pypi-Bird: name: Test-pypi-BiRD (${{ matrix.python-version }}, ${{ matrix.os }}) From b07d60089df4cb48c41ac448e98fe98ab1df1ab1 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 10 Jul 2025 16:50:06 -0600 Subject: [PATCH 68/86] specify nrel-bird version in ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 452df070..e31cf77a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install nrel-bird + pip install nrel-bird==0.0.26 pip install pytest - name: Test run: pytest . From f4c04e1495fad3fc4e08c84994447797afdb7d33 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 09:00:25 -0600 Subject: [PATCH 69/86] make sure prestep and run fail if any command fails --- tutorial_cases/FlatPanel_250L_ASU/Allclean | 24 ++++++++++++++ tutorial_cases/FlatPanel_250L_ASU/presteps.sh | 5 +++ tutorial_cases/FlatPanel_250L_ASU/run.sh | 5 +++ tutorial_cases/airlift_40m/Allclean | 24 ++++++++++++++ tutorial_cases/airlift_40m/presteps.sh | 5 +++ tutorial_cases/airlift_40m/run.sh | 7 ++++- .../bdofoam_cases/nonreact/Allclean | 25 ++++++++++++--- tutorial_cases/bdofoam_cases/react/Allclean | 25 ++++++++++++--- tutorial_cases/bubble_column_20L/Allclean | 28 ++++++++++------- tutorial_cases/bubble_column_20L/presteps.sh | 5 +++ tutorial_cases/bubble_column_20L/run.sh | 5 +++ tutorial_cases/loop_reactor_mixing/Allclean | 29 ++++++++++------- .../loop_reactor_mixing/presteps.sh | 5 +++ tutorial_cases/loop_reactor_mixing/run.sh | 5 +++ tutorial_cases/loop_reactor_reacting/Allclean | 29 ++++++++++------- tutorial_cases/loop_reactor_reacting/run.sh | 4 +++ tutorial_cases/side_sparger/Allclean | 30 +++++++++++------- tutorial_cases/side_sparger/run.sh | 5 +++ tutorial_cases/stirred_tank/Allclean | 31 ++++++++++++------- tutorial_cases/stirred_tank/run.sh | 5 +++ 20 files changed, 234 insertions(+), 67 deletions(-) create mode 100755 tutorial_cases/FlatPanel_250L_ASU/Allclean create mode 100755 tutorial_cases/airlift_40m/Allclean diff --git a/tutorial_cases/FlatPanel_250L_ASU/Allclean b/tutorial_cases/FlatPanel_250L_ASU/Allclean new file mode 100755 index 00000000..dc2f77db --- /dev/null +++ b/tutorial_cases/FlatPanel_250L_ASU/Allclean @@ -0,0 +1,24 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt + +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/FlatPanel_250L_ASU/presteps.sh b/tutorial_cases/FlatPanel_250L_ASU/presteps.sh index 238ba587..d1a0cf4c 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/presteps.sh +++ b/tutorial_cases/FlatPanel_250L_ASU/presteps.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + cp -r 0.orig 0 m4 ./system/panel.m4 > ./system/blockMeshDict blockMesh diff --git a/tutorial_cases/FlatPanel_250L_ASU/run.sh b/tutorial_cases/FlatPanel_250L_ASU/run.sh index bef3c727..c48291e4 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/run.sh +++ b/tutorial_cases/FlatPanel_250L_ASU/run.sh @@ -1,2 +1,7 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + bash presteps.sh birdmultiphaseEulerFoam diff --git a/tutorial_cases/airlift_40m/Allclean b/tutorial_cases/airlift_40m/Allclean new file mode 100755 index 00000000..dc2f77db --- /dev/null +++ b/tutorial_cases/airlift_40m/Allclean @@ -0,0 +1,24 @@ +#!/bin/sh +cd ${0%/*} || exit 1 # Run from this directory + +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt + +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/airlift_40m/presteps.sh b/tutorial_cases/airlift_40m/presteps.sh index 06433b60..9e615bf8 100644 --- a/tutorial_cases/airlift_40m/presteps.sh +++ b/tutorial_cases/airlift_40m/presteps.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + m4 system/conc_cylinder_mesh.m4 > system/blockMeshDict rm -rf 0 cp -r 0.org 0 diff --git a/tutorial_cases/airlift_40m/run.sh b/tutorial_cases/airlift_40m/run.sh index f79cc8d4..c48291e4 100644 --- a/tutorial_cases/airlift_40m/run.sh +++ b/tutorial_cases/airlift_40m/run.sh @@ -1,2 +1,7 @@ -. ./presteps.sh +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + +bash presteps.sh birdmultiphaseEulerFoam diff --git a/tutorial_cases/bdofoam_cases/nonreact/Allclean b/tutorial_cases/bdofoam_cases/nonreact/Allclean index b44622ae..e9221067 100644 --- a/tutorial_cases/bdofoam_cases/nonreact/Allclean +++ b/tutorial_cases/bdofoam_cases/nonreact/Allclean @@ -1,11 +1,26 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +[ -d "fluentInterface" ] && rm -rf "rm -f fluentInterface" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt rm -f constant/polyMesh/boundary -rm -rf fluentInterface -cleanCase -# ----------------------------------------------------------------- end-of-file +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/bdofoam_cases/react/Allclean b/tutorial_cases/bdofoam_cases/react/Allclean index b44622ae..e9221067 100644 --- a/tutorial_cases/bdofoam_cases/react/Allclean +++ b/tutorial_cases/bdofoam_cases/react/Allclean @@ -1,11 +1,26 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +[ -d "fluentInterface" ] && rm -rf "rm -f fluentInterface" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt rm -f constant/polyMesh/boundary -rm -rf fluentInterface -cleanCase -# ----------------------------------------------------------------- end-of-file +#------------------------------------------------------------------------------ diff --git a/tutorial_cases/bubble_column_20L/Allclean b/tutorial_cases/bubble_column_20L/Allclean index f55e0ec9..dc2f77db 100755 --- a/tutorial_cases/bubble_column_20L/Allclean +++ b/tutorial_cases/bubble_column_20L/Allclean @@ -1,18 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase +# Remove 0 +[ -d "0" ] && rm -rf 0 -#rm *.obj -#rm *.stl +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt #------------------------------------------------------------------------------ diff --git a/tutorial_cases/bubble_column_20L/presteps.sh b/tutorial_cases/bubble_column_20L/presteps.sh index 6b2d824f..99b8b62a 100755 --- a/tutorial_cases/bubble_column_20L/presteps.sh +++ b/tutorial_cases/bubble_column_20L/presteps.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + # Clean case module load anaconda3/2023 conda activate /projects/gas2fuels/conda_env/bird diff --git a/tutorial_cases/bubble_column_20L/run.sh b/tutorial_cases/bubble_column_20L/run.sh index 56d304e8..82d4b5e4 100755 --- a/tutorial_cases/bubble_column_20L/run.sh +++ b/tutorial_cases/bubble_column_20L/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + # Clean case ./Allclean diff --git a/tutorial_cases/loop_reactor_mixing/Allclean b/tutorial_cases/loop_reactor_mixing/Allclean index 6c023512..dc2f77db 100755 --- a/tutorial_cases/loop_reactor_mixing/Allclean +++ b/tutorial_cases/loop_reactor_mixing/Allclean @@ -1,19 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase +# Remove 0 +[ -d "0" ] && rm -rf 0 -rm *.obj -rm *.stl -rm *.txt +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt #------------------------------------------------------------------------------ diff --git a/tutorial_cases/loop_reactor_mixing/presteps.sh b/tutorial_cases/loop_reactor_mixing/presteps.sh index e111fe88..8ec4d15a 100644 --- a/tutorial_cases/loop_reactor_mixing/presteps.sh +++ b/tutorial_cases/loop_reactor_mixing/presteps.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + # Clean case module load anaconda3/2023 conda activate /projects/gas2fuels/conda_env/bird diff --git a/tutorial_cases/loop_reactor_mixing/run.sh b/tutorial_cases/loop_reactor_mixing/run.sh index c910f3c3..9864aaaf 100644 --- a/tutorial_cases/loop_reactor_mixing/run.sh +++ b/tutorial_cases/loop_reactor_mixing/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + # Clean case #module load anaconda3/2023 #conda activate /projects/gas2fuels/conda_env/bird diff --git a/tutorial_cases/loop_reactor_reacting/Allclean b/tutorial_cases/loop_reactor_reacting/Allclean index 371d82a8..dc2f77db 100755 --- a/tutorial_cases/loop_reactor_reacting/Allclean +++ b/tutorial_cases/loop_reactor_reacting/Allclean @@ -1,17 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase -rm *.obj -rm *.stl +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt #------------------------------------------------------------------------------ diff --git a/tutorial_cases/loop_reactor_reacting/run.sh b/tutorial_cases/loop_reactor_reacting/run.sh index b408d12f..54af60e0 100644 --- a/tutorial_cases/loop_reactor_reacting/run.sh +++ b/tutorial_cases/loop_reactor_reacting/run.sh @@ -1,3 +1,7 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR # Clean case ./Allclean diff --git a/tutorial_cases/side_sparger/Allclean b/tutorial_cases/side_sparger/Allclean index b00a24a7..dc2f77db 100755 --- a/tutorial_cases/side_sparger/Allclean +++ b/tutorial_cases/side_sparger/Allclean @@ -1,16 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase -#rm system/blockMeshDict -rm *.obj #------------------------------------------------------------------------------ diff --git a/tutorial_cases/side_sparger/run.sh b/tutorial_cases/side_sparger/run.sh index 8f029538..9cd2e36e 100755 --- a/tutorial_cases/side_sparger/run.sh +++ b/tutorial_cases/side_sparger/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" diff --git a/tutorial_cases/stirred_tank/Allclean b/tutorial_cases/stirred_tank/Allclean index e9225d17..dc2f77db 100755 --- a/tutorial_cases/stirred_tank/Allclean +++ b/tutorial_cases/stirred_tank/Allclean @@ -1,15 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions - -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase -rm system/blockMeshDict +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt + #------------------------------------------------------------------------------ diff --git a/tutorial_cases/stirred_tank/run.sh b/tutorial_cases/stirred_tank/run.sh index 307e7b84..e2d95769 100644 --- a/tutorial_cases/stirred_tank/run.sh +++ b/tutorial_cases/stirred_tank/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" From 8e41c00aa83f26f94edcc9334d13c69ef93bb430 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 09:06:52 -0600 Subject: [PATCH 70/86] fail fast experimental cases --- experimental_cases/deckwer17/Allclean | 31 ++++++++++++------- experimental_cases/deckwer17/run.sh | 5 +++ experimental_cases/deckwer19/Allclean | 31 ++++++++++++------- experimental_cases/deckwer19/run.sh | 5 +++ .../bubble_column_pbe_20L/Allclean | 28 ++++++++++------- .../bubble_column_pbe_20L/presteps.sh | 5 +++ .../bdofoam_cases/nonreact/Allclean | 0 tutorial_cases/bdofoam_cases/react/Allclean | 0 8 files changed, 72 insertions(+), 33 deletions(-) mode change 100644 => 100755 tutorial_cases/bdofoam_cases/nonreact/Allclean mode change 100644 => 100755 tutorial_cases/bdofoam_cases/react/Allclean diff --git a/experimental_cases/deckwer17/Allclean b/experimental_cases/deckwer17/Allclean index f4c6f3db..dc2f77db 100755 --- a/experimental_cases/deckwer17/Allclean +++ b/experimental_cases/deckwer17/Allclean @@ -1,15 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions - -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase -#rm system/blockMeshDict +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt + #------------------------------------------------------------------------------ diff --git a/experimental_cases/deckwer17/run.sh b/experimental_cases/deckwer17/run.sh index 599e5a5b..4bb7ca5f 100755 --- a/experimental_cases/deckwer17/run.sh +++ b/experimental_cases/deckwer17/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" diff --git a/experimental_cases/deckwer19/Allclean b/experimental_cases/deckwer19/Allclean index e9225d17..dc2f77db 100755 --- a/experimental_cases/deckwer19/Allclean +++ b/experimental_cases/deckwer19/Allclean @@ -1,15 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions - -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase -rm system/blockMeshDict +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi + +# Remove 0 +[ -d "0" ] && rm -rf 0 + +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt + #------------------------------------------------------------------------------ diff --git a/experimental_cases/deckwer19/run.sh b/experimental_cases/deckwer19/run.sh index 599e5a5b..4bb7ca5f 100755 --- a/experimental_cases/deckwer19/run.sh +++ b/experimental_cases/deckwer19/run.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" diff --git a/experimental_cases/disengagement/bubble_column_pbe_20L/Allclean b/experimental_cases/disengagement/bubble_column_pbe_20L/Allclean index f55e0ec9..dc2f77db 100755 --- a/experimental_cases/disengagement/bubble_column_pbe_20L/Allclean +++ b/experimental_cases/disengagement/bubble_column_pbe_20L/Allclean @@ -1,18 +1,24 @@ #!/bin/sh cd ${0%/*} || exit 1 # Run from this directory -# Source tutorial clean functions -. $WM_PROJECT_DIR/bin/tools/CleanFunctions +if [ -n "$WM_PROJECT_DIR" ]; then + . $WM_PROJECT_DIR/bin/tools/CleanFunctions + cleanCase +else + echo "WARNING: could not run cleanCase, OpenFOAM env not found" +fi -# Remove surface, features and solution -#rm -rf constant/extendedFeatureEdgeMesh > /dev/null 2>&1 -#rm -f constant/triSurface/*.eMesh > /dev/null 2>&1 -#rm -rf constant/polyMesh > /dev/null 2>&1 -#rm -rf processor* > /dev/null 2>&1 -rm -rf 0 -cleanCase +# Remove 0 +[ -d "0" ] && rm -rf 0 -#rm *.obj -#rm *.stl +# rm -f constant/triSurface/*.eMesh +# [ -d "constant/extendedFeatureEdgeMesh" ] && rm -rf "constant/extendedFeatureEdgeMesh" +[ -d "constant/polyMesh" ] && rm -rf "constant/polyMesh" +[ -d "dynamicCode" ] && rm -rf "dynamicCode" +[ -d "processor*" ] && rm -rf "processor*" +# rm -f constant/fvModels +rm -f *.obj +rm -f *.stl +rm -f *.txt #------------------------------------------------------------------------------ diff --git a/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh b/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh index 63e1b33b..fc4e90f4 100755 --- a/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh +++ b/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh @@ -1,3 +1,8 @@ +#!/bin/bash +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + # Clean case module load conda conda activate bird diff --git a/tutorial_cases/bdofoam_cases/nonreact/Allclean b/tutorial_cases/bdofoam_cases/nonreact/Allclean old mode 100644 new mode 100755 diff --git a/tutorial_cases/bdofoam_cases/react/Allclean b/tutorial_cases/bdofoam_cases/react/Allclean old mode 100644 new mode 100755 From e3f2ee9ff02f7eaa4221a4547c081a3767185d77 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 09:11:24 -0600 Subject: [PATCH 71/86] we do not use force sign anymore --- tutorial_cases/loop_reactor_mixing/presteps.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial_cases/loop_reactor_mixing/presteps.sh b/tutorial_cases/loop_reactor_mixing/presteps.sh index 8ec4d15a..4a19534b 100644 --- a/tutorial_cases/loop_reactor_mixing/presteps.sh +++ b/tutorial_cases/loop_reactor_mixing/presteps.sh @@ -19,8 +19,8 @@ python /projects/gas2fuels/BioReactorDesign/applications/write_stl_patch.py -i s #python ../../../applications/write_stl_patch.py -i system/inlets_outlets.json # Generate mixers -python /projects/gas2fuels/BioReactorDesign/applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant -#python ../../../applications/write_dynMix_fvModels_force_sign.py -i system/mixers.json -o constant +python /projects/gas2fuels/BioReactorDesign/applications/write_dynMix_fvModels.py -fs -i system/mixers.json -o constant +#python ../../../applications/write_dynMix_fvModels.py -fs -i system/mixers.json -o constant echo PRESTEP 2 # Mesh gen From 66f0c07b24fc417ae6ae85d0b9556dcfa93809c3 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 09:51:51 -0600 Subject: [PATCH 72/86] add boiler plate post process doc --- .github/workflows/ci.yml | 2 +- README.md | 15 +++++++++------ docs/source/pythonInterface_tut.rst | 29 +++++++++++++++++++++++++++++ docs/source/tutorials.rst | 15 +++++++++++++-- 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 docs/source/pythonInterface_tut.rst diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e31cf77a..452df070 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,7 +89,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install nrel-bird==0.0.26 + pip install nrel-bird pip install pytest - name: Test run: pytest . diff --git a/README.md b/README.md index 6d8301d2..c28a1a3b 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,15 @@ To cite BiRD, please use these articles on [CO2 interphase mass transfer](https: ``` -@article{hassanaly2025inverse, - title={Bayesian calibration of bubble size dynamics applied to \ce{CO2} gas fermenters}, - author={Hassanaly, Malik and Parra-Alvarez, John M. and Rahimi, Mohammad J., Municchi, Federico and Sitaraman, Hariswaran}, - journal={Chemical Engineering Research and Design}, - year={2025}, - } +@article{hassanaly2025bayesian, + title={Bayesian calibration of bubble size dynamics applied to CO2 gas fermenters}, + author={Hassanaly, Malik and Parra-Alvarez, John M and Rahimi, Mohammad J and Municchi, Federico and Sitaraman, Hariswaran}, + journal={Chemical Engineering Research and Design}, + volume={215}, + pages={312--328}, + year={2025}, + publisher={Elsevier} +} @article{rahimi2018computational, title={Computational fluid dynamics study of full-scale aerobic bioreactors: Evaluation of gas--liquid mass transfer, oxygen uptake, and dynamic oxygen distribution}, diff --git a/docs/source/pythonInterface_tut.rst b/docs/source/pythonInterface_tut.rst new file mode 100644 index 00000000..d203b63d --- /dev/null +++ b/docs/source/pythonInterface_tut.rst @@ -0,0 +1,29 @@ +Post processing with the python interface to OpenFOAM +===== + +This tutorial demonstrates how to post process a case with the python interface to OpenFOAM provided in BiRD. + +Manipulate fields with python +------------ + +Plot conditional means +------------ + + +Compute reactor properties +------------ + + +Compute kLa with uncertainty estimates +------------ + + +Perform early prediction +------------ + + + + + + + diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 1d19efcf..b93bbbbf 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -7,9 +7,20 @@ Tutorials ********* .. toctree:: - :maxdepth: 3 - :caption: Tutorials contents: + :maxdepth: 1 + :caption: Bioreactor cases bubbleColumn.rst + +.. toctree:: + :maxdepth: 2 + :caption: Post processing OpenFOAM cases + + pythonInterface_tut.rst + +.. toctree:: + :maxdepth: 1 + :caption: Inverse problem utilities + calibration_normal_beta.rst calibration_bsd.rst From 5b21d40a5ae824c3cf47e5816e032f60f9c6e466 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 10:19:43 -0600 Subject: [PATCH 73/86] replace conv2 with conv in the new read history scripts --- .../read_history.py | 4 ++-- tutorial_cases/loop_reactor_mixing/read_history.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py index 8d92ac65..4dd134d6 100644 --- a/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py +++ b/bird/preprocess/data_case_gen/loop_reactor_pbe_dynmix_nonstat_headbranch_scaleup/read_history.py @@ -35,7 +35,7 @@ case_path = "." dataFolder = args.data_folder -if os.path.isfile(os.path.join(dataFolder, case_name, "conv2.npz")): +if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): sys.exit("WARNING: History already created, Skipping") time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) @@ -104,7 +104,7 @@ os.makedirs(dataFolder, exist_ok=True) os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) np.savez( - os.path.join(dataFolder, case_name, "conv2.npz"), + os.path.join(dataFolder, case_name, "conv.npz"), time=np.array(time_float_sorted), gh=gh_history, co2=co2_history, diff --git a/tutorial_cases/loop_reactor_mixing/read_history.py b/tutorial_cases/loop_reactor_mixing/read_history.py index 8d92ac65..4dd134d6 100644 --- a/tutorial_cases/loop_reactor_mixing/read_history.py +++ b/tutorial_cases/loop_reactor_mixing/read_history.py @@ -35,7 +35,7 @@ case_path = "." dataFolder = args.data_folder -if os.path.isfile(os.path.join(dataFolder, case_name, "conv2.npz")): +if os.path.isfile(os.path.join(dataFolder, case_name, "conv.npz")): sys.exit("WARNING: History already created, Skipping") time_float_sorted, time_str_sorted = getCaseTimes(case_path, remove_zero=True) @@ -104,7 +104,7 @@ os.makedirs(dataFolder, exist_ok=True) os.makedirs(os.path.join(dataFolder, case_name), exist_ok=True) np.savez( - os.path.join(dataFolder, case_name, "conv2.npz"), + os.path.join(dataFolder, case_name, "conv.npz"), time=np.array(time_float_sorted), gh=gh_history, co2=co2_history, From 77decd14f697de1f28f46d16c8860c328d220b3b Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 10:35:59 -0600 Subject: [PATCH 74/86] do the trap after module load (seems like a lot of failure can happen there --- experimental_cases/deckwer17/run.sh | 9 +++++---- .../disengagement/bubble_column_pbe_20L/presteps.sh | 9 ++++++--- tutorial_cases/FlatPanel_250L_ASU/run.sh | 4 ---- tutorial_cases/airlift_40m/run.sh | 4 ---- tutorial_cases/bubble_column_20L/presteps.sh | 11 +++++++---- tutorial_cases/bubble_column_20L/run.sh | 6 ++++-- tutorial_cases/loop_reactor_mixing/presteps.sh | 10 ++++++---- tutorial_cases/loop_reactor_mixing/run.sh | 9 +++++---- tutorial_cases/loop_reactor_reacting/run.sh | 6 ++++-- tutorial_cases/side_sparger/run.sh | 9 +++++---- tutorial_cases/stirred_tank/run.sh | 9 +++++---- 11 files changed, 47 insertions(+), 39 deletions(-) diff --git a/experimental_cases/deckwer17/run.sh b/experimental_cases/deckwer17/run.sh index 4bb7ca5f..7c667d34 100755 --- a/experimental_cases/deckwer17/run.sh +++ b/experimental_cases/deckwer17/run.sh @@ -1,8 +1,4 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" @@ -11,6 +7,11 @@ else ./Allclean fi +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + if ! type "python" &> /dev/null; then echo " could not be found" echo "Skipping Mesh generation" diff --git a/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh b/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh index fc4e90f4..f7db7b17 100755 --- a/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh +++ b/experimental_cases/disengagement/bubble_column_pbe_20L/presteps.sh @@ -1,7 +1,4 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR # Clean case module load conda @@ -10,6 +7,12 @@ module load openfoam/9-craympich #source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc ./Allclean + +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + BIRD_HOME="/home/fmunicch/BioReactorDesign/bird" diff --git a/tutorial_cases/FlatPanel_250L_ASU/run.sh b/tutorial_cases/FlatPanel_250L_ASU/run.sh index c48291e4..ea9ed020 100644 --- a/tutorial_cases/FlatPanel_250L_ASU/run.sh +++ b/tutorial_cases/FlatPanel_250L_ASU/run.sh @@ -1,7 +1,3 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - bash presteps.sh birdmultiphaseEulerFoam diff --git a/tutorial_cases/airlift_40m/run.sh b/tutorial_cases/airlift_40m/run.sh index c48291e4..ea9ed020 100644 --- a/tutorial_cases/airlift_40m/run.sh +++ b/tutorial_cases/airlift_40m/run.sh @@ -1,7 +1,3 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - bash presteps.sh birdmultiphaseEulerFoam diff --git a/tutorial_cases/bubble_column_20L/presteps.sh b/tutorial_cases/bubble_column_20L/presteps.sh index 99b8b62a..1ac2a84c 100755 --- a/tutorial_cases/bubble_column_20L/presteps.sh +++ b/tutorial_cases/bubble_column_20L/presteps.sh @@ -1,14 +1,17 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR # Clean case -module load anaconda3/2023 +module load conda conda activate /projects/gas2fuels/conda_env/bird source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc ./Allclean + +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + BIRD_HOME=`python -c "import bird; print(bird.BIRD_DIR)"` diff --git a/tutorial_cases/bubble_column_20L/run.sh b/tutorial_cases/bubble_column_20L/run.sh index 82d4b5e4..4a76e6ad 100755 --- a/tutorial_cases/bubble_column_20L/run.sh +++ b/tutorial_cases/bubble_column_20L/run.sh @@ -1,10 +1,12 @@ #!/bin/bash + +# Clean case +./Allclean + set -e # Exit on any error # Define what to do on error trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR -# Clean case -./Allclean echo PRESTEP 1 # Generate blockmeshDict diff --git a/tutorial_cases/loop_reactor_mixing/presteps.sh b/tutorial_cases/loop_reactor_mixing/presteps.sh index 4a19534b..fde6546f 100644 --- a/tutorial_cases/loop_reactor_mixing/presteps.sh +++ b/tutorial_cases/loop_reactor_mixing/presteps.sh @@ -1,14 +1,16 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR # Clean case -module load anaconda3/2023 +module load conda conda activate /projects/gas2fuels/conda_env/bird source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc ./Allclean +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + echo PRESTEP 1 # Generate blockmeshDict python /projects/gas2fuels/BioReactorDesign/applications/write_block_rect_mesh.py -i system/mesh.json -o system diff --git a/tutorial_cases/loop_reactor_mixing/run.sh b/tutorial_cases/loop_reactor_mixing/run.sh index 9864aaaf..19458495 100644 --- a/tutorial_cases/loop_reactor_mixing/run.sh +++ b/tutorial_cases/loop_reactor_mixing/run.sh @@ -1,14 +1,15 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - # Clean case #module load anaconda3/2023 #conda activate /projects/gas2fuels/conda_env/bird #source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc ./Allclean +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + echo PRESTEP 1 # Generate blockmeshDict python ../../applications/write_block_rect_mesh.py -i system/mesh.json -o system diff --git a/tutorial_cases/loop_reactor_reacting/run.sh b/tutorial_cases/loop_reactor_reacting/run.sh index 54af60e0..1749dd3f 100644 --- a/tutorial_cases/loop_reactor_reacting/run.sh +++ b/tutorial_cases/loop_reactor_reacting/run.sh @@ -1,10 +1,12 @@ #!/bin/bash + +# Clean case +./Allclean + set -e # Exit on any error # Define what to do on error trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR -# Clean case -./Allclean # Generate blockmeshDict python ../../applications/write_block_rect_mesh.py -i system/mesh.json -o system diff --git a/tutorial_cases/side_sparger/run.sh b/tutorial_cases/side_sparger/run.sh index 9cd2e36e..b88b1c3d 100755 --- a/tutorial_cases/side_sparger/run.sh +++ b/tutorial_cases/side_sparger/run.sh @@ -1,8 +1,4 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" @@ -11,6 +7,11 @@ else ./Allclean fi +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + if ! type "python" &> /dev/null; then echo " could not be found" echo "Skipping Mesh generation" diff --git a/tutorial_cases/stirred_tank/run.sh b/tutorial_cases/stirred_tank/run.sh index e2d95769..6e4b240b 100644 --- a/tutorial_cases/stirred_tank/run.sh +++ b/tutorial_cases/stirred_tank/run.sh @@ -1,8 +1,4 @@ #!/bin/bash -set -e # Exit on any error -# Define what to do on error -trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR - if ! type "blockMesh" &> /dev/null; then echo " could not be found" echo "OpenFoam is likely not installed, skipping run" @@ -11,6 +7,11 @@ else ./Allclean fi +set -e # Exit on any error +# Define what to do on error +trap 'echo "ERROR: Something failed! Running cleanup..."; ./Allclean' ERR + + if ! type "python" &> /dev/null; then echo " could not be found" echo "Skipping Mesh generation" From e9beae01f407df75653f6cda257a4121d9ea22ca Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:43:43 -0600 Subject: [PATCH 75/86] setup logger and init it once --- bird/__init__.py | 4 ++++ bird/logging_config.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 bird/logging_config.py diff --git a/bird/__init__.py b/bird/__init__.py index 6eb11de8..3835ca6e 100644 --- a/bird/__init__.py +++ b/bird/__init__.py @@ -37,3 +37,7 @@ BIRD_KLA_DATA_DIR = os.path.join(BIRD_POST_DIR, "data_kla") BIRD_CASE_GEN_DATA_DIR = os.path.join(BIRD_PRE_DIR, "data_case_gen") BIRD_INV_DIR = os.path.join(BIRD_DIR, "inverse_modeling") + +from bird.logging_config import setup_logging + +setup_logging(level="INFO") diff --git a/bird/logging_config.py b/bird/logging_config.py new file mode 100644 index 00000000..89cabf90 --- /dev/null +++ b/bird/logging_config.py @@ -0,0 +1,34 @@ +import logging +from pathlib import Path + + +class ConditionalFormatter(logging.Formatter): + def format(self, record): + """Change how to log if it is an INFO or a WARNING/ERROR""" + if record.levelno >= logging.WARNING: + self._style._fmt = ( + "%(asctime)s [%(levelname)s] %(name)s: %(message)s" + ) + else: + self._style._fmt = "%(asctime)s [%(levelname)s] bird: %(message)s" + return super().format(record) + + +def setup_logging(logfile: str | None = None, level=logging.INFO): + """Setup logging""" + + logger = logging.getLogger() + logger.setLevel(level) + + formatter = ConditionalFormatter() + + # Console handler + ch = logging.StreamHandler() + ch.setFormatter(formatter) + logger.addHandler(ch) + + # Optional file handler + if logfile: + fh = logging.FileHandler(Path(logfile)) + fh.setFormatter(formatter) + logger.addHandler(fh) From 72c7c73109da01d96f403833f5a9fd1c882b516c Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:44:41 -0600 Subject: [PATCH 76/86] get rid of verbose argument when just used for printing --- bird/utilities/folderManagement.py | 8 ++++++-- bird/utilities/label_plot.py | 6 +++++- bird/utilities/mathtools.py | 11 ++++++++--- bird/utilities/ofio.py | 16 +++++++--------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/bird/utilities/folderManagement.py b/bird/utilities/folderManagement.py index 380a1497..38adf933 100644 --- a/bird/utilities/folderManagement.py +++ b/bird/utilities/folderManagement.py @@ -1,6 +1,9 @@ +import logging import os import re +logger = logging.getLogger(__name__) + def makeRecursiveFolder(path): folder_list = path.split("/") @@ -22,8 +25,9 @@ def getManyFolders(rootFolder, prefix="flat_donut"): for entry in fold_tmp: num = re.findall(r"\d+", entry) if len(num) > 1: - print(f"WARNING: Cannot find num of folder {entry}.") - print("Do not trust the spearman stat") + msg = f"Cannot find num of folder {entry}." + msg += "\nDo not trust the spearman stat" + logger.warning(msg) else: fold_num.append(int(num[0])) diff --git a/bird/utilities/label_plot.py b/bird/utilities/label_plot.py index f19012ed..17e50ceb 100644 --- a/bird/utilities/label_plot.py +++ b/bird/utilities/label_plot.py @@ -1,5 +1,9 @@ +import logging + from prettyPlot.plotting import plt, pretty_labels, pretty_legend +logger = logging.getLogger(__name__) + def label_conv(input_string): if input_string.lower() == "width": @@ -52,5 +56,5 @@ def label_conv(input_string): elif input_string.lower() == "gh_height": return "Height-based gas holdup" else: - print(input_string) + logger.info(input_string) return input_string diff --git a/bird/utilities/mathtools.py b/bird/utilities/mathtools.py index e75cfec3..8d9bd017 100644 --- a/bird/utilities/mathtools.py +++ b/bird/utilities/mathtools.py @@ -1,13 +1,18 @@ +import logging + import numpy as np +logger = logging.getLogger(__name__) + def conditionalAverage(x, y, nbin): try: assert len(x) == len(y) except AssertionError: - print("conditional average x and y have different dimension") - print("dim x = ", len(x)) - print("dim y = ", len(y)) + error_msg = "conditional average x and y have different dimension" + error_msg += f"\ndim x = {len(x)}" + error_msg += f"\ndim y = {len(y)}" + logger.error(error_msg) sys.exit() # Bin conditional space mag = np.amax(x) - np.amin(x) diff --git a/bird/utilities/ofio.py b/bird/utilities/ofio.py index 59ee0d46..fd2f1aaf 100644 --- a/bird/utilities/ofio.py +++ b/bird/utilities/ofio.py @@ -1,9 +1,12 @@ +import logging import os import re import sys import numpy as np +logger = logging.getLogger(__name__) + def readMesh(filename: str) -> np.ndarray: """ @@ -253,7 +256,7 @@ def readOFScal( try: field = np.loadtxt(filename, skiprows=n_header, max_rows=n_cells) except Exception as err: - print(f"Issue when reading {filename}") + logger.error(f"Issue when reading {filename}") print(err) sys.exit() @@ -348,7 +351,7 @@ def readOFVec( field = np.array(field).astype(float) except Exception as err: - print(f"Issue when reading {filename}") + logger.error(f"Issue when reading {filename}") print(err) sys.exit() @@ -450,9 +453,7 @@ def readSizeGroups(file): return sizeGroup, binGroup -def getCaseTimes( - casePath: str, remove_zero: bool = False, verbose: bool = True -) -> tuple: +def getCaseTimes(casePath: str, remove_zero: bool = False) -> tuple: """ Get list of all time folders from an OpenFOAM case @@ -462,8 +463,6 @@ def getCaseTimes( Path to case folder remove_zero : bool Whether to remove zero from the time folder list - verbose : bool - Whether to print what time folders are included returns ---------- @@ -483,8 +482,7 @@ def getCaseTimes( if abs(a) < 1e-12: _ = times_tmp.pop(i) except ValueError: - if verbose: - print(f"{entry} not a time folder, removing") + logger.debug(f"{entry} not a time folder, removing") a = times_tmp.pop(i) # print('removed ', a) time_float = [float(entry) for entry in times_tmp] From 5bb13af246930c9f5ffd9fbc53fb63db292257b4 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:45:38 -0600 Subject: [PATCH 77/86] get rid of verbose argument when possible --- bird/preprocess/dynamic_mixer/mixer.py | 8 +++-- .../inhomogeneousBC/util/fromMomtoPdf.py | 35 +++++++++---------- bird/preprocess/stl_patch/stl_bc.py | 7 ++-- bird/preprocess/stl_patch/stl_mesh.py | 12 ++++--- bird/preprocess/stl_patch/stl_shapes.py | 17 +++++---- 5 files changed, 46 insertions(+), 33 deletions(-) diff --git a/bird/preprocess/dynamic_mixer/mixer.py b/bird/preprocess/dynamic_mixer/mixer.py index 1cc9b8b5..bb959f80 100644 --- a/bird/preprocess/dynamic_mixer/mixer.py +++ b/bird/preprocess/dynamic_mixer/mixer.py @@ -1,5 +1,9 @@ +import logging + import numpy as np +logger = logging.getLogger(__name__) + class Mixer: def __init__(self): @@ -80,7 +84,7 @@ def check_status(self, blocks=None): ): self.ready = False else: - print( + logger.info( f"\n\tpos({self.x:.2g}, {self.y:.2g}, {self.z:.2g})" + f"\n\tnormal_dir {self.normal_dir}" + f"\n\trad {self.rad:.2g}" @@ -90,6 +94,6 @@ def check_status(self, blocks=None): + f"\n\tstart_time {self.start_time:.2g}" ) if blocks is not None: - print(f"\tbranch = {blocks}") + logger.info(f"\tbranch = {blocks}") self.ready = True diff --git a/bird/preprocess/inhomogeneousBC/util/fromMomtoPdf.py b/bird/preprocess/inhomogeneousBC/util/fromMomtoPdf.py index b1c2bddb..b6ed1a15 100644 --- a/bird/preprocess/inhomogeneousBC/util/fromMomtoPdf.py +++ b/bird/preprocess/inhomogeneousBC/util/fromMomtoPdf.py @@ -1,8 +1,11 @@ +import logging import sys import numpy as np from scipy.optimize import minimize +logger = logging.getLogger(__name__) + def bounded_constraint(x): xend = 1 - np.sum(x) @@ -64,16 +67,16 @@ def opt(meanTar, stdTar, diam): return res -def get_f_vals(meanTar, stdTar, diam, verb=True): +def get_f_vals(meanTar, stdTar, diam): if meanTar < np.amin(diam) or meanTar > np.amax(diam): - sys.exit( - f"ERROR: mean target {meanTar} out of bounds [{np.amin(diam)}, {np.amax(diam)}]" + logger.error( + f"mean target {meanTar} out of bounds [{np.amin(diam)}, {np.amax(diam)}]" ) + sys.exit() tol = 10 irep = 0 while tol > 0.01: - if verb: - print(f"Rep = {irep}, tol={tol}") + logger.debug(f"Rep = {irep}, tol={tol}") res = opt(meanTar, stdTar, diam) x = np.zeros(len(res.x) + 1) x[:-1] = res.x @@ -89,21 +92,17 @@ def get_f_vals(meanTar, stdTar, diam, verb=True): ) irep += 1 if irep > 100: - sys.exit( - "ERROR: optimization fail, typically occurs because the population balance domain is too tight" + logger.error( + "optimization fail, typically occurs because the population balance domain is too tight" ) - if verb: - print("meanTar = ", meanTar) - print("stdTar = ", stdTar) - print("x = ", x) - print("mean = ", np.sum(x * diam)) - print( - "std = ", - np.sqrt( - np.sum(np.clip(x, a_min=0, a_max=None) * (diam - meanTar) ** 2) - ), - ) + logger.debug(f"meanTar = {meanTar}") + logger.debug(f"stdTar = {stdTar}") + logger.debug(f"x = {x}") + logger.debug(f"mean = {np.sum(x * diam)}") + logger.debug( + f"std = {np.sqrt(np.sum(np.clip(x, a_min=0, a_max=None) * (diam - meanTar) ** 2))}" + ) return x diff --git a/bird/preprocess/stl_patch/stl_bc.py b/bird/preprocess/stl_patch/stl_bc.py index a14584fa..066eab6d 100644 --- a/bird/preprocess/stl_patch/stl_bc.py +++ b/bird/preprocess/stl_patch/stl_bc.py @@ -1,4 +1,5 @@ import json +import logging import sys import numpy as np @@ -7,6 +8,8 @@ from bird.meshing._mesh_tools import parseJsonFile from bird.preprocess.stl_patch.stl_shapes import * +logger = logging.getLogger(__name__) + def check_input(input_dict): assert isinstance(input_dict, dict) @@ -41,9 +44,9 @@ def write_boundaries(input_dict): check_input(input_dict) for boundary_name in input_dict.keys(): if not boundary_name == "Geometry": - print(f"Making {boundary_name}") + logger.info(f"Making {boundary_name}") boundary_mesh = get_all_vert_faces(input_dict, boundary_name) - print(f"\tArea {boundary_mesh.area} m2") + logger.info(f"\tArea {boundary_mesh.area} m2") boundary_mesh.save(f"{boundary_name}.stl") diff --git a/bird/preprocess/stl_patch/stl_mesh.py b/bird/preprocess/stl_patch/stl_mesh.py index 7a568c1c..a66cd86e 100644 --- a/bird/preprocess/stl_patch/stl_mesh.py +++ b/bird/preprocess/stl_patch/stl_mesh.py @@ -1,7 +1,11 @@ +import logging + import numpy as np import stl from scipy.spatial import Delaunay +logger = logging.getLogger(__name__) + class STLMesh: def __init__( @@ -59,20 +63,20 @@ def update(self): and not self.status["normal_dir"] and self.status["planar"] ): - print("\t\tUpdating normal_dir to") + logger.debug("\t\tUpdating normal_dir to") for i in range(3): A = np.where(self.vertices[:, i] == self.vertices[0, i])[0] if len(A) == self.vertices.shape[0]: self.normal_dir = i self.status["normal_dir"] = True - print(f"\t\t\t{i}") + logger.debug(f"\t\t\t{i}") break if ( self.status["vertices"] and not self.status["faces"] and self.status["planar"] ): - print("\t\tUpdating faces") + logger.debug("\t\tUpdating faces") points = np.zeros((self.vertices.shape[0], 2)) count = 0 for i in range(3): @@ -84,7 +88,7 @@ def update(self): self.status["faces"] = True if self.status["vertices"] and self.status["faces"]: - print("\t\tUpdating area") + logger.debug("\t\tUpdating area") self.calc_area() self.status["area"] = True diff --git a/bird/preprocess/stl_patch/stl_shapes.py b/bird/preprocess/stl_patch/stl_shapes.py index e9cc1fec..3a6c0a0b 100644 --- a/bird/preprocess/stl_patch/stl_shapes.py +++ b/bird/preprocess/stl_patch/stl_shapes.py @@ -1,4 +1,5 @@ import json +import logging import sys import numpy as np @@ -6,9 +7,11 @@ from bird.meshing.block_rect_mesh import from_block_rect_to_seg from bird.preprocess.stl_patch.stl_mesh import STLMesh +logger = logging.getLogger(__name__) + def make_polygon(rad, nvert, center, normal_dir): - print( + logger.debug( f"\tMaking polygon at ({center[0]:.4g}, {center[1]:.4g}, {center[2]:.4g})" ) theta = 2 * np.pi / nvert @@ -26,7 +29,7 @@ def make_polygon(rad, nvert, center, normal_dir): def make_rectangle(w, h, center, normal_dir): - print( + logger.debug( f"\tMaking rectangle at ({center[0]:.4g}, {center[1]:.4g}, {center[2]:.4g})" ) # Define vertices @@ -49,7 +52,7 @@ def make_rectangle(w, h, center, normal_dir): def make_circle(radius, center, normal_dir, npts=3): - print( + logger.debug( f"\tMaking circle at ({center[0]:.4g}, {center[1]:.4g}, {center[2]:.4g})" ) vertices = np.zeros((npts + 1, 3)) @@ -69,8 +72,7 @@ def make_circle(radius, center, normal_dir, npts=3): def make_spider(centerRad, nArms, widthArms, lengthArms, center, normal_dir): globalArea = 0 if nArms < 2: - print("ERROR: nArms must be >= 2") - print(f"Got nArms = {nArms}") + logger.error(f"nArms ({nArms}) must be >= 2") sys.exit() if nArms == 2: nVertPol = 4 @@ -82,8 +84,9 @@ def make_spider(centerRad, nArms, widthArms, lengthArms, center, normal_dir): vertices = center_mesh.vertices maxWidth = np.linalg.norm((vertices[1, :] - vertices[0, :])) if widthArms > maxWidth: - print("ERROR: arm width will make arms overlap") - print("Either increase center radius or reduce arm width") + error_msg = "arm width will make arms overlap" + error_msg += "\nEither increase center radius or reduce arm width" + logger.error(error_msg) sys.exit() arm_mesh_list = [] From 50bd7b4c283ca4b92639807f7ccac25099fdc105 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:47:01 -0600 Subject: [PATCH 78/86] use logger and exception when possible --- bird/calibration/param_nn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bird/calibration/param_nn.py b/bird/calibration/param_nn.py index e6622cfd..4b869149 100644 --- a/bird/calibration/param_nn.py +++ b/bird/calibration/param_nn.py @@ -1,4 +1,5 @@ import argparse +import logging import os import sys import time @@ -13,6 +14,8 @@ from sklearn.preprocessing import MinMaxScaler, StandardScaler from tensorflow.keras import initializers, layers, optimizers, regularizers +logger = logging.getLogger(__name__) + def flexible_activation(x, activation): if activation is None: @@ -36,7 +39,7 @@ def flexible_activation(x, activation): elif activation.lower() == "leakyrelu": out = layers.LeakyReLU()(x) else: - sys.exit(f"ERROR: unknown activation {activation}") + raise NotImplementedError(f"unknown activation {activation}") return out @@ -185,7 +188,7 @@ def train( gradient_threshold=None, ): if gradient_threshold is not None: - print(f"INFO: clipping gradients at {gradient_threshold:.2g}") + logger.info("clipping gradients at {gradient_threshold:.2g}") # Make sure the control file for learning rate is consistent with main.py, at least at first self.prepareLog() bestLoss = None From e865ee3b82f331bae67cc8309d2f2a9a25203996 Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:47:48 -0600 Subject: [PATCH 79/86] use logger warning and info instead of doing it by hand --- bird/meshing/_mesh_tools.py | 31 ++++++++++++++++--------------- bird/meshing/block_cyl_mesh.py | 20 +++++++++++--------- bird/meshing/stirred_tank_mesh.py | 3 --- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/bird/meshing/_mesh_tools.py b/bird/meshing/_mesh_tools.py index 4d6044f8..c437744d 100644 --- a/bird/meshing/_mesh_tools.py +++ b/bird/meshing/_mesh_tools.py @@ -1,4 +1,5 @@ import json +import logging import os import sys from pathlib import Path @@ -6,6 +7,8 @@ import numpy as np from ruamel.yaml import YAML +logger = logging.getLogger(__name__) + def parseJsonFile(input_filename): with open(input_filename) as f: @@ -110,10 +113,8 @@ def bissection(val, stretch_fun, N1): resultmin = stretch_fun(Gmin, N1) - val resultmax = stretch_fun(Gmax, N1) - val if resultmin * resultmax > 0: - print( - "Error,the initial bounds of grading do not encompass the solution" - ) - # stop + logger.error("Initial bounds of grading do not encompass the solution") + sys.exit() for i in range(1000): Gmid = 0.5 * (Gmax + Gmin) @@ -227,8 +228,8 @@ def verticalCoarsening( length / deltaE, stretch_fun, NVert[ind] ) if iterate: - print( - f"WARNING: reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" + logger.warning( + f"reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" ) block_cell_minus_length[ind] = deltaE block_cell_plus_length[ind] = deltaE * gradVert[ind] @@ -250,8 +251,8 @@ def verticalCoarsening( length / deltaE, stretch_fun, NVert[ind] ) if iterate: - print( - f"WARNING: reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" + logger.warning( + f"reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" ) block_cell_minus_length[ind] = deltaE / gradVert[ind] block_cell_plus_length[ind] = deltaE @@ -341,8 +342,8 @@ def radialCoarsening( length / deltaE, stretch_fun, NR[ind] ) if iterate: - print( - f"WARNING: reduced NR[{ind}] from {origNR} to {NR[ind]}" + logger.warning( + f"reduced NR[{ind}] from {origNR} to {NR[ind]}" ) block_cell_minus_length[ind] = deltaE block_cell_plus_length[ind] = deltaE * gradR[ind] @@ -359,8 +360,8 @@ def radialCoarsening( length / deltaE, stretch_fun, NR[ind] ) if iterate: - print( - f"WARNING: reduced NR[{ind}] from {origNR} to {NR[ind]}" + logger.warning( + f"reduced NR[{ind}] from {origNR} to {NR[ind]}" ) block_cell_minus_length[ind] = deltaE / gradR[ind] block_cell_plus_length[ind] = deltaE @@ -384,9 +385,9 @@ def radialCoarsening( # if (gradR[last_R] > 2 or gradR[last_R] < 0.5) and abs( # ratio - 1 # ) <= 1e-12: - # print( - # "WARNING: radial smoothing had to be used because your mesh is very coarse" + # logger.warning( + # "radial smoothing had to be used because your mesh is very coarse" # ) - # print("\tIncrease NS in input file to avoid this warning") + # logger.warning("\tIncrease NS in input file to avoid this warning") return NR, gradR, minCell, maxCell diff --git a/bird/meshing/block_cyl_mesh.py b/bird/meshing/block_cyl_mesh.py index 6a16be65..2760b2c6 100644 --- a/bird/meshing/block_cyl_mesh.py +++ b/bird/meshing/block_cyl_mesh.py @@ -1,3 +1,4 @@ +import logging import os import sys @@ -5,6 +6,8 @@ from bird.meshing._mesh_tools import * +logger = logging.getLogger(__name__) + def assemble_geom(input_file, topo_file): # inpt = parseJsonFile(input_file) @@ -106,7 +109,6 @@ def assemble_mesh(input_file, geomDict): ), 1, ) - # print(NR) NS = [NR[0] * 2] # Now figure out grading of each block for ir in range(len(R)): @@ -189,14 +191,14 @@ def assemble_mesh(input_file, geomDict): minCellR = np.amin(block_cell_length) maxCellR = np.amax(block_cell_length) - print("Vertical mesh:") - print(f"\tTotal NVert {sum(NVert)}") - print(f"\tNVert {NVert}") - print(f"\tsize min {minCellVert:.2f}mm max {maxCellVert:.2f}mm") - print("Radial mesh:") - print(f"\tTotal NR {sum(NR)}") - print(f"\tNR {NR}") - print(f"\tsize min {minCellR:.2f}mm max {maxCellR:.2f}mm") + logger.info("Vertical mesh:") + logger.info(f"\tTotal NVert {sum(NVert)}") + logger.info(f"\tNVert {NVert}") + logger.info(f"\tsize min {minCellVert:.2f}mm max {maxCellVert:.2f}mm") + logger.info("Radial mesh:") + logger.info(f"\tTotal NR {sum(NR)}") + logger.info(f"\tNR {NR}") + logger.info(f"\tsize min {minCellR:.2f}mm max {maxCellR:.2f}mm") return { "NR": NR, diff --git a/bird/meshing/stirred_tank_mesh.py b/bird/meshing/stirred_tank_mesh.py index 5b9a30b8..ba67b779 100644 --- a/bird/meshing/stirred_tank_mesh.py +++ b/bird/meshing/stirred_tank_mesh.py @@ -314,9 +314,6 @@ def write_blocks(outfile, react): outfile.write(");\n") - # print "meshz:",meshz - # print "meshr:",meshr - def write_patches(outfile, react): inhub_ci = react.inhub_circ From fabda0dc2f82f51e94b15c2be8fc60a284b4629b Mon Sep 17 00:00:00 2001 From: Malik Date: Tue, 15 Jul 2025 13:48:36 -0600 Subject: [PATCH 80/86] get rid of verbose parameter and use more f strings --- .../get_optimal_with_constraint.py | 7 ++++-- bird/postprocess/SA_optimization/surrogate.py | 5 +++- bird/postprocess/conditional_mean.py | 11 +++++---- bird/postprocess/early_pred.py | 7 ++++-- bird/postprocess/kla_utils.py | 24 ++++++++++++------- bird/postprocess/post_quantities.py | 15 +++++------- 6 files changed, 42 insertions(+), 27 deletions(-) diff --git a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py index b4ec9b52..b9a28af1 100644 --- a/bird/postprocess/SA_optimization/get_optimal_with_constraint.py +++ b/bird/postprocess/SA_optimization/get_optimal_with_constraint.py @@ -1,3 +1,4 @@ +import logging import os import random @@ -14,6 +15,8 @@ tune_rf, ) +logger = logging.getLogger(__name__) + def simulated_annealing_surrogate( surrogate: Surrogate_wrapper, @@ -201,8 +204,8 @@ def run_optimization( index=False, ) - print("X = ", x_best) - print("surrogate-predicted y", y_best) + logger.info(f"X = {x_best}") + logger.info(f"surrogate-predicted y = {y_best}") # Make the mean-CI plot for the objective function and save it mean_trace = np.mean(-1 * all_traces, axis=0) diff --git a/bird/postprocess/SA_optimization/surrogate.py b/bird/postprocess/SA_optimization/surrogate.py index 1dfec6f9..0c670352 100644 --- a/bird/postprocess/SA_optimization/surrogate.py +++ b/bird/postprocess/SA_optimization/surrogate.py @@ -14,6 +14,9 @@ from tensorflow.keras.models import Sequential warnings.filterwarnings("ignore") +import logging + +logger = logging.getLogger(__name__) def check_data_shape(X: np.ndarray, y: np.ndarray) -> None: @@ -38,7 +41,7 @@ def check_data_shape(X: np.ndarray, y: np.ndarray) -> None: assert len(y.shape) == 2 # only 1 QoI assert y.shape[1] == 1 - print(f"INFO: {X.shape[0]} sim with {X.shape[1]} design variables") + logger.info(f"{X.shape[0]} sim with {X.shape[1]} design variables") def tune_rbf(X: np.ndarray, y: np.ndarray) -> dict: diff --git a/bird/postprocess/conditional_mean.py b/bird/postprocess/conditional_mean.py index 5f246b66..39edbccb 100644 --- a/bird/postprocess/conditional_mean.py +++ b/bird/postprocess/conditional_mean.py @@ -1,3 +1,4 @@ +import logging import os import pickle @@ -7,6 +8,8 @@ from bird.utilities.mathtools import * from bird.utilities.ofio import * +logger = logging.getLogger(__name__) + def compute_cond_mean( case_path, @@ -32,11 +35,11 @@ def compute_cond_mean( fields_cond[name] = {} fields_cond_tmp[name] = {} - print(f"Case : {case_path}") + logger.info(f"Case : {case_path}") for i_ave in range(window_ave): time_folder = time_str_sorted[-i_ave - 1] - print(f"\tReading Time : {time_folder}") + logger.debug(f"\tReading Time : {time_folder}") field_file = [] for field_name in field_name_list: field_file.append(os.path.join(case_path, time_folder, field_name)) @@ -71,8 +74,8 @@ def sequencePlot( if not len(case_names) == len(folder_names): case_names = [f"test{i}" for i in range(len(folder_names))] if len(case_names) > len(symbList): - print( - f"ERROR: too many cases ({len(case_names)}), reduce number of case to {len(symbList)} or add symbols" + logger.error( + f"too many cases ({len(case_names)}), reduce number of case to {len(symbList)} or add symbols" ) sys.exit() for ic, (case_name, folder_name) in enumerate( diff --git a/bird/postprocess/early_pred.py b/bird/postprocess/early_pred.py index 0dae2d44..c27a2139 100644 --- a/bird/postprocess/early_pred.py +++ b/bird/postprocess/early_pred.py @@ -1,3 +1,4 @@ +import logging import os import corner @@ -10,6 +11,8 @@ from prettyPlot.plotting import * from scipy.optimize import curve_fit +logger = logging.getLogger(__name__) + def plotAllEarly(data_dict, color_files=None, chop=False, extrap=False): fig = plt.figure() @@ -95,7 +98,7 @@ def multi_data_load(data_root, tmax=600, data_files=None, color_files=None): increase_ind = increase_ind_arr[ np.argwhere(data_dict[datf]["t"][increase_ind_arr] > 10)[0][0] ][0] - print( + logger.info( f"data {datf} first time {data_dict[datf]['t'][increase_ind]:.2f}" ) data_dict[datf]["lim"] = increase_ind @@ -135,7 +138,7 @@ def fit_and_ext( bounds=bounds, ) data_dict[datf]["yextrap"] = func(data_dict[datf]["textrap"], *popt) - print(f"data {datf} coeff {popt}") + logger.info(f"data {datf} coeff {popt}") lim_ind = data_dict[datf]["lim"] data_dict[datf]["textrap"] += data_dict[datf]["t"][lim_ind] data_dict[datf]["yextrap"] += data_dict[datf]["y"][lim_ind] diff --git a/bird/postprocess/kla_utils.py b/bird/postprocess/kla_utils.py index d5d69573..e8098e33 100644 --- a/bird/postprocess/kla_utils.py +++ b/bird/postprocess/kla_utils.py @@ -1,3 +1,5 @@ +import logging + import corner import jax.numpy as jnp import jax.random as random @@ -7,6 +9,8 @@ from numpyro.infer import MCMC, NUTS from prettyPlot.plotting import * +logger = logging.getLogger(__name__) + def read_data( filename: str, time_ind: int, conc_ind: int, ind_start=0 @@ -165,7 +169,7 @@ def compute_kla( for ind_start in range(len(data_t_tmp) - 5): if max_chop is not None: max_chop -= 1 - print(f"Chopping index = {ind_start}") + logger.debug(f"Chopping index = {ind_start}") data_t, data_c = read_data(filename, time_ind, conc_ind, ind_start) # Hamiltonian Monte Carlo (HMC) with no u turn sampling (NUTS) @@ -223,10 +227,10 @@ def compute_kla( cstar_boot = [] cstar_err_boot = [] bootstrapped = True - print("Doing data bootstrapping") + logger.info("Doing data bootstrapping") for i in range(4): - print(f"\t scenario {i}") + logger.info(f"\t scenario {i}") if i == 0: data_t = data_t_tmp[ind_start + 1 :] data_c = data_c_tmp[ind_start + 1 :] @@ -361,13 +365,15 @@ def print_res_dict(res_dict: dict) -> None: bs = res_dict["bootstrapped"] - print(f"For {file} with time index: {t_ind}, concentration index: {c_ind}") + logger.info( + f"For {file} with time index: {t_ind}, concentration index: {c_ind}" + ) if bs: - print(f"\tkla = {kla:.4g} +/- {kla_err:.4g}") - print(f"\tcstar = {cs:.4g} +/- {cs_err:.4g}") - print(f"Without data bootstrap") - print(f"\tkla = {kla_nb:.4g} +/- {kla_err_nb:.4g}") - print(f"\tcstar = {cs_nb:.4g} +/- {cs_err_nb:.4g}") + logger.info(f"\tkla = {kla:.4g} +/- {kla_err:.4g}") + logger.info(f"\tcstar = {cs:.4g} +/- {cs_err:.4g}") + logger.info(f"Without data bootstrap") + logger.info(f"\tkla = {kla_nb:.4g} +/- {kla_err_nb:.4g}") + logger.info(f"\tcstar = {cs_nb:.4g} +/- {cs_err_nb:.4g}") if __name__ == "__main__": diff --git a/bird/postprocess/post_quantities.py b/bird/postprocess/post_quantities.py index 0b23a658..6733806a 100644 --- a/bird/postprocess/post_quantities.py +++ b/bird/postprocess/post_quantities.py @@ -480,7 +480,6 @@ def compute_ave_conc_liq( mol_weight: float = 0.04401, rho_val: float | None = 1000, field_dict={}, - verbose: bool = True, ) -> tuple: """ Calculate liquid volume averaged concentration of a species at a given time @@ -506,8 +505,6 @@ def compute_ave_conc_liq( Constant density not available from time folder (kg/m3) field_dict : dict Dictionary of fields used to avoid rereading the same fields to calculate different quantities - verbose : bool - If true, output mol weight, species name and density Returns ---------- @@ -516,12 +513,12 @@ def compute_ave_conc_liq( field_dict : dict Dictionary of fields read """ - if verbose: - print( - f"INFO: Computing concentration for {spec_name} with molecular weight {mol_weight:.4g} kg/mol" - ) - if rho_val is not None: - print(f"INFO: Assuming liquid density {rho_val} kg/m3") + + logger.debug( + f"Computing concentration for {spec_name} with molecular weight {mol_weight:.4g} kg/mol" + ) + if rho_val is not None: + logger.debug(f"Assuming liquid density {rho_val} kg/m3") # Read relevant fields kwargs = { From babb6655ff5c42fce677f6c9bba6b890232ed529 Mon Sep 17 00:00:00 2001 From: Malik Date: Mon, 21 Jul 2025 14:49:30 -0600 Subject: [PATCH 81/86] Format sim folder --- bird/preprocess/json_gen/generate_designs.py | 78 ++++++++++++-------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index 461e072c..7345b030 100644 --- a/bird/preprocess/json_gen/generate_designs.py +++ b/bird/preprocess/json_gen/generate_designs.py @@ -9,6 +9,24 @@ from bird.preprocess.json_gen.design_io import * +def id2simfolder(sim_id: int) -> str: + """ + Generates simulation folder name from simulation index + + Parameters + ---------- + sim_id: int + Simulation index + + Returns + ---------- + sim_folder : str + Simulation folder name + """ + sim_folder = f"Sim_{sim_id:04}" + return sim_folder + + def compare_config(config1, config2): same = True for key in config1: @@ -46,7 +64,8 @@ def load_config_dict(filename): def write_script_start(filename, n): with open(filename, "w+") as f: for i in range(n): - f.write(f"cd Sim_{i}\n") + sim_folder = id2simfolder(i) + f.write(f"cd {sim_folder}\n") f.write(f"sbatch script\n") f.write(f"cd ..\n") @@ -54,7 +73,8 @@ def write_script_start(filename, n): def write_script_post(filename, n): with open(filename, "w+") as f: for i in range(n): - f.write(f"cd Sim_{i}\n") + sim_folder = id2simfolder(i) + f.write(f"cd {sim_folder}\n") f.write(f"sbatch script_post\n") f.write(f"cd ..\n") @@ -71,7 +91,8 @@ def write_prep(filename, n): f"source /projects/gas2fuels/ofoam_cray_mpich/OpenFOAM-dev/etc/bashrc\n" ) for i in range(n): - f.write(f"prep Sim_{i}\n") + sim_folder = id2simfolder(i) + f.write(f"prep {sim_folder}\n") def overwrite_vvm(case_folder, vvm): @@ -155,10 +176,11 @@ def generate_small_reactor_cases( except: pass os.makedirs(study_folder) - for simid in config_dict: + for sim_id in config_dict: + sim_folder = id2simfolder(sim_id) shutil.copytree( f"{template_folder}", - os.path.join(f"{study_folder}", "Sim_{simid}"), + os.path.join(f"{study_folder}", sim_folder), ) bc_dict = {} bc_dict["inlets"] = [] @@ -187,7 +209,7 @@ def generate_small_reactor_cases( ) for branch in config_dict: if branch in [0, 1, 2]: - ind = np.argwhere(config_dict[simid][branch] == 1) + ind = np.argwhere(config_dict[sim_id][branch] == 1) if len(ind) > 0: ind = list(ind[:, 0]) for iind in ind: @@ -204,7 +226,7 @@ def generate_small_reactor_cases( ) generate_stl_patch( os.path.join( - study_folder, f"Sim_{simid}", "system", "inlets_outlets.json" + study_folder, sim_folder, "system", "inlets_outlets.json" ), bc_dict, geom_dict, @@ -213,7 +235,7 @@ def generate_small_reactor_cases( mix_list = [] for branch in config_dict: if branch in [0, 1, 2]: - ind = np.argwhere(config_dict[simid][branch] == 0) + ind = np.argwhere(config_dict[sim_id][branch] == 0) if len(ind) > 0: ind = list(ind[:, 0]) for iind in ind: @@ -231,17 +253,15 @@ def generate_small_reactor_cases( } ) generate_dynamic_mixer( - os.path.join( - study_folder, f"Sim_{simid}", "system", "mixers.json" - ), + os.path.join(study_folder, sim_folder, "system", "mixers.json"), mix_list, geom_dict, ) overwrite_vvm( - case_folder=os.path.join(study_folder, f"Sim_{simid}"), vvm=vvm + case_folder=os.path.join(study_folder, sim_folder), vvm=vvm ) overwrite_bubble_size_model( - case_folder=os.path.join(study_folder, f"Sim_{simid}"), + case_folder=os.path.join(study_folder, sim_folder), constantD=constantD, ) @@ -274,10 +294,11 @@ def generate_scaledup_reactor_cases( except: pass os.makedirs(study_folder) - for simid in config_dict: + for sim_id in config_dict: + sim_folder = id2simfolder(sim_id) shutil.copytree( f"{template_folder}", - os.path.join(f"{study_folder}", f"Sim_{simid}"), + os.path.join(f"{study_folder}", sim_folder), ) bc_dict = {} bc_dict["inlets"] = [] @@ -306,7 +327,7 @@ def generate_scaledup_reactor_cases( ) for branch in config_dict: if branch in [0, 1, 2]: - ind = np.argwhere(config_dict[simid][branch] == 1) + ind = np.argwhere(config_dict[sim_id][branch] == 1) if len(ind) > 0: ind = list(ind[:, 0]) for iind in ind: @@ -323,7 +344,7 @@ def generate_scaledup_reactor_cases( ) generate_stl_patch( os.path.join( - study_folder, f"Sim_{simid}", "system", "inlets_outlets.json" + study_folder, sim_folder, "system", "inlets_outlets.json" ), bc_dict, geom_dict, @@ -332,7 +353,7 @@ def generate_scaledup_reactor_cases( mix_list = [] for branch in config_dict: if branch in [0, 1, 2]: - ind = np.argwhere(config_dict[simid][branch] == 0) + ind = np.argwhere(config_dict[sim_id][branch] == 0) if len(ind) > 0: ind = list(ind[:, 0]) for iind in ind: @@ -350,17 +371,15 @@ def generate_scaledup_reactor_cases( } ) generate_dynamic_mixer( - os.path.join( - study_folder, f"Sim_{simid}", "system", "mixers.json" - ), + os.path.join(study_folder, sim_folder, "system", "mixers.json"), mix_list, geom_dict, ) overwrite_vvm( - case_folder=os.path.join(study_folder, f"Sim_{simid}"), vvm=vvm + case_folder=os.path.join(study_folder, sim_folder), vvm=vvm ) overwrite_bubble_size_model( - case_folder=os.path.join(study_folder, f"Sim_{simid}"), + case_folder=os.path.join(study_folder, sim_folder), constantD=constantD, ) @@ -498,9 +517,10 @@ def generate_single_scaledup_reactor_sparger_cases( ) # Start from template + sim_folder = id2simfolder(sim_id) shutil.copytree( f"{template_folder}", - os.path.join(f"{study_folder}", f"Sim_{sim_id}"), + os.path.join(f"{study_folder}", sim_folder), ) bc_dict = {} @@ -544,7 +564,7 @@ def generate_single_scaledup_reactor_sparger_cases( generate_stl_patch( os.path.join( - study_folder, f"Sim_{sim_id}", "system", "inlets_outlets.json" + study_folder, sim_folder, "system", "inlets_outlets.json" ), bc_dict, geom_dict, @@ -552,14 +572,12 @@ def generate_single_scaledup_reactor_sparger_cases( mix_list = [] generate_dynamic_mixer( - os.path.join(study_folder, f"Sim_{sim_id}", "system", "mixers.json"), + os.path.join(study_folder, sim_folder, "system", "mixers.json"), mix_list, geom_dict, ) - overwrite_vvm( - case_folder=os.path.join(study_folder, f"Sim_{sim_id}"), vvm=vvm - ) + overwrite_vvm(case_folder=os.path.join(study_folder, sim_folder), vvm=vvm) overwrite_bubble_size_model( - case_folder=os.path.join(study_folder, f"Sim_{sim_id}"), + case_folder=os.path.join(study_folder, sim_folder), constantD=constantD, ) From e1598c525171c2763bda0803867b8863603c2255 Mon Sep 17 00:00:00 2001 From: Malik Date: Wed, 23 Jul 2025 18:32:56 -0600 Subject: [PATCH 82/86] add tutorial for python interface --- .../python_interface_tut.py | 71 ++++++++++ bird/utilities/mathtools.py | 3 + bird/version.py | 2 +- docs/assets/cond_mean_tut.png | Bin 0 -> 24933 bytes docs/source/index.rst | 2 +- docs/source/pythonInterface_tut.rst | 127 +++++++++++++++++- docs/source/tutorials.rst | 2 +- docs/source/{postprocess.rst => uq.rst} | 25 +--- 8 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 bird/postprocess/data_conditional_mean/python_interface_tut.py create mode 100644 docs/assets/cond_mean_tut.png rename docs/source/{postprocess.rst => uq.rst} (79%) diff --git a/bird/postprocess/data_conditional_mean/python_interface_tut.py b/bird/postprocess/data_conditional_mean/python_interface_tut.py new file mode 100644 index 00000000..89b9e8a2 --- /dev/null +++ b/bird/postprocess/data_conditional_mean/python_interface_tut.py @@ -0,0 +1,71 @@ +# Import the relevant IO functions +from bird.utilities.ofio import * + +# Read cell centers +cell_centers = readMesh("meshCellCentres_1.obj") +print("cell centers shape = ", cell_centers.shape) + +# Read relevant fields at time 80 +co2_gas = readOF("80/CO2.gas") +alpha_gas = readOF("80/alpha.gas") +u_liq = readOF("80/U.liquid") +print("cell CO2 gas shape = ", co2_gas["field"].shape) +print("cell alpha gas shape = ", alpha_gas["field"].shape) +print("cell u liq shape = ", u_liq["field"].shape) + +# Compute conditional average of co2_gas and alpha_gas over y +from bird.utilities.mathtools import conditionalAverage + +y_co2_gas_cond, co2_gas_cond = conditionalAverage( + cell_centers[:, 1], co2_gas["field"], nbin=32 +) +y_alpha_gas_cond, alpha_gas_cond = conditionalAverage( + cell_centers[:, 1], alpha_gas["field"], nbin=32 +) + +# Plot +from prettyPlot.plotting import * + +fig = plt.figure() +plt.plot(y_co2_gas_cond, co2_gas_cond, color="k", label=r"$Y_{CO_2}$ [-]") +plt.plot( + y_alpha_gas_cond, alpha_gas_cond, color="b", label=r"$\alpha_{g}$ [-]" +) +pretty_labels("Y [m]", "", fontsize=20, grid=False, fontname="Times") +pretty_legend(fontname="Times") +plt.show() + +# Compute reactor quantities +from bird.postprocess.post_quantities import * + +kwargs = {"case_folder": ".", "time_folder": "80"} +gh, field_dict = compute_gas_holdup( + volume_time="1", field_dict={"cell_centers": cell_centers}, **kwargs +) +print("fields stored = ", list(field_dict.keys())) +print(f"Gas Holdup = {gh:.4g}") +sup_vel, field_dict = compute_superficial_velocity( + volume_time="1", field_dict=field_dict, **kwargs +) +print("fields stored = ", list(field_dict.keys())) +print(f"Superficial velocity = {sup_vel:.4g} m/s") +y_ave_co2, field_dict = compute_ave_y_liq( + volume_time="1", spec_name="CO2", field_dict=field_dict, **kwargs +) +print("fields stored = ", list(field_dict.keys())) +print(f"Reactor averaged YCO2 = {y_ave_co2:.4g}") +c_ave_co2, field_dict = compute_ave_conc_liq( + volume_time="1", + spec_name="CO2", + mol_weight=0.04401, + rho_val=1000, + field_dict=field_dict, + **kwargs, +) +print("fields stored = ", list(field_dict.keys())) +print(f"Reactor averaged [CO2] = {c_ave_co2:.4g} mol/m3") +diam, field_dict = compute_ave_bubble_diam( + volume_time="1", field_dict=field_dict, **kwargs +) +print("fields stored = ", list(field_dict.keys())) +print(f"Reactor averaged bubble diameter = {diam:.4g} m") diff --git a/bird/utilities/mathtools.py b/bird/utilities/mathtools.py index e75cfec3..543bc383 100644 --- a/bird/utilities/mathtools.py +++ b/bird/utilities/mathtools.py @@ -1,3 +1,5 @@ +import sys + import numpy as np @@ -9,6 +11,7 @@ def conditionalAverage(x, y, nbin): print("dim x = ", len(x)) print("dim y = ", len(y)) sys.exit() + # Bin conditional space mag = np.amax(x) - np.amin(x) x_bin = np.linspace( diff --git a/bird/version.py b/bird/version.py index 817149b5..c4338ae1 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.26" +__version__ = "0.0.27" diff --git a/docs/assets/cond_mean_tut.png b/docs/assets/cond_mean_tut.png new file mode 100644 index 0000000000000000000000000000000000000000..564d02e9ae6c68f0d1c59857f97a760b19559785 GIT binary patch literal 24933 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 Date: Wed, 23 Jul 2025 18:34:25 -0600 Subject: [PATCH 83/86] update refs --- docs/source/references.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/references.rst b/docs/source/references.rst index 5b325880..051a2b7f 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -9,10 +9,13 @@ To cite BiRD, please use these articles on `CO2 interphase mass transfer Date: Thu, 24 Jul 2025 10:13:15 -0600 Subject: [PATCH 84/86] remove camelcase and add doc strings for tutorial --- bird/postprocess/conditional_mean.py | 4 +- .../python_interface_tut.py | 10 +-- bird/utilities/mathtools.py | 65 ++++++++++++++++--- docs/source/pythonInterface_tut.rst | 10 +-- 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/bird/postprocess/conditional_mean.py b/bird/postprocess/conditional_mean.py index 39edbccb..e940413e 100644 --- a/bird/postprocess/conditional_mean.py +++ b/bird/postprocess/conditional_mean.py @@ -47,8 +47,8 @@ def compute_cond_mean( for filename, name in zip(field_file, field_name_list): val_dict = {} field_tmp = readOFScal(filename, nCells)["field"] - vert_axis, field_cond_tmp = conditionalAverage( - cellCentres[:, vert_ind], field_tmp, nbin=n_bins + vert_axis, field_cond_tmp = conditional_average( + cellCentres[:, vert_ind], field_tmp, nbins=n_bins ) if i_ave == 0: fields_cond[name]["val"] = field_cond_tmp / window_ave diff --git a/bird/postprocess/data_conditional_mean/python_interface_tut.py b/bird/postprocess/data_conditional_mean/python_interface_tut.py index 89b9e8a2..59258b9e 100644 --- a/bird/postprocess/data_conditional_mean/python_interface_tut.py +++ b/bird/postprocess/data_conditional_mean/python_interface_tut.py @@ -14,13 +14,13 @@ print("cell u liq shape = ", u_liq["field"].shape) # Compute conditional average of co2_gas and alpha_gas over y -from bird.utilities.mathtools import conditionalAverage +from bird.utilities.mathtools import conditional_average -y_co2_gas_cond, co2_gas_cond = conditionalAverage( - cell_centers[:, 1], co2_gas["field"], nbin=32 +y_co2_gas_cond, co2_gas_cond = conditional_average( + cell_centers[:, 1], co2_gas["field"], nbins=32 ) -y_alpha_gas_cond, alpha_gas_cond = conditionalAverage( - cell_centers[:, 1], alpha_gas["field"], nbin=32 +y_alpha_gas_cond, alpha_gas_cond = conditional_average( + cell_centers[:, 1], alpha_gas["field"], nbins=32 ) # Plot diff --git a/bird/utilities/mathtools.py b/bird/utilities/mathtools.py index 8f07ac4d..6788ba2e 100644 --- a/bird/utilities/mathtools.py +++ b/bird/utilities/mathtools.py @@ -6,7 +6,47 @@ logger = logging.getLogger(__name__) -def conditionalAverage(x, y, nbin): +def conditional_average( + x: np.ndarray, y: np.ndarray, nbins: int = 32 +) -> tuple: + """ + Compute a 1D conditional average of y with respect to x + The conditional average is distributed to neighbors of the binned array when needed + + Parameters + ---------- + x: np.ndarray + 1D array with respect to which conditional averaged is performed + y : np.ndarray + 1D array conditioned + nbins: int + Number of bins through x + + Returns + ---------- + x_cond: np.ndarray + The binned array of values conditioned againsts + y_cond: np.ndarray + The conditional averages at each bin + """ + # Check the shape of input arrays + try: + assert len(x.shape) <= 2 + assert len(y.shape) <= 2 + if len(x.shape) == 2: + assert x.shape[1] == 1 + if len(y.shape) == 2: + assert y.shape[1] == 1 + except AssertionError: + error_msg = "conditional average of tensors is ambiguous" + error_msg += f"\nx shape = {x.shape}" + error_msg += f"\ny shape = {y.shape}" + logger.error(error_msg) + sys.exit() + if len(x.shape) == 2: + x = x[:, 0] + if len(y.shape) == 2: + y = y[:, 0] try: assert len(x) == len(y) except AssertionError: @@ -19,14 +59,14 @@ def conditionalAverage(x, y, nbin): # Bin conditional space mag = np.amax(x) - np.amin(x) x_bin = np.linspace( - np.amin(x) - mag / (2 * nbin), np.amax(x) + mag / (2 * nbin), nbin + np.amin(x) - mag / (2 * nbins), np.amax(x) + mag / (2 * nbins), nbins ) - weight = np.zeros(nbin) - weightVal = np.zeros(nbin) - asum = np.zeros(nbin) - bsum = np.zeros(nbin) - avalsum = np.zeros(nbin) - bvalsum = np.zeros(nbin) + weight = np.zeros(nbins) + weightVal = np.zeros(nbins) + asum = np.zeros(nbins) + bsum = np.zeros(nbins) + avalsum = np.zeros(nbins) + bvalsum = np.zeros(nbins) inds = np.digitize(x, x_bin) a = abs(y - x_bin[inds - 1]) @@ -35,7 +75,8 @@ def conditionalAverage(x, y, nbin): a = a / c b = b / c - for i in range(nbin): + # Conditional average at each bin + for i in range(nbins): asum[i] = np.sum(a[np.argwhere(inds == i)]) bsum[i] = np.sum(b[np.argwhere(inds == i + 1)]) avalsum[i] = np.sum( @@ -47,4 +88,8 @@ def conditionalAverage(x, y, nbin): weight = asum + bsum weightVal = avalsum + bvalsum - return x_bin, weightVal / (weight) + # Assemble output + x_cond = x_bin + y_cond = weightVal / (weight) + + return x_cond, y_cond diff --git a/docs/source/pythonInterface_tut.rst b/docs/source/pythonInterface_tut.rst index b332cdf2..38390400 100644 --- a/docs/source/pythonInterface_tut.rst +++ b/docs/source/pythonInterface_tut.rst @@ -55,13 +55,13 @@ With cells center locations and the internal fields, one can use python function .. code-block:: python - from bird.utilities.mathtools import conditionalAverage + from bird.utilities.mathtools import conditional_average - y_co2_gas_cond, co2_gas_cond = conditionalAverage( - cell_centers[:, 1], co2_gas["field"], nbin=32 + y_co2_gas_cond, co2_gas_cond = conditional_average( + cell_centers[:, 1], co2_gas["field"], nbins=32 ) - y_alpha_gas_cond, alpha_gas_cond = conditionalAverage( - cell_centers[:, 1], alpha_gas["field"], nbin=32 + y_alpha_gas_cond, alpha_gas_cond = conditional_average( + cell_centers[:, 1], alpha_gas["field"], nbins=32 ) from prettyPlot.plotting import * From 9834d28baa1fa78f40a033f91df46ee18afe604d Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 24 Jul 2025 10:14:01 -0600 Subject: [PATCH 85/86] add temporary fix for numpyro --- bird/postprocess/early_pred.py | 7 +++++++ bird/postprocess/kla_utils.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/bird/postprocess/early_pred.py b/bird/postprocess/early_pred.py index c27a2139..a4896dca 100644 --- a/bird/postprocess/early_pred.py +++ b/bird/postprocess/early_pred.py @@ -2,9 +2,16 @@ import os import corner + +# https://github.com/pyro-ppl/numpyro/issues/2051 +import jax.experimental.pjit import jax.numpy as jnp import jax.random as random import numpy as np +from jax.extend.core.primitives import jit_p + +jax.experimental.pjit.pjit_p = jit_p + import numpyro import numpyro.distributions as dist from numpyro.infer import MCMC, NUTS diff --git a/bird/postprocess/kla_utils.py b/bird/postprocess/kla_utils.py index e8098e33..8a527221 100644 --- a/bird/postprocess/kla_utils.py +++ b/bird/postprocess/kla_utils.py @@ -1,9 +1,16 @@ import logging import corner + +# https://github.com/pyro-ppl/numpyro/issues/2051 +import jax.experimental.pjit import jax.numpy as jnp import jax.random as random import numpy as np +from jax.extend.core.primitives import jit_p + +jax.experimental.pjit.pjit_p = jit_p + import numpyro import numpyro.distributions as dist from numpyro.infer import MCMC, NUTS From 8075078b0eede5314fbcae291e3d5f4448d94e17 Mon Sep 17 00:00:00 2001 From: Malik Date: Thu, 24 Jul 2025 10:28:33 -0600 Subject: [PATCH 86/86] correct only for specific jax versions --- bird/postprocess/early_pred.py | 13 ++++++++----- bird/postprocess/kla_utils.py | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/bird/postprocess/early_pred.py b/bird/postprocess/early_pred.py index a4896dca..d96edf26 100644 --- a/bird/postprocess/early_pred.py +++ b/bird/postprocess/early_pred.py @@ -2,15 +2,18 @@ import os import corner - -# https://github.com/pyro-ppl/numpyro/issues/2051 -import jax.experimental.pjit +import jax import jax.numpy as jnp import jax.random as random import numpy as np -from jax.extend.core.primitives import jit_p +from packaging import version + +if version.parse(jax.__version__) >= version.parse("0.7.0"): + # https://github.com/pyro-ppl/numpyro/issues/2051 + import jax.experimental.pjit + from jax.extend.core.primitives import jit_p -jax.experimental.pjit.pjit_p = jit_p + jax.experimental.pjit.pjit_p = jit_p import numpyro import numpyro.distributions as dist diff --git a/bird/postprocess/kla_utils.py b/bird/postprocess/kla_utils.py index 8a527221..28095d0d 100644 --- a/bird/postprocess/kla_utils.py +++ b/bird/postprocess/kla_utils.py @@ -1,15 +1,18 @@ import logging import corner - -# https://github.com/pyro-ppl/numpyro/issues/2051 -import jax.experimental.pjit +import jax import jax.numpy as jnp import jax.random as random import numpy as np -from jax.extend.core.primitives import jit_p +from packaging import version + +if version.parse(jax.__version__) >= version.parse("0.7.0"): + # https://github.com/pyro-ppl/numpyro/issues/2051 + import jax.experimental.pjit + from jax.extend.core.primitives import jit_p -jax.experimental.pjit.pjit_p = jit_p + jax.experimental.pjit.pjit_p = jit_p import numpyro import numpyro.distributions as dist