From 9953198a475475cfabc334927224fc73225a84d7 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Wed, 6 Aug 2025 10:11:06 +0200 Subject: [PATCH 01/15] Initial code to add micro-dumux surrogate for two-scale-heat-conduction --- .../offline-training/model.py | 5 +++ .../offline-training/train_surrogate.py | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py b/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py new file mode 100644 index 000000000..5284f7485 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py @@ -0,0 +1,5 @@ +# Dummy model function for the micro-dumux surrogate. +# We do not wrap the original DuMuX model because we will directly provide +# snapshots (computed by the Micro Manager) to BayesValidRox. +def model(samples): + return None diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py b/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py new file mode 100644 index 000000000..3b4eeeb5e --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py @@ -0,0 +1,40 @@ +from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine +import h5py +import joblib +import numpy as np + + +def read_snapshots(snapshots_dir: str) -> np.array, dict: + inputs = np.empty((0, 0)) + outputs = {"porosity": [], "k_00": [], "k_01": [], "k_10": [], "k_11": [], "x_values": []} # Need to be 2D arrays + return inputs, outputs + + +def create_surrogate(snapshots_dir: str) -> None: + # We create a fake model from model.py because we directly provide the + # input and outputs from the Micro Manager snapshots. + model = PyLinkForwardModel() + model.py_file = "model" + model.name = "micro-dumux-surrogate" + + inputs, outputs = read_snapshots(snapshots_dir) + + inputs = Input() + inputs.add_marginals("concentration", "unif", ) + + exp_design = ExpDesigns() + exp_design.x = inputs + exp_design.y = outputs + + # Create the surrogate model + meta_model = PCE(inputs) + meta_model.meta_model_type = "aPCE" + meta_model.pce_reg_method = "FastARD" + meta_model.pce_degree = 5 + + # Train the surrogate model + engine = Engine(meta_model, model, exp_design) + engine.train_normal() + + with open(f'{model.name}.pkl', 'wb') as output: + joblib.dump(engine, output, 2) From 0ed5449566131a04b4b986eb6ed6ce75afbf8de2 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Wed, 6 Aug 2025 10:44:02 +0200 Subject: [PATCH 02/15] Add mechanism to create snapshots using the Micro Manager --- .../micro-dumux-surrogate/create-snapshots.sh | 29 +++++++++++++++++++ .../micro-manager-snapshot-config.json | 11 +++++++ .../{offline-training => }/model.py | 0 .../{offline-training => }/train_surrogate.py | 28 ++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100755 two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json rename two-scale-heat-conduction/micro-dumux-surrogate/{offline-training => }/model.py (100%) rename two-scale-heat-conduction/micro-dumux-surrogate/{offline-training => }/train_surrogate.py (64%) diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh b/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh new file mode 100755 index 000000000..8d616e8ae --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e -u + +. ../../tools/log.sh +exec > >(tee --append "$LOGFILE") 2>&1 + +usage() { echo "Usage: cmd [-s] [-p n]" 1>&2; exit 1; } + +# Check if no input argument was provided +if [ -z "$*" ] ; then + echo "No input argument provided. Micro Manager for snapshot computation is launched in serial" + micro-manager-precice --snapshot micro-manager-snapshot-config.json +fi + +while getopts ":sp" opt; do + case ${opt} in + s) + micro-manager-precice --snapshot micro-manager-snapshot-config.json + ;; + p) + mpiexec -n "$2" micro-manager-precice --snapshot micro-manager-snapshot-config.json + ;; + *) + usage + ;; + esac +done + +close_log diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json new file mode 100644 index 000000000..674a4156b --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json @@ -0,0 +1,11 @@ +{ + "micro_file_name": "micro_sim", + "coupling_params": { + "parameter_file_name": "input_samples.hdf5", + "read_data_names": ["concentration"], + "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"] + }, + "simulation_params": { + "micro_dt": 0.01 + } +} diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py b/two-scale-heat-conduction/micro-dumux-surrogate/model.py similarity index 100% rename from two-scale-heat-conduction/micro-dumux-surrogate/offline-training/model.py rename to two-scale-heat-conduction/micro-dumux-surrogate/model.py diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py b/two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py similarity index 64% rename from two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py rename to two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py index 3b4eeeb5e..89b65ec23 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/offline-training/train_surrogate.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py @@ -1,9 +1,23 @@ +import os from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine import h5py import joblib import numpy as np +def create_snapshots() -> None: + """ + Create snapshots of the DuMuX model in micro-dumux/ using the Micro Manager. + The snapshots are saved in the specified directory. + """ + concentration_samples = np.linspace(0.0, 0.5, 50) + + with h5py.File("input_samples.hdf5", "w") as f: + f.create_dataset("concentration", data=concentration_samples) + + os.subprocess.run(["./create-snapshots.sh", "-s"], check=True) + + def read_snapshots(snapshots_dir: str) -> np.array, dict: inputs = np.empty((0, 0)) outputs = {"porosity": [], "k_00": [], "k_01": [], "k_10": [], "k_11": [], "x_values": []} # Need to be 2D arrays @@ -38,3 +52,17 @@ def create_surrogate(snapshots_dir: str) -> None: with open(f'{model.name}.pkl', 'wb') as output: joblib.dump(engine, output, 2) + + +def main(): + snapshots_dir = "snapshots" + if not os.path.exists(snapshots_dir): + os.makedirs(snapshots_dir) + + os.chdir(snapshots_dir) + create_snapshots() + create_surrogate(snapshots_dir) + + +if __name__ == "__main__": + main() From 3f8ffe8d0174dda2370dfd4ed4f247fad408791c Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Wed, 6 Aug 2025 14:39:25 +0200 Subject: [PATCH 03/15] Add more functionality related to creation and training of the surrogate --- .../surrogate_workflow.py | 147 ++++++++++++++++++ .../micro-dumux-surrogate/train_surrogate.py | 68 -------- 2 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py delete mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py new file mode 100644 index 000000000..b96d6c55d --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -0,0 +1,147 @@ +import os +import subprocess +from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine +import h5py +import joblib +import numpy as np + + +def create_snapshots() -> None: + """ + Create snapshots of the DuMuX model in micro-dumux/ using the Micro Manager. + The snapshots are saved in the specified directory. + """ + # What effect do uniformely distributed concentration samples have on the quality of the surrogate model? + concentration_samples = np.linspace(0.0, 0.5, 50) + + with h5py.File("input_samples.hdf5", "w") as f: + f.create_dataset("concentration", data=concentration_samples) + + subprocess.run(["./create-snapshots.sh", "-s"], check=True) + + +def read_snapshots(snapshots_dir: str) -> tuple: + """ + Read the snapshots created by the Micro Manager and return the inputs and outputs. + The inputs are the concentration samples, and the outputs are the porosity and conductivity matrix values. + + Parameters + ---------- + snapshots_dir : str + The directory where the snapshots are stored. + + Returns + ------- + tuple + A tuple containing the inputs (concentration samples) and outputs (porosity and conductivity values). + """ + with h5py.File(os.path.join(snapshots_dir, "output.hdf5"), "r") as f: + concentration_data = f["concentration"] + porosity_data = f["porosity"] + k_00_data = f["k_00"] + k_01_data = f["k_01"] + k_10_data = f["k_10"] + k_11_data = f["k_11"] + + concentration = np.swapaxes(np.array([concentration_data]), 0, 1) + + porosity = np.swapaxes(np.array([porosity_data]), 0, 1) + k_00 = np.swapaxes(np.array([k_00_data]), 0, 1) + k_01 = np.swapaxes(np.array([k_01_data]), 0, 1) + k_10 = np.swapaxes(np.array([k_10_data]), 0, 1) + k_11 = np.swapaxes(np.array([k_11_data]), 0, 1) + + outputs = {"porosity": porosity, "k_00": k_00, "k_01": k_01, "k_10": k_10, "k_11": k_11, "x_values": np.array([0])} + return concentration, outputs + + +def split_samples(X, y, n_valid): + """ + Split the samples and evaluations into training and validation/test data. + The split is performed randomly. + + Parameters + ---------- + X : np.ndarray + Samples, shape (#samples, #parameters) + y : dict + Corresponding model evaluations. Expected to match BVR output format. + n_valid : int + Number of samples to keep for validation. + + Returns + ------- + X_train, y_train, + X_valid, y_valid + """ + n_samples = X.shape[0] + if n_valid >= n_samples: + raise AttributeError('The set number of validation points is invalid.') + + # Random split + n_train = n_samples - n_valid + choice = np.random.choice( + range(n_samples), size=(n_train,), replace=False + ) + ind = np.zeros(n_samples, dtype=bool) + ind[choice] = True + + # Split samples + X_train = X[ind] + X_valid = X[~ind] + + # Split outputs + y_train = {} + y_valid = {} + for key in y: + if key != "x_values": + y_train[key] = y[key][ind] + y_valid[key] = y[key][~ind] + + return X_train, y_train, X_valid, y_valid + + +def create_surrogate(snapshots_dir: str) -> None: + # We create a fake model from model.py because we directly provide the + # input and outputs from the Micro Manager snapshots. + model = PyLinkForwardModel() + model.py_file = "model" + model.name = "micro-dumux-surrogate" + model.link_type = "function" + model.output_names = ["porosity", "k_00", "k_01", "k_10", "k_11"] + + x, y = read_snapshots(snapshots_dir) + + inputs = Input() + inputs.add_marginals(name="concentration", dist_type="unif", parameters=[0, 0.5]) + + exp_design = ExpDesigns(inputs) + exp_design.x = x + exp_design.y = y + + # Create the surrogate model + meta_model = PCE(inputs) + meta_model.meta_model_type = "aPCE" + meta_model.pce_reg_method = "FastARD" + meta_model.pce_degree = 5 + + # Train the surrogate model + engine = Engine(meta_model, model, exp_design) + engine.train_normal() + + with open(f'{model.name}.pkl', 'wb') as output: + joblib.dump(engine, output, 2) + + +def main(): + snapshots_dir = "snapshots" + if not os.path.exists(snapshots_dir): + os.makedirs(snapshots_dir) + + os.chdir(snapshots_dir) + create_snapshots() + create_surrogate(snapshots_dir) + + +if __name__ == "__main__": + main() diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py b/two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py deleted file mode 100644 index 89b65ec23..000000000 --- a/two-scale-heat-conduction/micro-dumux-surrogate/train_surrogate.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine -import h5py -import joblib -import numpy as np - - -def create_snapshots() -> None: - """ - Create snapshots of the DuMuX model in micro-dumux/ using the Micro Manager. - The snapshots are saved in the specified directory. - """ - concentration_samples = np.linspace(0.0, 0.5, 50) - - with h5py.File("input_samples.hdf5", "w") as f: - f.create_dataset("concentration", data=concentration_samples) - - os.subprocess.run(["./create-snapshots.sh", "-s"], check=True) - - -def read_snapshots(snapshots_dir: str) -> np.array, dict: - inputs = np.empty((0, 0)) - outputs = {"porosity": [], "k_00": [], "k_01": [], "k_10": [], "k_11": [], "x_values": []} # Need to be 2D arrays - return inputs, outputs - - -def create_surrogate(snapshots_dir: str) -> None: - # We create a fake model from model.py because we directly provide the - # input and outputs from the Micro Manager snapshots. - model = PyLinkForwardModel() - model.py_file = "model" - model.name = "micro-dumux-surrogate" - - inputs, outputs = read_snapshots(snapshots_dir) - - inputs = Input() - inputs.add_marginals("concentration", "unif", ) - - exp_design = ExpDesigns() - exp_design.x = inputs - exp_design.y = outputs - - # Create the surrogate model - meta_model = PCE(inputs) - meta_model.meta_model_type = "aPCE" - meta_model.pce_reg_method = "FastARD" - meta_model.pce_degree = 5 - - # Train the surrogate model - engine = Engine(meta_model, model, exp_design) - engine.train_normal() - - with open(f'{model.name}.pkl', 'wb') as output: - joblib.dump(engine, output, 2) - - -def main(): - snapshots_dir = "snapshots" - if not os.path.exists(snapshots_dir): - os.makedirs(snapshots_dir) - - os.chdir(snapshots_dir) - create_snapshots() - create_surrogate(snapshots_dir) - - -if __name__ == "__main__": - main() From 65f8145d9b8c8cb0021e938a1901b95e7b06ef94 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Wed, 6 Aug 2025 17:40:46 +0200 Subject: [PATCH 04/15] Add first working version of the complete surrogate workflow --- .../micro-manager-snapshot-config.json | 3 + .../micro-dumux-surrogate/params.input | 25 ++++++ .../micro-dumux-surrogate/requirements.txt | 3 + .../run-surrogate-workflow.sh | 9 ++ .../surrogate_workflow.py | 86 +++++++++++++++---- 5 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/params.input create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt create mode 100755 two-scale-heat-conduction/micro-dumux-surrogate/run-surrogate-workflow.sh diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json index 674a4156b..9079d97bf 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json @@ -7,5 +7,8 @@ }, "simulation_params": { "micro_dt": 0.01 + }, + "snapshot_params": { + "initialize_once": true } } diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/params.input b/two-scale-heat-conduction/micro-dumux-surrogate/params.input new file mode 100644 index 000000000..964a6896d --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/params.input @@ -0,0 +1,25 @@ +[Assembly] +Multithreading = false + +[TimeLoop] +TEnd = 0.25 # end time of the simulation +DtInitial = 0.01 # initial time step size +MaxTimeStepSize = 0.01 # maximal time step size + +[Grid] +LowerLeft = 0.0 0.0 # lower left (front) corner of the domain (keep this fixed at 0 0!) +UpperRight = 1.0 1.0 # upper right (back) corner of the domain +Cells = 80 80 # grid resolution in each coordinate direction +Periodic = 1 1 # Periodic Boundary conditions in both dimensions + +[Problem] +xi = 0.08 # phasefield parameter (lambda, set to around 4/Ncells) +omega = 0.01 # phasefield diffusivity/surface tension parameter (gamma) +kt = 1.0 # constant deciding speed of expansion/contraction +eqconc = 0.5 # equilibrium concentration +ks = 1.0 # conductivity of sand material +kg = 0.0 # conductivity of void material +Name = cell_phase # base name for VTK output files +Radius = 0.4 # initial radius of the grain +PhasefieldICScaling = 4.0 # factor in initial phasefield function +MaxPorosity = 0.9686 # porosity cap diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt b/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt new file mode 100644 index 000000000..f167b1a92 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt @@ -0,0 +1,3 @@ +numpy +bayesvalidrox +micro-manager-precice diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/run-surrogate-workflow.sh b/two-scale-heat-conduction/micro-dumux-surrogate/run-surrogate-workflow.sh new file mode 100755 index 000000000..ddf636773 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/run-surrogate-workflow.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e -u + +python3 -m venv .venv +. .venv/bin/activate + +pip install -r requirements.txt + +python surrogate_workflow.py diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index b96d6c55d..56a0dfebd 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -4,6 +4,7 @@ import h5py import joblib import numpy as np +import matplotlib.pyplot as plt def create_snapshots() -> None: @@ -11,13 +12,14 @@ def create_snapshots() -> None: Create snapshots of the DuMuX model in micro-dumux/ using the Micro Manager. The snapshots are saved in the specified directory. """ - # What effect do uniformely distributed concentration samples have on the quality of the surrogate model? + # What effect do uniformly distributed concentration samples have on the quality of the surrogate model? concentration_samples = np.linspace(0.0, 0.5, 50) with h5py.File("input_samples.hdf5", "w") as f: f.create_dataset("concentration", data=concentration_samples) - subprocess.run(["./create-snapshots.sh", "-s"], check=True) + # Run the Micro Manager to create snapshot database + subprocess.call('micro-manager-precice --snapshot micro-manager-snapshot-config.json', shell=True) def read_snapshots(snapshots_dir: str) -> tuple: @@ -35,16 +37,15 @@ def read_snapshots(snapshots_dir: str) -> tuple: tuple A tuple containing the inputs (concentration samples) and outputs (porosity and conductivity values). """ - with h5py.File(os.path.join(snapshots_dir, "output.hdf5"), "r") as f: - concentration_data = f["concentration"] - porosity_data = f["porosity"] - k_00_data = f["k_00"] - k_01_data = f["k_01"] - k_10_data = f["k_10"] - k_11_data = f["k_11"] + with h5py.File(os.path.join(snapshots_dir, "snapshot_data.hdf5"), "r") as f: + concentration_data = f["concentration"][:] + porosity_data = f["porosity"][:] + k_00_data = f["k_00"][:] + k_01_data = f["k_01"][:] + k_10_data = f["k_10"][:] + k_11_data = f["k_11"][:] concentration = np.swapaxes(np.array([concentration_data]), 0, 1) - porosity = np.swapaxes(np.array([porosity_data]), 0, 1) k_00 = np.swapaxes(np.array([k_00_data]), 0, 1) k_01 = np.swapaxes(np.array([k_01_data]), 0, 1) @@ -101,7 +102,11 @@ def split_samples(X, y, n_valid): return X_train, y_train, X_valid, y_valid -def create_surrogate(snapshots_dir: str) -> None: +def create_surrogate(snapshots_dir: str) -> tuple: + """ + Create a surrogate model from the Micro Manager snapshots. + + """ # We create a fake model from model.py because we directly provide the # input and outputs from the Micro Manager snapshots. model = PyLinkForwardModel() @@ -112,18 +117,22 @@ def create_surrogate(snapshots_dir: str) -> None: x, y = read_snapshots(snapshots_dir) + # Split the samples into training and validation sets + n_valid = 10 + x_train, y_train, x_valid, y_valid = split_samples(x, y, n_valid) + inputs = Input() inputs.add_marginals(name="concentration", dist_type="unif", parameters=[0, 0.5]) exp_design = ExpDesigns(inputs) - exp_design.x = x - exp_design.y = y + exp_design.x = x_train + exp_design.y = y_train # Create the surrogate model meta_model = PCE(inputs) meta_model.meta_model_type = "aPCE" meta_model.pce_reg_method = "FastARD" - meta_model.pce_degree = 5 + meta_model.pce_deg = 5 # Train the surrogate model engine = Engine(meta_model, model, exp_design) @@ -132,15 +141,56 @@ def create_surrogate(snapshots_dir: str) -> None: with open(f'{model.name}.pkl', 'wb') as output: joblib.dump(engine, output, 2) + return x_train, y_train + + +def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl"): + """ + Validate the surrogate model using the validation samples and outputs. + + Parameters + ---------- + x_valid : np.ndarray + Validation samples. + y_valid : dict + Corresponding model evaluations for validation samples. + model_name : str + Name of the surrogate model file. + """ + with open(model_name, 'rb') as input: + engine = joblib.load(input) + + y_metamod, _ = engine.eval_metamodel(x_valid) + + # engine.plot_adapt(y_valid, y_metamod, y_metamod_std, x_valid) + + # Compare predictions with true values + plt.figure() + # plt.scatter(y_valid["porosity"], y_metamod["porosity"]) + # plt.xlabel("True Values") + # plt.ylabel("Predictions") + # plt.title(f"Validation: porosity") + # plt.plot([0.5, 1], [0.5, 1], "k--") + plt.scatter(x_valid[:, 0], y_valid["porosity"]) + plt.scatter(x_valid[:, 0], y_metamod["porosity"], marker='x', color='red') + plt.xlabel("Concentration") + plt.ylabel("Porosity") + plt.title(f"Model vs. Surrogate: porosity") + + plt.xlim(0.0, 0.6) + plt.ylim(0.0, 1.2) + plt.show() + def main(): - snapshots_dir = "snapshots" + snapshots_dir = "output" if not os.path.exists(snapshots_dir): os.makedirs(snapshots_dir) - os.chdir(snapshots_dir) - create_snapshots() - create_surrogate(snapshots_dir) + # os.chdir(snapshots_dir) + # create_snapshots() + x_valid, y_valid = create_surrogate(snapshots_dir) + validate_surrogate(x_valid, y_valid) if __name__ == "__main__": From e4ed0dbefd0dda3ada918130cb6b9cbbfd464174 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Thu, 7 Aug 2025 17:12:53 +0200 Subject: [PATCH 05/15] Pass validation data to the validation of the surrogate --- .../micro-dumux-surrogate/clean.sh | 6 +++++ .../surrogate_workflow.py | 26 +++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) create mode 100755 two-scale-heat-conduction/micro-dumux-surrogate/clean.sh diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/clean.sh b/two-scale-heat-conduction/micro-dumux-surrogate/clean.sh new file mode 100755 index 000000000..3e9fd4e51 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/clean.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh +set -e -u + +. ../../tools/cleaning-tools.sh + +clean_dumux . diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index 56a0dfebd..3e8c77dbd 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -118,7 +118,7 @@ def create_surrogate(snapshots_dir: str) -> tuple: x, y = read_snapshots(snapshots_dir) # Split the samples into training and validation sets - n_valid = 10 + n_valid = 20 x_train, y_train, x_valid, y_valid = split_samples(x, y, n_valid) inputs = Input() @@ -141,7 +141,7 @@ def create_surrogate(snapshots_dir: str) -> tuple: with open(f'{model.name}.pkl', 'wb') as output: joblib.dump(engine, output, 2) - return x_train, y_train + return x_valid, y_valid def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl"): @@ -166,19 +166,13 @@ def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl") # Compare predictions with true values plt.figure() - # plt.scatter(y_valid["porosity"], y_metamod["porosity"]) - # plt.xlabel("True Values") - # plt.ylabel("Predictions") - # plt.title(f"Validation: porosity") - # plt.plot([0.5, 1], [0.5, 1], "k--") - plt.scatter(x_valid[:, 0], y_valid["porosity"]) - plt.scatter(x_valid[:, 0], y_metamod["porosity"], marker='x', color='red') - plt.xlabel("Concentration") - plt.ylabel("Porosity") - plt.title(f"Model vs. Surrogate: porosity") - - plt.xlim(0.0, 0.6) - plt.ylim(0.0, 1.2) + plt.scatter(y_valid["porosity"], y_metamod["porosity"]) + plt.xlabel("True Values") + plt.ylabel("Predictions") + plt.title(f"Validation: porosity") + plt.plot([0.4, 1.1], [0.4, 1.1], "k--") + plt.xlim(0.4, 1.1) + plt.ylim(0.4, 1.1) plt.show() @@ -188,7 +182,7 @@ def main(): os.makedirs(snapshots_dir) # os.chdir(snapshots_dir) - # create_snapshots() + create_snapshots() x_valid, y_valid = create_surrogate(snapshots_dir) validate_surrogate(x_valid, y_valid) From 79081bd55debe08a985642a28e1b015e63ab9c55 Mon Sep 17 00:00:00 2001 From: Alex Hocks Date: Fri, 7 Nov 2025 11:15:08 +0100 Subject: [PATCH 06/15] bugfixes and add runnable surrogate model --- .../micro-manager-snapshot-config.json | 5 +- .../micro-manager-surrogate-config.json | 28 +++++++++ .../micro-dumux-surrogate/micro_sim.pc.in | 15 +++++ .../micro-dumux-surrogate/micro_sim_sur.py | 59 +++++++++++++++++++ .../micro-dumux-surrogate/run.sh | 29 +++++++++ .../surrogate_workflow.py | 12 +++- 6 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro_sim.pc.in create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py create mode 100755 two-scale-heat-conduction/micro-dumux-surrogate/run.sh diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json index 9079d97bf..3eabcff11 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json @@ -9,6 +9,7 @@ "micro_dt": 0.01 }, "snapshot_params": { - "initialize_once": true - } + "initialize_once": false + }, + "output_directory": "output" } diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json new file mode 100644 index 000000000..198abbffd --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json @@ -0,0 +1,28 @@ +{ + "micro_file_name": "micro_sim_sur", + "coupling_params": { + "participant_name": "Micro-Manager", + "precice_config_file_name": "../precice-config.xml", + "macro_mesh_name": "macro-mesh", + "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"], + "read_data_names": ["concentration"] + }, + "simulation_params": { + "micro_dt": 0.01, + "macro_domain_bounds": [0.0, 1.0, 0.0, 0.5], + "decomposition": [1, 1], + "adaptivity": "True", + "adaptivity_settings": { + "type": "global", + "data": ["k_00", "k_11", "porosity", "concentration"], + "history_param": 0.1, + "coarsening_constant": 0.2, + "refining_constant": 0.05, + "every_implicit_iteration": "False", + "similarity_measure": "L2rel" + } + }, + "diagnostics": { + "data_from_micro_sims": ["grain_size"] + } +} diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim.pc.in b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim.pc.in new file mode 100644 index 000000000..566aba5e4 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim.pc.in @@ -0,0 +1,15 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +CXX=@CXX@ +CC=@CC@ +DEPENDENCIES=@REQUIRES@ + +Name: @PACKAGE_NAME@ +Version: @VERSION@ +Description: micro_sim module +URL: http://dune-project.org/ +Requires: dumux-phasefield dumux-precice +Libs: -L${libdir} +Cflags: -I${includedir} diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py new file mode 100644 index 000000000..d2502c60e --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py @@ -0,0 +1,59 @@ +""" +Micro simulation Surrogate, requrie previous computation of surrogate model +""" +import os +import subprocess +from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine +import h5py +import joblib +import numpy as np +import math + + +class MicroSimulation: + + def __init__(self, sim_id): + """ + Constructor of MicroSimulation class. + """ + self._sim_id = sim_id + self._state = None + + self._model = None + with open('micro-dumux-surrogate.pkl', 'rb') as input: + self._model = joblib.load(input) + if self._model is None: + raise RuntimeError("Failed to load model.") + + def initialize(self): + output_data = dict() + output_data["k_00"] = 0.4912490635619572 + output_data["k_11"] = 0.4912490635989945 + output_data["porosity"] = 0.4933482661391027 + + if self._sim_id == 0: + output_data["k_00"] = 0.4912490640081466 + output_data["k_11"] = 0.4912490640081367 + + return output_data + + def get_state(self): + return self._state; + + def set_state(self, state): + self._state = state + + def solve(self, macro_data, dt): + model_eval, _ = self._model.eval_metamodel(np.array([macro_data["concentration"]])[:, np.newaxis]) + output_data = dict() + output_data["k_00"] = model_eval["k_00"][0][0] + output_data["k_01"] = model_eval["k_01"][0][0] + output_data["k_10"] = model_eval["k_10"][0][0] + output_data["k_11"] = model_eval["k_11"][0][0] + output_data["porosity"] = model_eval["porosity"][0][0] + output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) + + return output_data + + + diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/run.sh b/two-scale-heat-conduction/micro-dumux-surrogate/run.sh new file mode 100755 index 000000000..f28acb260 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/run.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e -u + +. ../../tools/log.sh +exec > >(tee --append "$LOGFILE") 2>&1 + +usage() { echo "Usage: cmd [-s] [-p n]" 1>&2; exit 1; } + +# Check if no input argument was provided +if [ -z "$*" ] ; then + echo "No input argument provided. Micro Manager is launched in serial" + micro-manager-precice micro-manager-surrogate-config.json +fi + +while getopts ":sp" opt; do + case ${opt} in + s) + micro-manager-precice micro-manager-surrogate-config.json + ;; + p) + mpiexec -n "$2" micro-manager-precice micro-manager-surrogate-config.json + ;; + *) + usage + ;; + esac +done + +close_log diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index 3e8c77dbd..cb7cac941 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -13,7 +13,7 @@ def create_snapshots() -> None: The snapshots are saved in the specified directory. """ # What effect do uniformly distributed concentration samples have on the quality of the surrogate model? - concentration_samples = np.linspace(0.0, 0.5, 50) + concentration_samples = np.linspace(0.0, 0.9, 500) with h5py.File("input_samples.hdf5", "w") as f: f.create_dataset("concentration", data=concentration_samples) @@ -118,9 +118,9 @@ def create_surrogate(snapshots_dir: str) -> tuple: x, y = read_snapshots(snapshots_dir) # Split the samples into training and validation sets - n_valid = 20 + n_valid = 200 x_train, y_train, x_valid, y_valid = split_samples(x, y, n_valid) - + inputs = Input() inputs.add_marginals(name="concentration", dist_type="unif", parameters=[0, 0.5]) @@ -175,6 +175,12 @@ def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl") plt.ylim(0.4, 1.1) plt.show() + plt.figure() + plt.scatter(x_valid, y_valid["porosity"]) + plt.xlabel("valid concentration") + plt.ylabel("valid porosity") + plt.title("IO: con to poro") + plt.show() def main(): snapshots_dir = "output" From 776655a3fb267cdc7c852822026ca514badb975c Mon Sep 17 00:00:00 2001 From: Alex Hocks Date: Fri, 7 Nov 2025 14:47:37 +0100 Subject: [PATCH 07/15] add config for model adaptivity --- .../micro-dumux-mada/mada_switcher.py | 19 ++++++ .../micro-manager-mada-config.json | 35 +++++++++++ .../micro-dumux-mada/micro_sim.pc.in | 15 +++++ .../micro-dumux-mada/micro_sim_sur.py | 59 +++++++++++++++++++ .../micro-dumux-mada/params.input | 25 ++++++++ 5 files changed, 153 insertions(+) create mode 100644 two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py create mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json create mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in create mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py create mode 100644 two-scale-heat-conduction/micro-dumux-mada/params.input diff --git a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py new file mode 100644 index 000000000..0acd40ca8 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py @@ -0,0 +1,19 @@ +import numpy as np + + +def switching_function(resolutions, locations, t, inputs, prev_output, active): + output = np.zeros_like(resolutions) + # in the beginning we only want FOM + if t == 0.0: + output[(resolutions > 0) * active] = -1 + # after small init phase, we want dynamic model selection + # for test purposes we say ROM is accurate in range + else: + concentrations = np.array([d['concentration'] for d in inputs]) + valid_range_mask = (concentrations > 0.45) * (concentrations < 0.55) + fom_mask = resolutions == 0 + + output[fom_mask * valid_range_mask * active] = 1 + output[(~fom_mask) * (~valid_range_mask) * active] = -1 + + return output diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json b/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json new file mode 100644 index 000000000..7ccf5e012 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json @@ -0,0 +1,35 @@ +{ + "micro_file_name": "micro_sim", + "coupling_params": { + "participant_name": "Micro-Manager", + "precice_config_file_name": "../precice-config.xml", + "macro_mesh_name": "macro-mesh", + "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"], + "read_data_names": ["concentration"] + }, + "simulation_params": { + "micro_dt": 0.01, + "macro_domain_bounds": [0.0, 1.0, 0.0, 0.5], + "decomposition": [1, 1], + "adaptivity": "True", + "adaptivity_settings": { + "type": "global", + "data": ["k_00", "k_11", "porosity", "concentration"], + "history_param": 0.1, + "coarsening_constant": 0.2, + "refining_constant": 0.05, + "every_implicit_iteration": "False", + "similarity_measure": "L2rel" + }, + "model_adaptivity": true, + "model_adaptivity_settings": { + "micro_file_names": ["micro_sim", "micro_sim_sur"], + "data": [], + "thresholds": [1.0, 1.0], + "switching_function": "mada_switcher" + } + }, + "diagnostics": { + "data_from_micro_sims": ["grain_size"] + } +} diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in b/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in new file mode 100644 index 000000000..566aba5e4 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in @@ -0,0 +1,15 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +CXX=@CXX@ +CC=@CC@ +DEPENDENCIES=@REQUIRES@ + +Name: @PACKAGE_NAME@ +Version: @VERSION@ +Description: micro_sim module +URL: http://dune-project.org/ +Requires: dumux-phasefield dumux-precice +Libs: -L${libdir} +Cflags: -I${includedir} diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py new file mode 100644 index 000000000..d2502c60e --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py @@ -0,0 +1,59 @@ +""" +Micro simulation Surrogate, requrie previous computation of surrogate model +""" +import os +import subprocess +from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine +import h5py +import joblib +import numpy as np +import math + + +class MicroSimulation: + + def __init__(self, sim_id): + """ + Constructor of MicroSimulation class. + """ + self._sim_id = sim_id + self._state = None + + self._model = None + with open('micro-dumux-surrogate.pkl', 'rb') as input: + self._model = joblib.load(input) + if self._model is None: + raise RuntimeError("Failed to load model.") + + def initialize(self): + output_data = dict() + output_data["k_00"] = 0.4912490635619572 + output_data["k_11"] = 0.4912490635989945 + output_data["porosity"] = 0.4933482661391027 + + if self._sim_id == 0: + output_data["k_00"] = 0.4912490640081466 + output_data["k_11"] = 0.4912490640081367 + + return output_data + + def get_state(self): + return self._state; + + def set_state(self, state): + self._state = state + + def solve(self, macro_data, dt): + model_eval, _ = self._model.eval_metamodel(np.array([macro_data["concentration"]])[:, np.newaxis]) + output_data = dict() + output_data["k_00"] = model_eval["k_00"][0][0] + output_data["k_01"] = model_eval["k_01"][0][0] + output_data["k_10"] = model_eval["k_10"][0][0] + output_data["k_11"] = model_eval["k_11"][0][0] + output_data["porosity"] = model_eval["porosity"][0][0] + output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) + + return output_data + + + diff --git a/two-scale-heat-conduction/micro-dumux-mada/params.input b/two-scale-heat-conduction/micro-dumux-mada/params.input new file mode 100644 index 000000000..964a6896d --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-mada/params.input @@ -0,0 +1,25 @@ +[Assembly] +Multithreading = false + +[TimeLoop] +TEnd = 0.25 # end time of the simulation +DtInitial = 0.01 # initial time step size +MaxTimeStepSize = 0.01 # maximal time step size + +[Grid] +LowerLeft = 0.0 0.0 # lower left (front) corner of the domain (keep this fixed at 0 0!) +UpperRight = 1.0 1.0 # upper right (back) corner of the domain +Cells = 80 80 # grid resolution in each coordinate direction +Periodic = 1 1 # Periodic Boundary conditions in both dimensions + +[Problem] +xi = 0.08 # phasefield parameter (lambda, set to around 4/Ncells) +omega = 0.01 # phasefield diffusivity/surface tension parameter (gamma) +kt = 1.0 # constant deciding speed of expansion/contraction +eqconc = 0.5 # equilibrium concentration +ks = 1.0 # conductivity of sand material +kg = 0.0 # conductivity of void material +Name = cell_phase # base name for VTK output files +Radius = 0.4 # initial radius of the grain +PhasefieldICScaling = 4.0 # factor in initial phasefield function +MaxPorosity = 0.9686 # porosity cap From dae606f5262a6fae8c14362e74e3e5680d03a26d Mon Sep 17 00:00:00 2001 From: Alex Hocks Date: Mon, 1 Dec 2025 20:00:21 +0100 Subject: [PATCH 08/15] update to new mm version --- .../micro-dumux-mada/mada_switcher.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py index 0acd40ca8..84fd05713 100644 --- a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py +++ b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py @@ -1,19 +1,20 @@ import numpy as np -def switching_function(resolutions, locations, t, inputs, prev_output, active): - output = np.zeros_like(resolutions) +def switching_function(resolution, location, t, input, prev_output): + result = 0 # in the beginning we only want FOM if t == 0.0: - output[(resolutions > 0) * active] = -1 + if resolution > 0: result = -1 + else: result = 0 # after small init phase, we want dynamic model selection # for test purposes we say ROM is accurate in range else: - concentrations = np.array([d['concentration'] for d in inputs]) - valid_range_mask = (concentrations > 0.45) * (concentrations < 0.55) - fom_mask = resolutions == 0 + concentration = input['concentration'] + is_valid_range = 0.45 < concentration < 0.55 + is_fom = resolution == 0 - output[fom_mask * valid_range_mask * active] = 1 - output[(~fom_mask) * (~valid_range_mask) * active] = -1 + if is_fom and is_valid_range: result = 1 + if not is_fom and not is_valid_range: result = -1 - return output + return result From 79b8c4616047f7a591997e4b6767f9f80dbf54b7 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Sun, 28 Dec 2025 11:25:20 +0100 Subject: [PATCH 09/15] Add changelog entry --- changelog-entries/684.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog-entries/684.md diff --git a/changelog-entries/684.md b/changelog-entries/684.md new file mode 100644 index 000000000..dc4f8adf8 --- /dev/null +++ b/changelog-entries/684.md @@ -0,0 +1 @@ +- Added PCE-base surrogate for micro-dumux in two-scale heat conduction [#684](https://github.com/precice/tutorials/pull/684) From 53d9b2b9066bdc823565e00ad3d6a3e8cd78311d Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Sun, 28 Dec 2025 11:46:55 +0100 Subject: [PATCH 10/15] autopep8 formatting --- .../micro-dumux-mada/mada_switcher.py | 12 ++++++++---- .../micro-dumux-mada/micro_sim_sur.py | 7 ++----- .../micro-dumux-surrogate/micro_sim_sur.py | 7 ++----- .../micro-dumux-surrogate/surrogate_workflow.py | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py index 84fd05713..963df4502 100644 --- a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py +++ b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py @@ -5,8 +5,10 @@ def switching_function(resolution, location, t, input, prev_output): result = 0 # in the beginning we only want FOM if t == 0.0: - if resolution > 0: result = -1 - else: result = 0 + if resolution > 0: + result = -1 + else: + result = 0 # after small init phase, we want dynamic model selection # for test purposes we say ROM is accurate in range else: @@ -14,7 +16,9 @@ def switching_function(resolution, location, t, input, prev_output): is_valid_range = 0.45 < concentration < 0.55 is_fom = resolution == 0 - if is_fom and is_valid_range: result = 1 - if not is_fom and not is_valid_range: result = -1 + if is_fom and is_valid_range: + result = 1 + if not is_fom and not is_valid_range: + result = -1 return result diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py index d2502c60e..13de7571f 100644 --- a/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py +++ b/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py @@ -18,7 +18,7 @@ def __init__(self, sim_id): """ self._sim_id = sim_id self._state = None - + self._model = None with open('micro-dumux-surrogate.pkl', 'rb') as input: self._model = joblib.load(input) @@ -38,7 +38,7 @@ def initialize(self): return output_data def get_state(self): - return self._state; + return self._state def set_state(self, state): self._state = state @@ -54,6 +54,3 @@ def solve(self, macro_data, dt): output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) return output_data - - - diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py index d2502c60e..13de7571f 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py @@ -18,7 +18,7 @@ def __init__(self, sim_id): """ self._sim_id = sim_id self._state = None - + self._model = None with open('micro-dumux-surrogate.pkl', 'rb') as input: self._model = joblib.load(input) @@ -38,7 +38,7 @@ def initialize(self): return output_data def get_state(self): - return self._state; + return self._state def set_state(self, state): self._state = state @@ -54,6 +54,3 @@ def solve(self, macro_data, dt): output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) return output_data - - - diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index cb7cac941..762312535 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -120,7 +120,7 @@ def create_surrogate(snapshots_dir: str) -> tuple: # Split the samples into training and validation sets n_valid = 200 x_train, y_train, x_valid, y_valid = split_samples(x, y, n_valid) - + inputs = Input() inputs.add_marginals(name="concentration", dist_type="unif", parameters=[0, 0.5]) @@ -182,6 +182,7 @@ def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl") plt.title("IO: con to poro") plt.show() + def main(): snapshots_dir = "output" if not os.path.exists(snapshots_dir): From 8ca17c8693d438cef51e806ce3c975b5c8eed6cc Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Fri, 7 Aug 2026 13:10:30 +0200 Subject: [PATCH 11/15] Remove folder micro-dumux-mada because the new files are accommodated in micro-dumux-surrogate --- .../micro-dumux-mada/mada_switcher.py | 24 -------- .../micro-manager-mada-config.json | 35 ------------ .../micro-dumux-mada/micro_sim.pc.in | 15 ----- .../micro-dumux-mada/micro_sim_sur.py | 56 ------------------- .../micro-dumux-mada/params.input | 25 --------- 5 files changed, 155 deletions(-) delete mode 100644 two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py delete mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json delete mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in delete mode 100644 two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py delete mode 100644 two-scale-heat-conduction/micro-dumux-mada/params.input diff --git a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py b/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py deleted file mode 100644 index 963df4502..000000000 --- a/two-scale-heat-conduction/micro-dumux-mada/mada_switcher.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np - - -def switching_function(resolution, location, t, input, prev_output): - result = 0 - # in the beginning we only want FOM - if t == 0.0: - if resolution > 0: - result = -1 - else: - result = 0 - # after small init phase, we want dynamic model selection - # for test purposes we say ROM is accurate in range - else: - concentration = input['concentration'] - is_valid_range = 0.45 < concentration < 0.55 - is_fom = resolution == 0 - - if is_fom and is_valid_range: - result = 1 - if not is_fom and not is_valid_range: - result = -1 - - return result diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json b/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json deleted file mode 100644 index 7ccf5e012..000000000 --- a/two-scale-heat-conduction/micro-dumux-mada/micro-manager-mada-config.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "micro_file_name": "micro_sim", - "coupling_params": { - "participant_name": "Micro-Manager", - "precice_config_file_name": "../precice-config.xml", - "macro_mesh_name": "macro-mesh", - "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"], - "read_data_names": ["concentration"] - }, - "simulation_params": { - "micro_dt": 0.01, - "macro_domain_bounds": [0.0, 1.0, 0.0, 0.5], - "decomposition": [1, 1], - "adaptivity": "True", - "adaptivity_settings": { - "type": "global", - "data": ["k_00", "k_11", "porosity", "concentration"], - "history_param": 0.1, - "coarsening_constant": 0.2, - "refining_constant": 0.05, - "every_implicit_iteration": "False", - "similarity_measure": "L2rel" - }, - "model_adaptivity": true, - "model_adaptivity_settings": { - "micro_file_names": ["micro_sim", "micro_sim_sur"], - "data": [], - "thresholds": [1.0, 1.0], - "switching_function": "mada_switcher" - } - }, - "diagnostics": { - "data_from_micro_sims": ["grain_size"] - } -} diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in b/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in deleted file mode 100644 index 566aba5e4..000000000 --- a/two-scale-heat-conduction/micro-dumux-mada/micro_sim.pc.in +++ /dev/null @@ -1,15 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ -CXX=@CXX@ -CC=@CC@ -DEPENDENCIES=@REQUIRES@ - -Name: @PACKAGE_NAME@ -Version: @VERSION@ -Description: micro_sim module -URL: http://dune-project.org/ -Requires: dumux-phasefield dumux-precice -Libs: -L${libdir} -Cflags: -I${includedir} diff --git a/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py deleted file mode 100644 index 13de7571f..000000000 --- a/two-scale-heat-conduction/micro-dumux-mada/micro_sim_sur.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Micro simulation Surrogate, requrie previous computation of surrogate model -""" -import os -import subprocess -from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine -import h5py -import joblib -import numpy as np -import math - - -class MicroSimulation: - - def __init__(self, sim_id): - """ - Constructor of MicroSimulation class. - """ - self._sim_id = sim_id - self._state = None - - self._model = None - with open('micro-dumux-surrogate.pkl', 'rb') as input: - self._model = joblib.load(input) - if self._model is None: - raise RuntimeError("Failed to load model.") - - def initialize(self): - output_data = dict() - output_data["k_00"] = 0.4912490635619572 - output_data["k_11"] = 0.4912490635989945 - output_data["porosity"] = 0.4933482661391027 - - if self._sim_id == 0: - output_data["k_00"] = 0.4912490640081466 - output_data["k_11"] = 0.4912490640081367 - - return output_data - - def get_state(self): - return self._state - - def set_state(self, state): - self._state = state - - def solve(self, macro_data, dt): - model_eval, _ = self._model.eval_metamodel(np.array([macro_data["concentration"]])[:, np.newaxis]) - output_data = dict() - output_data["k_00"] = model_eval["k_00"][0][0] - output_data["k_01"] = model_eval["k_01"][0][0] - output_data["k_10"] = model_eval["k_10"][0][0] - output_data["k_11"] = model_eval["k_11"][0][0] - output_data["porosity"] = model_eval["porosity"][0][0] - output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) - - return output_data diff --git a/two-scale-heat-conduction/micro-dumux-mada/params.input b/two-scale-heat-conduction/micro-dumux-mada/params.input deleted file mode 100644 index 964a6896d..000000000 --- a/two-scale-heat-conduction/micro-dumux-mada/params.input +++ /dev/null @@ -1,25 +0,0 @@ -[Assembly] -Multithreading = false - -[TimeLoop] -TEnd = 0.25 # end time of the simulation -DtInitial = 0.01 # initial time step size -MaxTimeStepSize = 0.01 # maximal time step size - -[Grid] -LowerLeft = 0.0 0.0 # lower left (front) corner of the domain (keep this fixed at 0 0!) -UpperRight = 1.0 1.0 # upper right (back) corner of the domain -Cells = 80 80 # grid resolution in each coordinate direction -Periodic = 1 1 # Periodic Boundary conditions in both dimensions - -[Problem] -xi = 0.08 # phasefield parameter (lambda, set to around 4/Ncells) -omega = 0.01 # phasefield diffusivity/surface tension parameter (gamma) -kt = 1.0 # constant deciding speed of expansion/contraction -eqconc = 0.5 # equilibrium concentration -ks = 1.0 # conductivity of sand material -kg = 0.0 # conductivity of void material -Name = cell_phase # base name for VTK output files -Radius = 0.4 # initial radius of the grain -PhasefieldICScaling = 4.0 # factor in initial phasefield function -MaxPorosity = 0.9686 # porosity cap From 770dc187ccf3528e8d2cc4180c082679c74e7d96 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Fri, 7 Aug 2026 14:09:52 +0200 Subject: [PATCH 12/15] Give files appropriate names --- .../micro-dumux-surrogate/create-snapshots.sh | 29 ------------------- .../micro-manager-model-switching-config.json | 22 ++++++++++++++ .../micro-manager-snapshot-config.json | 12 ++++---- .../micro-manager-surrogate-config.json | 28 ------------------ .../micro-dumux-surrogate/switch.py | 24 +++++++++++++++ 5 files changed, 52 insertions(+), 63 deletions(-) delete mode 100755 two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json delete mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json create mode 100644 two-scale-heat-conduction/micro-dumux-surrogate/switch.py diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh b/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh deleted file mode 100755 index 8d616e8ae..000000000 --- a/two-scale-heat-conduction/micro-dumux-surrogate/create-snapshots.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -e -u - -. ../../tools/log.sh -exec > >(tee --append "$LOGFILE") 2>&1 - -usage() { echo "Usage: cmd [-s] [-p n]" 1>&2; exit 1; } - -# Check if no input argument was provided -if [ -z "$*" ] ; then - echo "No input argument provided. Micro Manager for snapshot computation is launched in serial" - micro-manager-precice --snapshot micro-manager-snapshot-config.json -fi - -while getopts ":sp" opt; do - case ${opt} in - s) - micro-manager-precice --snapshot micro-manager-snapshot-config.json - ;; - p) - mpiexec -n "$2" micro-manager-precice --snapshot micro-manager-snapshot-config.json - ;; - *) - usage - ;; - esac -done - -close_log diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json new file mode 100644 index 000000000..3403c26ae --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json @@ -0,0 +1,22 @@ +{ + "micro_file_names": ["micro_sim_sur", "micro_sim"], + "coupling_params": { + "precice_config_file_name": "../precice-config.xml", + "macro_mesh_name": "Macro-Mesh", + "write_data_names": ["K00", "K11", "Porosity"], + "read_data_names": ["Concentration"] + }, + "simulation_params": { + "micro_dt": 0.01, + "macro_domain_bounds": [0.0, 1.0, 0.0, 0.5], + "decomposition": [2, 1], + "adaptivity": false, + "model_adaptivity": true, + "model_adaptivity_settings": { + "switching_function": "switch.py" + } + }, + "diagnostics": { + "data_from_micro_sims": ["grain_size"] + } +} diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json index 3eabcff11..3a527daad 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-snapshot-config.json @@ -1,15 +1,15 @@ { - "micro_file_name": "micro_sim", + "micro_file_names": ["micro_sim"], "coupling_params": { - "parameter_file_name": "input_samples.hdf5", - "read_data_names": ["concentration"], - "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"] + "parameter_file_name": "macro-concentration-samples.hdf5", + "write_data_names": ["K00", "K11", "Porosity"], + "read_data_names": ["Concentration"] }, "simulation_params": { "micro_dt": 0.01 }, "snapshot_params": { + "output_file_name": "micro-dumux-snapshots", "initialize_once": false - }, - "output_directory": "output" + } } diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json deleted file mode 100644 index 198abbffd..000000000 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-surrogate-config.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "micro_file_name": "micro_sim_sur", - "coupling_params": { - "participant_name": "Micro-Manager", - "precice_config_file_name": "../precice-config.xml", - "macro_mesh_name": "macro-mesh", - "write_data_names": ["k_00", "k_01", "k_10", "k_11", "porosity"], - "read_data_names": ["concentration"] - }, - "simulation_params": { - "micro_dt": 0.01, - "macro_domain_bounds": [0.0, 1.0, 0.0, 0.5], - "decomposition": [1, 1], - "adaptivity": "True", - "adaptivity_settings": { - "type": "global", - "data": ["k_00", "k_11", "porosity", "concentration"], - "history_param": 0.1, - "coarsening_constant": 0.2, - "refining_constant": 0.05, - "every_implicit_iteration": "False", - "similarity_measure": "L2rel" - } - }, - "diagnostics": { - "data_from_micro_sims": ["grain_size"] - } -} diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/switch.py b/two-scale-heat-conduction/micro-dumux-surrogate/switch.py new file mode 100644 index 000000000..ef806e9d7 --- /dev/null +++ b/two-scale-heat-conduction/micro-dumux-surrogate/switch.py @@ -0,0 +1,24 @@ +import numpy as np + + +def switching_function(resolution, location, t, input, prev_output): + result = 0 + # Only use the full-order model for the first time step + if t == 0.0: + if resolution > 0: + result = -1 + else: + result = 0 + # after small init phase, we want dynamic model selection + # for test purposes we say ROM is accurate in range + else: + concentration = input['concentration'] + is_valid_range = 0.45 < concentration < 0.55 + is_fom = resolution == 0 + + if is_fom and is_valid_range: + result = 1 + if not is_fom and not is_valid_range: + result = -1 + + return result From 67f602996d2b674a59f117cf3df175511faa684a Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Fri, 7 Aug 2026 17:24:15 +0200 Subject: [PATCH 13/15] Working state in folder micro-dumux-surrogate --- tools/cleaning-tools.sh | 1 + .../micro-manager-model-switching-config.json | 2 +- .../micro-dumux-surrogate/micro_sim_sur.py | 64 ++++++++++++----- .../micro-dumux-surrogate/requirements.txt | 1 + .../micro-dumux-surrogate/run.sh | 6 +- .../surrogate_workflow.py | 72 +++++++++---------- .../{switch.py => switch-model.py} | 0 two-scale-heat-conduction/precice-config.xml | 3 + 8 files changed, 90 insertions(+), 59 deletions(-) rename two-scale-heat-conduction/micro-dumux-surrogate/{switch.py => switch-model.py} (100%) diff --git a/tools/cleaning-tools.sh b/tools/cleaning-tools.sh index eaa4758fd..b8003eee7 100755 --- a/tools/cleaning-tools.sh +++ b/tools/cleaning-tools.sh @@ -199,6 +199,7 @@ clean_dumux() { echo "- Cleaning up DuMuX case in $(pwd)" rm -fv ./*.vtu rm -fv ./*.pvd + rm -fv ./*.hdf5 clean_precice_logs . clean_case_logs . ) diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json index 3403c26ae..e64b35ac0 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json @@ -13,7 +13,7 @@ "adaptivity": false, "model_adaptivity": true, "model_adaptivity_settings": { - "switching_function": "switch.py" + "switching_function": "switch-model.py" } }, "diagnostics": { diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py index 13de7571f..0a6a6d19e 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro_sim_sur.py @@ -1,10 +1,6 @@ """ Micro simulation Surrogate, requrie previous computation of surrogate model """ -import os -import subprocess -from bayesvalidrox import PyLinkForwardModel, Input, PCE, ExpDesigns, Engine -import h5py import joblib import numpy as np import math @@ -14,7 +10,12 @@ class MicroSimulation: def __init__(self, sim_id): """ - Constructor of MicroSimulation class. + Get the micro-scale model from the BayesValidRox surrogate model. + + Parameters + ---------- + sim_id : int + The simulation ID for the micro-scale simulation. """ self._sim_id = sim_id self._state = None @@ -27,30 +28,59 @@ def __init__(self, sim_id): def initialize(self): output_data = dict() - output_data["k_00"] = 0.4912490635619572 - output_data["k_11"] = 0.4912490635989945 - output_data["porosity"] = 0.4933482661391027 + output_data["K00"] = 0.4912490635619572 + output_data["K11"] = 0.4912490635989945 + output_data["Porosity"] = 0.4933482661391027 if self._sim_id == 0: - output_data["k_00"] = 0.4912490640081466 - output_data["k_11"] = 0.4912490640081367 + output_data["K00"] = 0.4912490640081466 + output_data["K11"] = 0.4912490640081367 return output_data def get_state(self): + """ + Get the current state of the micro-scale simulation. + + Returns + ------- + state : dict + The current state of the micro-scale simulation. + """ return self._state def set_state(self, state): + """ + Set the current state of the micro-scale simulation. + + Parameters + ---------- + state : dict + The state to set for the micro-scale simulation. + """ self._state = state def solve(self, macro_data, dt): - model_eval, _ = self._model.eval_metamodel(np.array([macro_data["concentration"]])[:, np.newaxis]) + """ + Solve the micro-scale simulation using the surrogate model. + + Parameters + ---------- + macro_data : dict + The macro-scale data required for the micro-scale simulation. + dt : float + The time step for the micro-scale simulation. + + Returns + ------- + output_data : dict + The output data from the micro-scale simulation. + """ + model_eval, _ = self._model.eval_metamodel(np.array([macro_data["Concentration"]])[:, np.newaxis]) output_data = dict() - output_data["k_00"] = model_eval["k_00"][0][0] - output_data["k_01"] = model_eval["k_01"][0][0] - output_data["k_10"] = model_eval["k_10"][0][0] - output_data["k_11"] = model_eval["k_11"][0][0] - output_data["porosity"] = model_eval["porosity"][0][0] - output_data["grain_size"] = math.sqrt((1 - model_eval["porosity"][0][0]) / math.pi) + output_data["K00"] = model_eval["K00"][0][0] + output_data["K11"] = model_eval["K11"][0][0] + output_data["Porosity"] = model_eval["Porosity"][0][0] + output_data["grain_size"] = math.sqrt((1 - model_eval["Porosity"][0][0]) / math.pi) return output_data diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt b/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt index f167b1a92..8970d34da 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt +++ b/two-scale-heat-conduction/micro-dumux-surrogate/requirements.txt @@ -1,3 +1,4 @@ numpy bayesvalidrox +pyprecice micro-manager-precice diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/run.sh b/two-scale-heat-conduction/micro-dumux-surrogate/run.sh index f28acb260..7736ee581 100755 --- a/two-scale-heat-conduction/micro-dumux-surrogate/run.sh +++ b/two-scale-heat-conduction/micro-dumux-surrogate/run.sh @@ -9,16 +9,16 @@ usage() { echo "Usage: cmd [-s] [-p n]" 1>&2; exit 1; } # Check if no input argument was provided if [ -z "$*" ] ; then echo "No input argument provided. Micro Manager is launched in serial" - micro-manager-precice micro-manager-surrogate-config.json + micro-manager-precice micro-manager-model-switching-config.json fi while getopts ":sp" opt; do case ${opt} in s) - micro-manager-precice micro-manager-surrogate-config.json + micro-manager-precice micro-manager-model-switching-config.json ;; p) - mpiexec -n "$2" micro-manager-precice micro-manager-surrogate-config.json + mpiexec -n "$2" micro-manager-precice micro-manager-model-switching-config.json ;; *) usage diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index 762312535..e3154552f 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -10,49 +10,43 @@ def create_snapshots() -> None: """ Create snapshots of the DuMuX model in micro-dumux/ using the Micro Manager. - The snapshots are saved in the specified directory. """ # What effect do uniformly distributed concentration samples have on the quality of the surrogate model? concentration_samples = np.linspace(0.0, 0.9, 500) - with h5py.File("input_samples.hdf5", "w") as f: - f.create_dataset("concentration", data=concentration_samples) + # The name of the HDF5 file should be the same as the parameter_file_name in micro-manager-snapshot-config.json + with h5py.File("macro-concentration-samples.hdf5", "w") as f: + f.create_dataset("Concentration", data=concentration_samples) # Run the Micro Manager to create snapshot database - subprocess.call('micro-manager-precice --snapshot micro-manager-snapshot-config.json', shell=True) + subprocess.run( + ["micro-manager-precice", "--snapshot", "micro-manager-snapshot-config.json"], + check=True, + ) -def read_snapshots(snapshots_dir: str) -> tuple: +def read_snapshots() -> tuple: """ Read the snapshots created by the Micro Manager and return the inputs and outputs. The inputs are the concentration samples, and the outputs are the porosity and conductivity matrix values. - Parameters - ---------- - snapshots_dir : str - The directory where the snapshots are stored. - Returns ------- tuple A tuple containing the inputs (concentration samples) and outputs (porosity and conductivity values). """ - with h5py.File(os.path.join(snapshots_dir, "snapshot_data.hdf5"), "r") as f: - concentration_data = f["concentration"][:] - porosity_data = f["porosity"][:] - k_00_data = f["k_00"][:] - k_01_data = f["k_01"][:] - k_10_data = f["k_10"][:] - k_11_data = f["k_11"][:] + with h5py.File("micro-dumux-snapshots.hdf5", "r") as f: + concentration_data = f["Concentration"][:] + porosity_data = f["Porosity"][:] + k_00_data = f["K00"][:] + k_11_data = f["K11"][:] concentration = np.swapaxes(np.array([concentration_data]), 0, 1) porosity = np.swapaxes(np.array([porosity_data]), 0, 1) k_00 = np.swapaxes(np.array([k_00_data]), 0, 1) - k_01 = np.swapaxes(np.array([k_01_data]), 0, 1) - k_10 = np.swapaxes(np.array([k_10_data]), 0, 1) k_11 = np.swapaxes(np.array([k_11_data]), 0, 1) - outputs = {"porosity": porosity, "k_00": k_00, "k_01": k_01, "k_10": k_10, "k_11": k_11, "x_values": np.array([0])} + outputs = {"Porosity": porosity, "K00": k_00, "K11": k_11, "x_values": np.array([0])} return concentration, outputs @@ -102,10 +96,14 @@ def split_samples(X, y, n_valid): return X_train, y_train, X_valid, y_valid -def create_surrogate(snapshots_dir: str) -> tuple: +def create_surrogate() -> tuple: """ Create a surrogate model from the Micro Manager snapshots. + Returns + ------- + tuple + A tuple containing the validation samples and corresponding model evaluations. """ # We create a fake model from model.py because we directly provide the # input and outputs from the Micro Manager snapshots. @@ -113,16 +111,16 @@ def create_surrogate(snapshots_dir: str) -> tuple: model.py_file = "model" model.name = "micro-dumux-surrogate" model.link_type = "function" - model.output_names = ["porosity", "k_00", "k_01", "k_10", "k_11"] + model.output_names = ["Porosity", "K00", "K11"] - x, y = read_snapshots(snapshots_dir) + x, y = read_snapshots() # Split the samples into training and validation sets n_valid = 200 x_train, y_train, x_valid, y_valid = split_samples(x, y, n_valid) inputs = Input() - inputs.add_marginals(name="concentration", dist_type="unif", parameters=[0, 0.5]) + inputs.add_marginals(name="Concentration", dist_type="unif", parameters=[0, 0.5]) exp_design = ExpDesigns(inputs) exp_design.x = x_train @@ -144,7 +142,7 @@ def create_surrogate(snapshots_dir: str) -> tuple: return x_valid, y_valid -def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl"): +def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl") -> None: """ Validate the surrogate model using the validation samples and outputs. @@ -166,31 +164,29 @@ def validate_surrogate(x_valid, y_valid, model_name="micro-dumux-surrogate.pkl") # Compare predictions with true values plt.figure() - plt.scatter(y_valid["porosity"], y_metamod["porosity"]) + plt.scatter(y_valid["Porosity"], y_metamod["Porosity"]) plt.xlabel("True Values") plt.ylabel("Predictions") plt.title(f"Validation: porosity") plt.plot([0.4, 1.1], [0.4, 1.1], "k--") plt.xlim(0.4, 1.1) plt.ylim(0.4, 1.1) - plt.show() + plt.savefig("surrogate-validation-porosity.pdf", bbox_inches="tight") plt.figure() - plt.scatter(x_valid, y_valid["porosity"]) - plt.xlabel("valid concentration") - plt.ylabel("valid porosity") - plt.title("IO: con to poro") - plt.show() + plt.scatter(x_valid, y_valid["Porosity"]) + plt.xlabel("Validation Concentration") + plt.ylabel("Validation Porosity") + plt.title("IO: Concentration to Porosity") + plt.savefig("surrogate-validation-concentration-porosity.pdf", bbox_inches="tight") def main(): - snapshots_dir = "output" - if not os.path.exists(snapshots_dir): - os.makedirs(snapshots_dir) - - # os.chdir(snapshots_dir) + """ + Create snaphots of the DuMuX model, create a surrogate model from the snapshots, and validate the surrogate model. + """ create_snapshots() - x_valid, y_valid = create_surrogate(snapshots_dir) + x_valid, y_valid = create_surrogate() validate_surrogate(x_valid, y_valid) diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/switch.py b/two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py similarity index 100% rename from two-scale-heat-conduction/micro-dumux-surrogate/switch.py rename to two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py diff --git a/two-scale-heat-conduction/precice-config.xml b/two-scale-heat-conduction/precice-config.xml index 028d0bf31..8e85953c2 100644 --- a/two-scale-heat-conduction/precice-config.xml +++ b/two-scale-heat-conduction/precice-config.xml @@ -16,6 +16,7 @@ + @@ -25,6 +26,7 @@ + @@ -44,6 +46,7 @@ + From 60f3ad2b2540cb521da3ee2ac2bd5225b8fbc2b1 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Sat, 8 Aug 2026 12:26:32 +0200 Subject: [PATCH 14/15] Changing switching function file name --- .../micro-manager-model-switching-config.json | 2 +- .../micro-dumux-surrogate/switch-model.py | 50 ++++++++++++++----- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json index e64b35ac0..860327c02 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json +++ b/two-scale-heat-conduction/micro-dumux-surrogate/micro-manager-model-switching-config.json @@ -13,7 +13,7 @@ "adaptivity": false, "model_adaptivity": true, "model_adaptivity_settings": { - "switching_function": "switch-model.py" + "switching_function": "switch-model" } }, "diagnostics": { diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py b/two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py index ef806e9d7..eb2d99b0d 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/switch-model.py @@ -1,24 +1,48 @@ import numpy as np +from typing import Dict -def switching_function(resolution, location, t, input, prev_output): - result = 0 +def switching_function(model_res: int, location: np.ndarray, t: float, input: Dict, prev_output: Dict): + """ + Determine which model to use based on the resolution, location, time, input, and previous output. + + Parameters + ---------- + model_res : int + The resolution level of the model (0 for full-order model, >0 for reduced-order model). + location : np.ndarray + The macro-scale coordinates corresponding to the micro-scale simulation. + t : float + The current time step. + input : Dict + The input data for the model, including concentration. + prev_output : dict + The output from the previous time step. + + Returns + ------- + model_res_offset : int + The offset to change the model used from the provided hierarchy of models. + """ + model_res_offset = 0 + # Only use the full-order model for the first time step if t == 0.0: - if resolution > 0: - result = -1 + if model_res > 0: + model_res_offset = -1 else: - result = 0 - # after small init phase, we want dynamic model selection - # for test purposes we say ROM is accurate in range + model_res_offset = 0 + + # After the first time step, the model is selected dynamically based on + # the concentration value and the model resolution. else: - concentration = input['concentration'] - is_valid_range = 0.45 < concentration < 0.55 - is_fom = resolution == 0 + concentration = input["Concentration"] + is_valid_range = 0.2 < concentration < 0.3 + is_fom = model_res == 0 if is_fom and is_valid_range: - result = 1 + model_res_offset = 1 if not is_fom and not is_valid_range: - result = -1 + model_res_offset = -1 - return result + return model_res_offset From 094390179433c8544982ddc1352bc95d366491a6 Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Sat, 8 Aug 2026 17:39:55 +0200 Subject: [PATCH 15/15] Compute snapshots in parallel --- .../micro-dumux-surrogate/surrogate_workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py index e3154552f..69581a747 100644 --- a/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py +++ b/two-scale-heat-conduction/micro-dumux-surrogate/surrogate_workflow.py @@ -20,7 +20,7 @@ def create_snapshots() -> None: # Run the Micro Manager to create snapshot database subprocess.run( - ["micro-manager-precice", "--snapshot", "micro-manager-snapshot-config.json"], + ["mpiexec", "-n", "2", "micro-manager-precice", "--snapshot", "micro-manager-snapshot-config.json"], check=True, )