diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index 40f3315a..3497584c 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -25,19 +25,22 @@ jobs: python-version: '3.10' cache: 'pip' - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview python -m pip install --upgrade pip - pip install -e . + pip install -e . pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints - name: Build documentation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03f0bc95..14f066c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,19 +66,22 @@ jobs: with: python-version: ${{matrix.python-version}} - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview pip install --upgrade pip - pip install . + pip install . pip install pytest - name: Test @@ -102,17 +105,20 @@ jobs: with: python-version: ${{matrix.python-version}} - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview pip install --upgrade pip pip install nrel-bird pip install pytest @@ -141,17 +147,20 @@ jobs: with: openfoam-version: 9 - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview pip install --upgrade pip pip install . diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 6defac05..28e9b622 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -27,17 +27,20 @@ jobs: python-version: '3.13' cache: 'pip' - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview pip install --upgrade pip pip install pytest pip install pytest-cov diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index ef1ee504..3751c0dc 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -26,18 +26,21 @@ jobs: with: python-version: '3.10' cache: 'pip' - - - name: Set up Miniconda - uses: conda-incubator/setup-miniconda@v3 + + - name: Set up micromamba + uses: mamba-org/setup-micromamba@v1 with: - activate-environment: test-env - python-version: ${{matrix.python-version}} + environment-name: test-env + create-args: >- + python=${{ matrix.python-version }} channels: conda-forge channel-priority: strict + cache-downloads: true + cache-env: true - name: Install dependencies run: | - conda install -y -c conda-forge paraview + micromamba install --yes -n test-env -c conda-forge paraview pip install --upgrade pip pip install -e . pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints diff --git a/applications/write_stl_patch.py b/applications/write_stl_patch.py index fe7d9f24..655156a2 100644 --- a/applications/write_stl_patch.py +++ b/applications/write_stl_patch.py @@ -32,8 +32,8 @@ if args.verbose: # plot - from bird.utilities.stl_plotting import plotSTL, plt, pretty_labels + from bird.utilities.stl_plotting import plot_stl, plt, pretty_labels - axes = plotSTL("inlets.stl") + axes = plot_stl("inlets.stl") pretty_labels("x", "y", zlabel="z", fontsize=14, ax=axes) plt.show() diff --git a/bird/calibration/param_nn.py b/bird/calibration/param_nn.py index c0cc8255..5847fdb0 100644 --- a/bird/calibration/param_nn.py +++ b/bird/calibration/param_nn.py @@ -1,6 +1,7 @@ import argparse import os import time +from pathlib import Path import joblib import numpy as np @@ -306,8 +307,8 @@ def load(self, weight_file): self.model.load_weights(weight_file) def prepareLog(self): - os.makedirs(self.model_folder, exist_ok=True) - os.makedirs(self.log_loss_folder, exist_ok=True) + Path(self.model_folder).mkdir(parents=True, exist_ok=True) + Path(self.log_loss_folder).mkdir(parents=True, exist_ok=True) try: os.remove(os.path.join(self.log_loss_folder, "log.csv")) except FileNotFoundError as err: diff --git a/bird/logging_config.py b/bird/logging_config.py index 0df52260..5e246367 100644 --- a/bird/logging_config.py +++ b/bird/logging_config.py @@ -6,9 +6,7 @@ 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" - ) + self._style._fmt = "%(asctime)s [%(levelname)s] bird: %(message)s" else: self._style._fmt = "%(asctime)s [%(levelname)s] bird: %(message)s" return super().format(record) diff --git a/bird/meshing/_mesh_tools.py b/bird/meshing/_mesh_tools.py index 0fef1067..bc4955a1 100644 --- a/bird/meshing/_mesh_tools.py +++ b/bird/meshing/_mesh_tools.py @@ -1,6 +1,7 @@ import numpy as np from bird import logger +from bird.utilities.mathtools import bissection def make_walls_from_topo(topo_dict: dict) -> dict: @@ -70,8 +71,8 @@ def make_bound_from_topo(topo_dict): } -def stretch_fun(G, N1): - result = (1.0 - G) / (G * (1 - np.power(G, 1.0 / N1))) +def stretch_fun(G, N): + result = (1.0 - G) / (G * (1 - np.power(G, 1.0 / N))) return result @@ -80,29 +81,6 @@ def stretch_fun(G, N1): # return result -def bissection(val, stretch_fun, N1): - Gmin = 0.00001 - Gmax = 1000000 - resultmin = stretch_fun(Gmin, N1) - val - resultmax = stretch_fun(Gmax, N1) - val - if resultmin * resultmax > 0: - error_msg = "Initial bounds of grading do not encompass the solution" - logger.error(error_msg) - raise ValueError(error_msg) - - for i in range(1000): - Gmid = 0.5 * (Gmax + Gmin) - resultmid = stretch_fun(Gmid, N1) - val - if resultmid * resultmax < 0: - Gmin = Gmid - resultmin = resultmid - else: - Gmax = Gmid - resultmax = resultmid - - return Gmid - - def is_wall(l_wall: list[int], r_wall: list[int], ir: int, il: int) -> int: """ Is the present block a wall (not meshed) @@ -223,9 +201,8 @@ def verticalCoarsening( raise ValueError(error_msg) if ratio_dir[ind] == "+": - gradVert[ind] = 1.0 / bissection( - length / deltaE, stretch_fun, NVert[ind] - ) + nl_func = lambda x: stretch_fun(x, NVert[ind]) + gradVert[ind] = 1.0 / bissection(length / deltaE, nl_func) iterate = False origNVert = NVert[ind] while gradVert[ind] < 1 and NVert[ind] > 1: @@ -233,9 +210,8 @@ def verticalCoarsening( NVert[ind] = max( int(round(min(0.99 * NVert[ind], NVert[ind] - 1))), 1 ) - gradVert[ind] = 1.0 / bissection( - length / deltaE, stretch_fun, NVert[ind] - ) + nl_func = lambda x: stretch_fun(x, NVert[ind]) + gradVert[ind] = 1.0 / bissection(length / deltaE, nl_func) if iterate: logger.warning( f"reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" @@ -245,9 +221,8 @@ def verticalCoarsening( elif ratio_dir[ind] == "-": deltaE = block_cell_minus_length[ind - 1] - gradVert[ind] = bissection( - length / deltaE, stretch_fun, NVert[ind] - ) + nl_func = lambda x: stretch_fun(x, NVert[ind]) + gradVert[ind] = bissection(length / deltaE, nl_func) iterate = False origNVert = NVert[ind] @@ -256,9 +231,8 @@ def verticalCoarsening( NVert[ind] = max( int(round(min(0.99 * NVert[ind], NVert[ind] - 1))), 1 ) - gradVert[ind] = bissection( - length / deltaE, stretch_fun, NVert[ind] - ) + nl_func = lambda x: stretch_fun(x, NVert[ind]) + gradVert[ind] = bissection(length / deltaE, nl_func) if iterate: logger.warning( f"reduced NVert[{ind}] from {origNVert} to {NVert[ind]}" @@ -337,9 +311,8 @@ def radialCoarsening( raise ValueError(error_msg) if ratio_dir[ind] == "+": - gradR[ind] = 1.0 / bissection( - length / deltaE, stretch_fun, NR[ind] - ) + nl_func = lambda x: stretch_fun(x, NR[ind]) + gradR[ind] = 1.0 / bissection(length / deltaE, nl_func) iterate = False origNR = NR[ind] while gradR[ind] < 1 and NR[ind] > 1: @@ -347,9 +320,8 @@ def radialCoarsening( NR[ind] = max( int(round(min(0.99 * NR[ind], NR[ind] - 1))), 1 ) - gradR[ind] = 1.0 / bissection( - length / deltaE, stretch_fun, NR[ind] - ) + nl_func = lambda x: stretch_fun(x, NR[ind]) + gradR[ind] = 1.0 / bissection(length / deltaE, nl_func) if iterate: logger.warning( f"reduced NR[{ind}] from {origNR} to {NR[ind]}" @@ -359,15 +331,15 @@ def radialCoarsening( elif ratio_dir[ind] == "-": deltaE = block_cell_minus_length[ind - 1] - gradR[ind] = bissection(length / deltaE, stretch_fun, NR[ind]) + nl_func = lambda x: stretch_fun(x, NR[ind]) + gradR[ind] = bissection(length / deltaE, nl_func) iterate = False origNR = NR[ind] while gradR[ind] > 1 and NR[ind] > 1: iterate = True NR[ind] = max(int(round(min(0.99 * NR[ind], -1))), 1) - gradR[ind] = bissection( - length / deltaE, stretch_fun, NR[ind] - ) + nl_func = lambda x: stretch_fun(x, NR[ind]) + gradR[ind] = bissection(length / deltaE, nl_func) if iterate: logger.warning( f"reduced NR[{ind}] from {origNR} to {NR[ind]}" diff --git a/bird/meshing/block_cyl_mesh.py b/bird/meshing/block_cyl_mesh.py index 8dbf25c2..518ba23d 100644 --- a/bird/meshing/block_cyl_mesh.py +++ b/bird/meshing/block_cyl_mesh.py @@ -550,12 +550,3 @@ def writeBlockMeshDict(out_folder, geom_dict, mesh_dict): fw.close() - -def main(input_file, topo_file, output_folder): - geom_dict = assemble_geom(input_file, topo_file) - mesh_dict = assemble_mesh(input_file, geom_dict) - writeBlockMeshDict(output_folder, geom_dict, mesh_dict) - - -if __name__ == "__main__": - main() diff --git a/bird/postprocess/early_pred.py b/bird/postprocess/early_pred.py index d96edf26..2e491048 100644 --- a/bird/postprocess/early_pred.py +++ b/bird/postprocess/early_pred.py @@ -102,7 +102,9 @@ def multi_data_load(data_root, tmax=600, data_files=None, color_files=None): A = np.loadtxt(filename) data_dict[datf] = {} data_dict[datf]["t"] = A[:, 0] - data_dict[datf]["y"] = A[:, 5] / (A[:, 4] * 16 / 44 + A[:, 5]) + data_dict[datf]["y"] = A[:, 5] / np.clip( + (A[:, 4] * 16 / 44 + A[:, 5]), a_min=1e-12, a_max=None + ) # chop data before increase and right after t=10s increase_ind_arr = np.argwhere(np.diff(data_dict[datf]["y"]) > 0) increase_ind = increase_ind_arr[ @@ -264,13 +266,3 @@ def bayes_fit(data_dict, num_warmup=1000, num_samples=500): return data_dict - -if __name__ == "__main__": - from bird import BIRD_EARLY_PRED_DATA_DIR - - 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) - bayes_fit(data_dict) - plotAllEarly_uq(data_dict, color_files=color_files) - plt.show() diff --git a/bird/postprocess/kla_utils.py b/bird/postprocess/kla_utils.py index b08bfd65..c7a5af4d 100644 --- a/bird/postprocess/kla_utils.py +++ b/bird/postprocess/kla_utils.py @@ -371,7 +371,3 @@ def print_res_dict(res_dict: dict) -> None: logger.info(f"\tkla = {kla_nb*3600:.4g} +/- {kla_err_nb*3600:.4g} [h-1]") logger.info(f"\tcstar = {cs_nb:.4g} +/- {cs_err_nb:.4g} [mol/m3]") - -if __name__ == "__main__": - res_dict = compute_kla("data_kla/volume_avg.dat", time_ind=0, conc_ind=1) - print_res_dict(res_dict) diff --git a/bird/postprocess/stats.py b/bird/postprocess/stats.py index 34565b47..e79f69b0 100644 --- a/bird/postprocess/stats.py +++ b/bird/postprocess/stats.py @@ -153,6 +153,10 @@ def calc_mean( """ Compute mean and the uncertainty about the mean, from a time-series + Following Trenberth, "Some Effects of Finite Sample Size and Persistence on Meteorological Statistics. Part I: Autocorrelations", 1984 + And Oliver et al., "Estimating uncertainties in statistics computed from direct numerical simulation", 2014 + + Parameters ---------- time_series: np.ndarray @@ -167,7 +171,7 @@ def calc_mean( mean_val: float Mean value of the time_series unc_val: float - 68% uncertainty (1 sigma) about the mean + 95% uncertainty (1.96 sigma) about the mean """ @@ -181,4 +185,4 @@ def calc_mean( sigsq = np.var(time_series) * N / (N - T0) unc_val = np.sqrt(sigsq * T0 / N) - return mean_val, unc_val + return mean_val, unc_val * 1.96 diff --git a/bird/preprocess/dynamic_mixer/mixing_fvModels.py b/bird/preprocess/dynamic_mixer/mixing_fvModels.py index 80b0df2b..69cc48f4 100644 --- a/bird/preprocess/dynamic_mixer/mixing_fvModels.py +++ b/bird/preprocess/dynamic_mixer/mixing_fvModels.py @@ -55,9 +55,3 @@ def write_fvModel(input_dict, output_folder=".", force_sign=False): write_end(output_folder) - -if __name__ == "__main__": - input_dict = parse_json( - os.path.join("mixing_template", "loop_reactor_list", "mixers.json"), - ) - write_fvModel(input_dict) diff --git a/bird/preprocess/json_gen/generate_designs.py b/bird/preprocess/json_gen/generate_designs.py index cdf06514..862de36a 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 +from pathlib import Path import numpy as np @@ -174,7 +175,7 @@ def generate_small_reactor_cases( shutil.rmtree(study_folder) except: pass - os.makedirs(study_folder) + Path(study_folder).mkdir(parents=True, exist_ok=True) for sim_id in config_dict: sim_folder = id2simfolder(sim_id) shutil.copytree( @@ -292,7 +293,7 @@ def generate_scaledup_reactor_cases( shutil.rmtree(study_folder) except: pass - os.makedirs(study_folder) + Path(study_folder).mkdir(parents=True, exist_ok=True) for sim_id in config_dict: sim_folder = id2simfolder(sim_id) shutil.copytree( diff --git a/bird/preprocess/species_gen/setup_thermo_prop.py b/bird/preprocess/species_gen/setup_thermo_prop.py index 95ff2138..31c68dc6 100644 --- a/bird/preprocess/species_gen/setup_thermo_prop.py +++ b/bird/preprocess/species_gen/setup_thermo_prop.py @@ -420,12 +420,3 @@ def write_species_properties(case_folder: str, phase: str = "gas") -> None: ) write_openfoam_dict(thermo_properties_update, filename=filename) - -if __name__ == "__main__": - from bird import BIRD_DIR - - case_folder = os.path.join(BIRD_DIR, "../experimental_cases/deckwer17") - write_species_properties(case_folder, phase="gas") - write_species_properties(case_folder, phase="liquid") - # fill_global_prop(os.path.join(BIRD_DIR,"../experimental_cases_new/disengagement/bubble_column_pbe_20L/")) - # fill_global_prop(os.path.join(BIRD_DIR, "../experimental_cases_new/deckwer17")) diff --git a/bird/preprocess/stl_patch/stl_bc.py b/bird/preprocess/stl_patch/stl_bc.py index 216c762c..855c09cb 100644 --- a/bird/preprocess/stl_patch/stl_bc.py +++ b/bird/preprocess/stl_patch/stl_bc.py @@ -47,14 +47,3 @@ def write_boundaries(input_dict, output_folder="."): boundary_mesh.save( os.path.join(output_folder, f"{boundary_name}.stl") ) - - -if __name__ == "__main__": - input_dict = parse_json( - "bc_patch_mesh_template/loop_reactor_expl/inlets_outlets.json" - ) - write_boundaries(input_dict) - input_dict = parse_json( - "bc_patch_mesh_template/loop_reactor_branch/inlets_outlets.json" - ) - write_boundaries(input_dict) diff --git a/bird/utilities/folderManagement.py b/bird/utilities/folderManagement.py deleted file mode 100644 index 3c31efa1..00000000 --- a/bird/utilities/folderManagement.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -import re - -from bird import logger - - -def makeRecursiveFolder(path): - folder_list = path.split("/") - localFolder = "" - for folder in folder_list: - localFolder = os.path.join(localFolder, folder) - os.makedirs(localFolder, exist_ok=True) - - -def getManyFolders(rootFolder, prefix="flat_donut"): - # Read Time - fold_tmp = os.listdir(rootFolder) - fold_num = [] - # remove non floats - for i, entry in reversed(list(enumerate(fold_tmp))): - if not entry.startswith(prefix) or entry.endswith("N2"): - a = fold_tmp.pop(i) - # print('removed ', a) - for entry in fold_tmp: - num = re.findall(r"\d+", entry) - if len(num) > 1: - 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])) - - sortedFold = [ - x for _, x in sorted(zip(fold_num, fold_tmp), key=lambda pair: pair[0]) - ] - return sortedFold diff --git a/bird/utilities/mathtools.py b/bird/utilities/mathtools.py index 159d8b88..4cf528ea 100644 --- a/bird/utilities/mathtools.py +++ b/bird/utilities/mathtools.py @@ -1,3 +1,5 @@ +from typing import Callable + import numpy as np from bird import logger @@ -90,3 +92,61 @@ def conditional_average( y_cond = weightVal / (weight) return x_cond, y_cond + + +def bissection( + fun_val: float, + nonlin_fun: Callable, + num_iter: int = 1000, + x_min: float = 1e-6, + x_max: float = 1e6, +) -> float: + """ + Solve a 1D non linear equation with a bissection method + This is useful for adjusting the mesh grading so that mesh size varies smoothly + + Parameters + ---------- + fun_val: + Target value for the non linear function + nonlin_fun: + Non linear function + num_iter: int + Number of bissection iterations + Defaults to 1000 + x_min : float + Lower bound of the search interval + Defaults to 1e-6 + x_max : float + Upper bound of the search interval + Defaults to 1e6 + + + Returns + ---------- + x_mid: float + The argument that achieves the desired function value + """ + + # Make sure residual sign changes over the search interval + residual_min = nonlin_fun(x_min) - fun_val + residual_max = nonlin_fun(x_max) - fun_val + if residual_min * residual_max > 0: + error_msg = "No guaranteed bissection solution" + error_msg += ( + "\nSearch interval [{x_min:.4g}, {x_max:.4g}] may be too narrow" + ) + raise ValueError(error_msg) + + # Do the bissection search + for i in range(num_iter): + x_mid = 0.5 * (x_max + x_min) + residual_mid = nonlin_fun(x_mid) - fun_val + if residual_mid * residual_max < 0: + x_min = x_mid + residual_min = residual_mid + else: + x_max = x_mid + residual_max = residual_mid + + return x_mid diff --git a/bird/utilities/ofio.py b/bird/utilities/ofio.py index 776c1204..a1e51b3c 100644 --- a/bird/utilities/ofio.py +++ b/bird/utilities/ofio.py @@ -479,47 +479,92 @@ def read_field( return field, field_dict -def readSizeGroups(file): - sizeGroup = {} - f = open(file, "r") - lines = f.readlines() - f.close() - begin = None - for iline, line in enumerate(lines): - if "sizeGroups" in line: - begin = iline + 2 - if not begin is None and ");" in line: - end = iline - 1 - break - for line in lines[begin : end + 1]: - tmp = line.split("{") - name = tmp[0].strip() - size = tmp[1].split(";")[0].split()[1] - sizeGroup[name] = float(size) - # Sort by size - sizeGroup = dict(sorted(sizeGroup.items(), key=lambda item: item[1])) - binGroup = {} - groups = list(sizeGroup.keys()) - for igroup, group in enumerate(groups): +def read_size_groups(case_folder: str) -> dict: + """ + Get the bubble size groups represented by the number density fields (fX.gas) + + Parameters + ---------- + case_folder: str + Path to case folder + + Returns + ---------- + ndf_groups: dict + Dictionary describing the number density fields + Key is the name of the number density field (fX) + Value is a dictionary with keys 'diam' and 'bin_size' + corresponding to the bubble diameter and the bin size in m + """ + + phaseProperties_file = os.path.join( + case_folder, "constant", "phaseProperties" + ) + phaseProperties = read_openfoam_dict(phaseProperties_file) + + # Make sure that population balance is used + try: + assert phaseProperties["populationBalances"] == ["bubbles"] + assert phaseProperties["gas"]["diameterModel"] == "velocityGroup" + assert ( + phaseProperties["gas"]["velocityGroupCoeffs"]["populationBalance"] + == "bubbles" + ) + except AssertionError: + logger.warning( + "Reading size groups for a case where population balance is not used" + ) + + size_grouptmp = phaseProperties["gas"]["velocityGroupCoeffs"]["sizeGroups"] + logger.debug(f"Found {len(size_grouptmp)} number density fields") + + # Associate number density field to size + size_group = {} + for name in size_grouptmp: + size_group[name] = float(size_grouptmp[name]["dSph"]) + + # Sort by size in ascending order + size_group = dict(sorted(size_group.items(), key=lambda item: item[1])) + + # Get the bin size + bin_size_group = {} + group_names = list(size_group.keys()) + logger.warning( + "The bin sizes definition make assumption of uniformity and need to be revisited if used" + ) + for igroup, name in enumerate(group_names): if igroup == 0: bin_size = ( - sizeGroup[groups[igroup + 1]] - sizeGroup[groups[igroup]] + size_group[group_names[igroup + 1]] + - size_group[group_names[igroup]] ) - elif igroup == len(groups) - 1: + elif igroup == len(group_names) - 1: bin_size = ( - sizeGroup[groups[igroup]] - sizeGroup[groups[igroup - 1]] + size_group[group_names[igroup]] + - size_group[group_names[igroup - 1]] ) else: bin_size_p = ( - sizeGroup[groups[igroup + 1]] - sizeGroup[groups[igroup]] + size_group[group_names[igroup + 1]] + - size_group[group_names[igroup]] ) bin_size_m = ( - sizeGroup[groups[igroup]] - sizeGroup[groups[igroup - 1]] + size_group[group_names[igroup]] + - size_group[group_names[igroup - 1]] ) assert abs(bin_size_p - bin_size_m) < 1e-12 bin_size = bin_size_m - binGroup[group] = bin_size - return sizeGroup, binGroup + bin_size_group[name] = bin_size + + # Put size and bin size together + ndf_groups = {} + for name in group_names: + ndf_groups[name] = { + "diam": size_group[name], + "bin_size": bin_size_group[name], + } + + return ndf_groups def get_case_times( @@ -748,15 +793,30 @@ def parse_block(index: int) -> tuple: index += 1 result[key] = dictlist else: - # Standard list + # Standard list or dict-like list (e.g. sizeGroups) lst = [] + dictlist = {} + while tokens[index] != ")": - lst.append(tokens[index]) + label = tokens[index] index += 1 - index += 1 + + if index < len(tokens) and tokens[index] == "{": + # Inline dict entry like: f1 { dSph 1e-3; value 0.0; } + index += 1 + subdict, index = parse_block(index) + dictlist[label] = subdict + else: + # Skip semicolons if present (e.g. in lists like species) + if label != ";": + lst.append(label) + + index += 1 # skip ')' if index < len(tokens) and tokens[index] == ";": index += 1 - result[key] = lst + + # Choose dictlist only if it has content; otherwise, use lst + result[key] = dictlist if dictlist else lst # key followed by scalar elif index < len(tokens): diff --git a/bird/utilities/stl_plotting.py b/bird/utilities/stl_plotting.py index 19fdaabb..e6b8b835 100644 --- a/bird/utilities/stl_plotting.py +++ b/bird/utilities/stl_plotting.py @@ -1,10 +1,25 @@ +import matplotlib import numpy as np +from mpl_toolkits import mplot3d from prettyPlot.plotting import plt, pretty_labels, pretty_legend +from stl import mesh -def plotSTL(stl_file): - from mpl_toolkits import mplot3d - from stl import mesh +def plot_stl(stl_file: str) -> matplotlib.axes.Axes: + """ + Plot a 2D STL file with matplotlib + This is useful to check boundary condition definition with STL + + Parameters + ---------- + stl_file: str + Name of the STL file to display + + Returns + ---------- + axes: matplotlib.axes.Axes + Axes of the plot created + """ # Create a new plot figure = plt.figure() @@ -25,7 +40,8 @@ def plotSTL(stl_file): max_z = np.amax(your_mesh.points[:, 2]) amp = np.array([max_x - min_x, max_y - min_y, max_z - min_z]) - # 2D view + + # Rotate so we have a 2D view if abs(amp[0]) < 1e-12: axes.view_init(0, 90) elif abs(amp[1]) < 1e-12: diff --git a/bird/version.py b/bird/version.py index 68c45d06..6825d034 100644 --- a/bird/version.py +++ b/bird/version.py @@ -1,3 +1,3 @@ """Bio reactor design version""" -__version__ = "0.0.45" +__version__ = "0.0.50" diff --git a/docs/source/bird.postprocess.rst b/docs/source/bird.postprocess.rst index 9ac22476..8dfe4b94 100644 --- a/docs/source/bird.postprocess.rst +++ b/docs/source/bird.postprocess.rst @@ -1,29 +1,30 @@ bird.postprocess package ======================== -bird.postprocess.conditional\_mean module ------------------------------------------ - -.. automodule:: bird.postprocess.conditional_mean - :members: - :undoc-members: - :show-inheritance: - -bird.postprocess.early\_pred module ------------------------------------ - -.. automodule:: bird.postprocess.early_pred - :members: - :undoc-members: - :show-inheritance: - -bird.postprocess.kla\_utils module ----------------------------------- - -.. automodule:: bird.postprocess.kla_utils - :members: - :undoc-members: - :show-inheritance: +.. comment API for now + bird.postprocess.conditional\_mean module + ----------------------------------------- + + .. automodule:: bird.postprocess.conditional_mean + :members: + :undoc-members: + :show-inheritance: + + bird.postprocess.early\_pred module + ----------------------------------- + + .. automodule:: bird.postprocess.early_pred + :members: + :undoc-members: + :show-inheritance: + + bird.postprocess.kla\_utils module + ---------------------------------- + + .. automodule:: bird.postprocess.kla_utils + :members: + :undoc-members: + :show-inheritance: bird.postprocess.post\_quantities module ---------------------------------------- diff --git a/docs/source/bird.rst b/docs/source/bird.rst index e8fb4a96..9d4cc2d5 100644 --- a/docs/source/bird.rst +++ b/docs/source/bird.rst @@ -7,9 +7,11 @@ Subpackages .. toctree:: :maxdepth: 1 - bird.calibration - bird.meshing + .. comment API for now + bird.calibration + bird.meshing + bird.preprocess + bird.postprocess - bird.preprocess bird.utilities diff --git a/papers/co2_model/validation/compareToExp.py b/papers/co2_model/validation/compareToExp.py index 5291917a..6f6964c3 100644 --- a/papers/co2_model/validation/compareToExp.py +++ b/papers/co2_model/validation/compareToExp.py @@ -1,11 +1,11 @@ import argparse import os import sys +from pathlib import Path import numpy as np from prettyPlot.plotting import plt, pretty_labels, pretty_legend -from bird.utilities.folderManagement import * from bird.utilities.ofio import * parser = argparse.ArgumentParser(description="Case folder") @@ -50,7 +50,7 @@ figureFolder = "Figures" figureFolder = os.path.join(figureFolder, args.figureFolder) -makeRecursiveFolder(figureFolder) +Path(figureFolder).mkdir(exist_ok=True, parents=True) conv17 = np.load(os.path.join(args.caseFolder17, "convergence_gh.npz")) conv19 = np.load(os.path.join(args.caseFolder19, "convergence_gh.npz")) diff --git a/papers/sparger/process_geom/compare_qoi.py b/papers/sparger/process_geom/compare_qoi.py index f526e344..0f5d3012 100644 --- a/papers/sparger/process_geom/compare_qoi.py +++ b/papers/sparger/process_geom/compare_qoi.py @@ -1,17 +1,15 @@ import argparse -import sys - -import numpy as np - -sys.path.append("../utilities") import os import pickle +import sys +from pathlib import Path -from folderManagement import * -from ofio import * +import numpy as np from prettyPlot.plotting import plt, pretty_labels, pretty_legend -from bird.utilities.label_plot import label_conv +from bird.utilities.ofio import * + +from .label_plot import label_conv parser = argparse.ArgumentParser(description="Compare Qoi") parser.add_argument( @@ -88,7 +86,7 @@ dataFiles = args.dataFiles mode = args.mode -makeRecursiveFolder(figureFolder) +Path(figureFolder).mkdir(parents=True, exist_ok=True) data_structs = [np.load(dataFile) for dataFile in dataFiles] symbol_list = ["o", "s", "^"] diff --git a/bird/utilities/label_plot.py b/papers/sparger/process_geom/label_plot.py similarity index 100% rename from bird/utilities/label_plot.py rename to papers/sparger/process_geom/label_plot.py diff --git a/papers/sparger/process_geom/plot_cond.py b/papers/sparger/process_geom/plot_cond.py index 04bf9c0a..6bebeb5d 100644 --- a/papers/sparger/process_geom/plot_cond.py +++ b/papers/sparger/process_geom/plot_cond.py @@ -1,17 +1,40 @@ import argparse +import os +import pickle import sys +from pathlib import Path import numpy as np +from prettyPlot.plotting import plt, pretty_labels -sys.path.append("../utilities") -import os -import pickle +from bird.utilities.ofio import * -from folderManagement import * -from ofio import * -from prettyPlot.plotting import plt, pretty_labels +from .label_plot import label_conv + + +def getManyFolders(rootFolder, prefix="flat_donut"): + # Read Time + fold_tmp = os.listdir(rootFolder) + fold_num = [] + # remove non floats + for i, entry in reversed(list(enumerate(fold_tmp))): + if not entry.startswith(prefix) or entry.endswith("N2"): + a = fold_tmp.pop(i) + # print('removed ', a) + for entry in fold_tmp: + num = re.findall(r"\d+", entry) + if len(num) > 1: + 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])) + + sortedFold = [ + x for _, x in sorted(zip(fold_num, fold_tmp), key=lambda pair: pair[0]) + ] + return sortedFold -from bird.utilities.label_plot import label_conv parser = argparse.ArgumentParser(description="Plot cond qoi") parser.add_argument( @@ -96,7 +119,7 @@ args = parser.parse_args() figureFolder = "Figures" figureFolder = os.path.join(figureFolder, args.figureFolder, "cond") -makeRecursiveFolder(figureFolder) +Path(figureFolder).mkdir(parents=True, exist_ok=True) field_names = args.field_list param_file = args.paramFile study_folder = args.studyFolder diff --git a/papers/sparger/process_geom/plot_cond_multiFold.py b/papers/sparger/process_geom/plot_cond_multiFold.py index b2ef5582..a571a62f 100644 --- a/papers/sparger/process_geom/plot_cond_multiFold.py +++ b/papers/sparger/process_geom/plot_cond_multiFold.py @@ -1,17 +1,15 @@ import argparse -import sys - -import numpy as np - -sys.path.append("../utilities") import os import pickle +import sys +from pathlib import Path -from folderManagement import * -from ofio import * +import numpy as np from prettyPlot.plotting import plt, pretty_labels -from bird.utilities.label_plot import label_conv +from bird.utilities.ofio import * + +from .label_plot import label_conv parser = argparse.ArgumentParser(description="Plot cond qoi") parser.add_argument( @@ -80,7 +78,7 @@ args = parser.parse_args() figureFolder = "Figures" figureFolder = os.path.join(figureFolder, args.figureFolder, "cond") -makeRecursiveFolder(figureFolder) +Path(figureFolder).mkdir(exist_ok=True, parents=True) field_names = args.field_list param_name = args.param_name param_vals = args.param_value diff --git a/papers/sparger/process_geom/plot_qoi.py b/papers/sparger/process_geom/plot_qoi.py index 76227422..81e4ec4f 100644 --- a/papers/sparger/process_geom/plot_qoi.py +++ b/papers/sparger/process_geom/plot_qoi.py @@ -1,17 +1,40 @@ import argparse +import os +import pickle import sys +from pathlib import Path import numpy as np +from prettyPlot.plotting import plt, pretty_labels -sys.path.append("../utilities") -import os -import pickle +from bird.utilities.ofio import * -from folderManagement import * -from ofio import * -from prettyPlot.plotting import plt, pretty_labels +from .label_plot import label_conv + + +def getManyFolders(rootFolder, prefix="flat_donut"): + # Read Time + fold_tmp = os.listdir(rootFolder) + fold_num = [] + # remove non floats + for i, entry in reversed(list(enumerate(fold_tmp))): + if not entry.startswith(prefix) or entry.endswith("N2"): + a = fold_tmp.pop(i) + # print('removed ', a) + for entry in fold_tmp: + num = re.findall(r"\d+", entry) + if len(num) > 1: + 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])) + + sortedFold = [ + x for _, x in sorted(zip(fold_num, fold_tmp), key=lambda pair: pair[0]) + ] + return sortedFold -from bird.utilities.label_plot import label_conv parser = argparse.ArgumentParser(description="Plot Qoi") parser.add_argument( @@ -124,9 +147,9 @@ ) -makeRecursiveFolder(figure_qoi_folder) -makeRecursiveFolder(figure_qoiNP_folder) -makeRecursiveFolder(figure_qoiConv_folder) +Path(figure_qoi_folder).mkdir(exist_ok=True, parents=True) +Path(figure_qoiNP_folder).mkdir(exist_ok=True, parents=True) +Path(figure_qoiConv_folder).mkdir(exist_ok=True, parents=True) var_names = args.var_list vmin = args.vmin vmax = args.vmax diff --git a/papers/sparger/process_geom/plot_qoi_multiFold.py b/papers/sparger/process_geom/plot_qoi_multiFold.py index 587ea49b..6f2e2547 100644 --- a/papers/sparger/process_geom/plot_qoi_multiFold.py +++ b/papers/sparger/process_geom/plot_qoi_multiFold.py @@ -1,17 +1,15 @@ import argparse -import sys - -import numpy as np - -sys.path.append("../utilities") import os import pickle +import sys +from pathlib import Path -from folderManagement import * -from ofio import * +import numpy as np from prettyPlot.plotting import plt, pretty_labels -from bird.utilities.label_plot import label_conv +from bird.utilities.ofio import * + +from .label_plot import label_conv parser = argparse.ArgumentParser(description="Plot Qoi") parser.add_argument( @@ -82,8 +80,8 @@ figure_qoiConv_Folder = os.path.join( figureFolder, args.figureFolder, "qoi_conv" ) -makeRecursiveFolder(figure_qoi_Folder) -makeRecursiveFolder(figure_qoiConv_Folder) +Path(figure_qoi_Folder).mkdir(parents=True, exist_ok=True) +Path(figure_qoiConv_Folder).mkdir(parents=True, exist_ok=True) var_names = args.var_list param_name = args.param_name param_vals = args.param_value diff --git a/tests/io/test_case.py b/tests/io/test_case.py index 2d80f37f..b0b9c636 100644 --- a/tests/io/test_case.py +++ b/tests/io/test_case.py @@ -80,8 +80,3 @@ def test_mesh_vol(): assert np.linalg.norm(volumes - volumes2) < 1e-12 - -if __name__ == "__main__": - test_case_time() - test_mesh() - test_mesh_vol() diff --git a/tests/io/test_read_foam_dict.py b/tests/io/test_read_foam_dict.py index b8051d24..b93f7e23 100644 --- a/tests/io/test_read_foam_dict.py +++ b/tests/io/test_read_foam_dict.py @@ -41,6 +41,41 @@ def test_read_phaseProperties(): ) +def test_read_ndf(): + """ + Test for reading content of `constant/phaseProperties` with population balance + """ + const_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + "constant", + ) + # Read non uniform field + foam_dict = read_openfoam_dict( + filename=os.path.join(const_folder, "phaseProperties") + ) + + assert foam_dict["phases"] == ["gas", "liquid"] + assert foam_dict["populationBalances"] == ["bubbles"] + assert foam_dict["gas"]["diameterModel"] == "velocityGroup" + assert len(foam_dict["gas"]["velocityGroupCoeffs"]["sizeGroups"]) == 21 + assert ( + abs( + float( + foam_dict["gas"]["velocityGroupCoeffs"]["sizeGroups"]["f4"][ + "dSph" + ] + ) + - 2.5e-3 + ) + < 1e-12 + ) + + def test_read_thermophysicalProperties(): """ Test for reading content of `constant/thermophysicalProperties` @@ -58,6 +93,7 @@ def test_read_thermophysicalProperties(): filename=os.path.join(const_folder, "thermophysicalProperties.gas") ) + print(foam_dict) assert foam_dict["species"] == ["H2", "CO2", "N2"] assert ( foam_dict["CO2"]["thermodynamics"]["highCpCoeffs"][0] == "3.85746029" @@ -106,9 +142,3 @@ def test_read_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() diff --git a/tests/io/test_read_foam_fields.py b/tests/io/test_read_foam_fields.py index 44afb59e..e75f1ce4 100644 --- a/tests/io/test_read_foam_fields.py +++ b/tests/io/test_read_foam_fields.py @@ -213,6 +213,3 @@ def test_read_mu_liquid(): os.path.join(case_folder, "80", "thermo:mu.liquid"), ) - -if __name__ == "__main__": - test_read_mu_liquid() diff --git a/tests/io/test_read_global_vars.py b/tests/io/test_read_global_vars.py index edc164c8..560e7bd4 100644 --- a/tests/io/test_read_global_vars.py +++ b/tests/io/test_read_global_vars.py @@ -105,6 +105,3 @@ def test_read_global_vars(): < 1e-3 ) - -if __name__ == "__main__": - test_read_global_vars() diff --git a/tests/io/test_read_ndf.py b/tests/io/test_read_ndf.py new file mode 100644 index 00000000..2ea0b069 --- /dev/null +++ b/tests/io/test_read_ndf.py @@ -0,0 +1,26 @@ +import os +from pathlib import Path + +import numpy as np + +from bird.utilities.ofio import read_size_groups + + +def test_read_size_groups(): + """ + Test for getting size group info + """ + case_folder = os.path.join( + Path(__file__).parent, + "..", + "..", + "bird", + "postprocess", + "data_conditional_mean", + ) + ndf_groups = read_size_groups(case_folder) + + assert len(ndf_groups) == 21 + assert abs(ndf_groups["f4"]["diam"] - 2.5e-3) < 1e-12 + assert abs(ndf_groups["f20"]["diam"] - 10.5e-3) < 1e-12 + # Place holder for bin size diff --git a/tests/meshing/test_block_cyl_mesh.py b/tests/meshing/test_block_cyl_mesh.py index c5d181a1..c9924ef9 100644 --- a/tests/meshing/test_block_cyl_mesh.py +++ b/tests/meshing/test_block_cyl_mesh.py @@ -13,7 +13,7 @@ def base_mesh(input_file, topo_file, output_folder): - os.makedirs(output_folder, exist_ok=True) + Path(output_folder).mkdir(parents=True, exist_ok=True) geomDict = assemble_geom(input_file, topo_file) meshDict = assemble_mesh(input_file, geomDict) writeBlockMeshDict(output_folder, geomDict, meshDict) diff --git a/tests/meshing/test_block_rect_mesh.py b/tests/meshing/test_block_rect_mesh.py index 32e7c855..f651161f 100644 --- a/tests/meshing/test_block_rect_mesh.py +++ b/tests/meshing/test_block_rect_mesh.py @@ -13,7 +13,7 @@ def base_mesh(input_file, output_folder): - os.makedirs(output_folder, exist_ok=True) + Path(output_folder).mkdir(parents=True, exist_ok=True) geomDict = assemble_geom(input_file) meshDict = assemble_mesh(input_file) writeBlockMeshDict(output_folder, geomDict, meshDict) diff --git a/tests/postprocess/test_cond_mean.py b/tests/postprocess/test_cond_mean.py index a3ec6785..f89e6c6f 100644 --- a/tests/postprocess/test_cond_mean.py +++ b/tests/postprocess/test_cond_mean.py @@ -44,6 +44,3 @@ def test_compute_cond(): pretty_labels(plot_name, "y [m]", 14) plt.close() - -if __name__ == "__main__": - test_compute_cond() diff --git a/tests/postprocess/test_post_quantities.py b/tests/postprocess/test_post_quantities.py index 9ecde554..faf8e205 100644 --- a/tests/postprocess/test_post_quantities.py +++ b/tests/postprocess/test_post_quantities.py @@ -367,10 +367,3 @@ def test_fitted_kla(): for time_folder in [str(entry) for entry in range(81, 91)]: shutil.rmtree(os.path.join(case_folder, time_folder)) - -if __name__ == "__main__": - # test_compute_superficial_gas_velocity() - # test_compute_gh() - # test_ave_y_liq() - # test_ave_conc_liq() - test_fitted_kla() diff --git a/tests/preprocess/test_stl_patch.py b/tests/preprocess/test_stl_patch.py index ea339480..bb5f9e90 100644 --- a/tests/preprocess/test_stl_patch.py +++ b/tests/preprocess/test_stl_patch.py @@ -7,7 +7,7 @@ from bird.preprocess.stl_patch.stl_bc import write_boundaries from bird.utilities.parser import parse_json -from bird.utilities.stl_plotting import plotSTL +from bird.utilities.stl_plotting import plot_stl def test_spider_sparger(): @@ -28,7 +28,7 @@ def test_spider_sparger(): # Output to temporary directory and delete when done with tempfile.TemporaryDirectory() as tmpdirname: write_boundaries(input_dict, output_folder=tmpdirname) - axes = plotSTL(os.path.join(tmpdirname, "inlets.stl")) + axes = plot_stl(os.path.join(tmpdirname, "inlets.stl")) pretty_labels("x", "y", zlabel="z", fontsize=14, ax=axes) @@ -51,7 +51,7 @@ def test_loop_reactor(): # Output to temporary directory and delete when done with tempfile.TemporaryDirectory() as tmpdirname: write_boundaries(input_dict, output_folder=tmpdirname) - axes = plotSTL(os.path.join(tmpdirname, "inlets.stl")) + axes = plot_stl(os.path.join(tmpdirname, "inlets.stl")) pretty_labels("x", "y", zlabel="z", fontsize=14, ax=axes) @@ -73,12 +73,6 @@ def test_loop_reactor_branch(): # Output to temporary directory and delete when done with tempfile.TemporaryDirectory() as tmpdirname: write_boundaries(input_dict, output_folder=tmpdirname) - axes = plotSTL(os.path.join(tmpdirname, "inlets.stl")) + axes = plot_stl(os.path.join(tmpdirname, "inlets.stl")) pretty_labels("x", "y", zlabel="z", fontsize=14, ax=axes) - -if __name__ == "__main__": - from prettyPlot.plotting import plt - - test_spider_sparger() - plt.show()