From 03618f18e1319d5645a2dd5a7861562b5c84d13e Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Mon, 13 Jan 2025 22:19:27 +0100 Subject: [PATCH 01/29] fix minor issues to make sure all tutorials work as expected --- grainlearning/rnn/predict.py | 4 +- grainlearning/rnn/train.py | 2 +- grainlearning/sampling.py | 4 +- .../PeriSp_1000_0.68.txt | 0 .../triax_YADE_DEM_model.py | 6 +-- .../triax_calibration.py | 2 +- .../triax_calibration_load_and_run.py | 48 +++++++++++++++++++ .../triax_data_DEM.dat | 1 - .../norsand_triax_calibration.py | 2 +- 9 files changed, 58 insertions(+), 11 deletions(-) rename tutorials/physics_based/{triaxial_compression => DEM_triaxial_compression}/PeriSp_1000_0.68.txt (100%) rename tutorials/physics_based/{triaxial_compression => DEM_triaxial_compression}/triax_YADE_DEM_model.py (97%) rename tutorials/physics_based/{triaxial_compression => DEM_triaxial_compression}/triax_calibration.py (96%) create mode 100644 tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py rename tutorials/physics_based/{triaxial_compression => DEM_triaxial_compression}/triax_data_DEM.dat (98%) rename tutorials/physics_based/{nosand_triax => NorSand_triax_compression}/norsand_triax_calibration.py (99%) diff --git a/grainlearning/rnn/predict.py b/grainlearning/rnn/predict.py index a8db184d..c165e711 100644 --- a/grainlearning/rnn/predict.py +++ b/grainlearning/rnn/predict.py @@ -150,9 +150,9 @@ def load_model(path_to_model: Path, train_stats: dict, config: dict): elif os.path.exists(path_to_model / 'saved_model.pb'): # Model has been saved directly using tf.keras model = tf.keras.models.load_model(path_to_model) - elif os.path.exists(path_to_model / 'weights.h5'): # Model's weights have been saved directly using tf.keras + elif os.path.exists(path_to_model / 'model.weights.h5'): # Model's weights have been saved directly using tf.keras model = rnn_model(train_stats, **config) - model.load_weights(path_to_model / 'weights.h5') + model.load_weights(path_to_model / 'model.weights.h5') else: raise FileNotFoundError("Could not find a model to load") return model diff --git a/grainlearning/rnn/train.py b/grainlearning/rnn/train.py index bbe57ca2..b3f54102 100644 --- a/grainlearning/rnn/train.py +++ b/grainlearning/rnn/train.py @@ -138,7 +138,7 @@ def train_without_wandb(preprocessor: Preprocessor, config=None, model: tf.keras split_data[split] = split_data[split].batch(config['batch_size']) # set up training - if config['save_weights_only']: path_save_data = path_save_data / "weights.h5" + if config['save_weights_only']: path_save_data = path_save_data / "model.weights.h5" early_stopping = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=config['patience'], diff --git a/grainlearning/sampling.py b/grainlearning/sampling.py index 184d7d5d..685dc8ad 100644 --- a/grainlearning/sampling.py +++ b/grainlearning/sampling.py @@ -204,7 +204,7 @@ def normalize(self, params: np.ndarray): :return: Normalized parameters """ return (params - self.min_params) / (self.max_params - self.min_params) - + def train(self, weight: np.ndarray, system: Type["DynamicSystem"]): """Train the Gaussian mixture model. @@ -237,7 +237,7 @@ def correct_scores(self, covs_ref: float, system: Type["DynamicSystem"], tol: fl """Correct the covariance (preceision) matrices and the scores of the Gaussian Mixture Model. :param covs_ref: Reference covariance from the current ensemble - :param samples: Samples to estimat the covariance + :param system: Dynamic system class that contains the parameter samples :param tol: Tolerance threshold on the difference between the sample covariance and the covariance estimated by GMM , defaults to 0.1, optional """ # raise an error is the GMM is not initialized or not converged diff --git a/tutorials/physics_based/triaxial_compression/PeriSp_1000_0.68.txt b/tutorials/physics_based/DEM_triaxial_compression/PeriSp_1000_0.68.txt similarity index 100% rename from tutorials/physics_based/triaxial_compression/PeriSp_1000_0.68.txt rename to tutorials/physics_based/DEM_triaxial_compression/PeriSp_1000_0.68.txt diff --git a/tutorials/physics_based/triaxial_compression/triax_YADE_DEM_model.py b/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py similarity index 97% rename from tutorials/physics_based/triaxial_compression/triax_YADE_DEM_model.py rename to tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py index 36d6582b..f5f2121b 100755 --- a/tutorials/physics_based/triaxial_compression/triax_YADE_DEM_model.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py @@ -40,9 +40,9 @@ conf = table.conf # confining pressure rate = 0.1 # strain rate (decrease this for serious calculations) damp = 0.2 # damping coefficient -stabilityRatio = 1.e-3 # threshold for quasi-static condition (decrease this for serious calculations) -stressTolRatio = 1.e-3 # tolerance for stress goal -initStabilityRatio = 1.e-3 # initial stability threshold +stabilityRatio = 1.e-2 # threshold for quasi-static condition (decrease this for serious calculations) +stressTolRatio = 1.e-2 # tolerance for stress goal +initStabilityRatio = 1.e-2 # initial stability threshold obsCtrl = 'e_z' # key for simulation control lowDamp = 0.2 # damping coefficient highDamp = 0.9 diff --git a/tutorials/physics_based/triaxial_compression/triax_calibration.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py similarity index 96% rename from tutorials/physics_based/triaxial_compression/triax_calibration.py rename to tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py index 9ffaaa5d..9a8ecc46 100644 --- a/tutorials/physics_based/triaxial_compression/triax_calibration.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py @@ -40,7 +40,7 @@ def run_sim(calib): "sigma_tol": 0.01, }, "calibration": { - "inference": {"ess_target": 0.3}, + "inference": {"ess_target": 0.3, "scale_cov_with_max": True}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py new file mode 100644 index 00000000..c2c00fee --- /dev/null +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py @@ -0,0 +1,48 @@ +""" + This tutorial shows how to perform iterative Bayesian calibration for a DEM simulation of two particle colliding + using GrainLearning. The simulation is performed using Yade on a desktop computer. +""" +import os +from math import floor, log +from grainlearning import BayesianCalibration +from grainlearning.dynamic_systems import IODynamicSystem + +PATH = os.path.abspath(os.path.dirname(__file__)) +curr_iter = 0 + + +calibration = BayesianCalibration.from_dict( + { + "curr_iter": curr_iter, + "num_iter": 0, + "system": { + "system_type": IODynamicSystem, + "param_min": [7, 0.0, 0.0, 0.0, 10.0], + "param_max": [11, 0.5, 1.0, 1.0, 50.0], + "param_names": ['E_m', 'v', 'kr', 'eta', 'mu'], + "num_samples": 15, + "obs_data_file": PATH + '/triax_data_DEM.dat', + "obs_names": ['e_v', 's33_over_s11'], + "ctrl_name": 'e_z', + "sim_name": 'triax', + "sim_data_dir": PATH + '/sim_data_backup_2025_01_13_20_58_49/', + "sim_data_file_ext": '.txt', + "sigma_tol": 0.01, + }, + "calibration": { + "inference": {"ess_target": 0.3, "scale_cov_with_max": True}, + "sampling": { + "max_num_components": 2, + "n_init": 1, + "random_state": 0, + "covariance_type": "full", + } + }, + "save_fig": 0, + } +) + +calibration.load_and_run_one_iteration() + +most_prob_params = calibration.get_most_prob_params() +print(f'Most probable parameter values: {most_prob_params}') diff --git a/tutorials/physics_based/triaxial_compression/triax_data_DEM.dat b/tutorials/physics_based/DEM_triaxial_compression/triax_data_DEM.dat similarity index 98% rename from tutorials/physics_based/triaxial_compression/triax_data_DEM.dat rename to tutorials/physics_based/DEM_triaxial_compression/triax_data_DEM.dat index 20baf63c..732fbef0 100755 --- a/tutorials/physics_based/triaxial_compression/triax_data_DEM.dat +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_data_DEM.dat @@ -1,5 +1,4 @@ # dt e_v e_x e_y e_z numIter realTime s33_over_s11 -0.000129182013495409 -6.387602852817072e-06 -3.1592026477987963e-06 -3.2284002050182757e-06 -0.0 717 2.588 0.9999979940877495 0.00012910045422267902 0.18940273348010736 -0.10814845542188825 -0.10244881109800316 0.3999999999999988 1134 3.224 1.7851818339269934 0.00013420856274592327 0.3355159096100714 -0.23533883755236604 -0.22914525283755974 0.7999999999999973 1593 3.911 2.3806411977264528 0.00013603320437014183 0.4175918136208415 -0.3903900145390303 -0.39201817184013044 1.2000000000000022 2022 4.544 2.813239805004959 diff --git a/tutorials/physics_based/nosand_triax/norsand_triax_calibration.py b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py similarity index 99% rename from tutorials/physics_based/nosand_triax/norsand_triax_calibration.py rename to tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py index f4d8ac2c..8381526e 100644 --- a/tutorials/physics_based/nosand_triax/norsand_triax_calibration.py +++ b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py @@ -159,7 +159,7 @@ def run_sim(calib): "random_state": 0, "slice_sampling": False, }, - "initial_sampling": "sobol", + "initial_sampling": "LH", }, "save_fig": 0, } From 4d522b27f87a7c84113d3502917a958ff500db52 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 22 Jan 2025 17:11:32 +0100 Subject: [PATCH 02/29] add the error measures to allow more flexible early stopping --- grainlearning/bayesian_calibration.py | 97 +++++++++++++++++-- grainlearning/dynamic_systems.py | 16 +-- grainlearning/iterative_bayesian_filter.py | 8 +- tests/integration/test_lenreg.py | 61 +++++++++++- .../2D_dike_model_inference.py | 3 +- .../norsand_triax_calibration.py | 2 +- .../python_linear_regression_solve.py | 2 + 7 files changed, 162 insertions(+), 27 deletions(-) diff --git a/grainlearning/bayesian_calibration.py b/grainlearning/bayesian_calibration.py index 39c6a36d..fd3cca2a 100644 --- a/grainlearning/bayesian_calibration.py +++ b/grainlearning/bayesian_calibration.py @@ -76,6 +76,8 @@ class BayesianCalibration: :param calibration: An iterative Bayesian Filter that iteratively sample the parameter space :param num_iter: Number of iteration steps :param curr_iter: Current iteration step + :param error_tol: Tolerance to check the sample with the smallest mean absolute percentage error + :param gl_error_tol: Tolerance to check the GrainLearning ensemble percentage error :param save_fig: Flag for skipping (-1), showing (0), or saving (1) the figures :param callback: A callback function that runs the external software and passes the parameter sample to generate outputs """ @@ -85,6 +87,8 @@ def __init__( calibration: Type["IterativeBayesianFilter"], num_iter: int = 1, curr_iter: int = 0, + error_tol: float = None, + gl_error_tol: float = None, save_fig: int = -1, callback: Callable = None, ): @@ -100,10 +104,18 @@ def __init__( self.curr_iter = curr_iter + self.error_tol = error_tol + + self.gl_error_tol = gl_error_tol + self.calibration = calibration self.callback = callback + self.error_arrays = None + + self.gl_errors = [] + def run(self): """ This is the main calibration loop which does the following steps 1. First iteration of Bayesian calibration starts with a Halton sequence @@ -112,19 +124,24 @@ def run(self): # Move existing simulation data to the backup folder self.system.backup_sim_data() - print(f"Bayesian calibration iter No. {self.curr_iter}") - # First iteration - self.run_one_iteration() - # Bayesian calibration continue until curr_iter = num_iter or sigma_max < tolerance - for _ in range(self.num_iter - 1): - self.increase_curr_iter() + for _ in range(self.num_iter): print(f"Bayesian calibration iter No. {self.curr_iter}") - self.run_one_iteration() - if abs(self.system.sigma_max - self.system.sigma_tol) / self.system.sigma_tol < 1e-2: - self.num_iter = self.curr_iter + 1 + stopping_criteria_met = self.run_one_iteration() + if stopping_criteria_met: break + # Print the errors after all iterations are done + if not stopping_criteria_met: + error_most_probable = min(self.error_arrays) + gl_error = self.gl_errors[-1] + print(f"\n" + f"Stopping criteria met: \n" + f"sigma = {self.system.sigma_max},\n" + f"Smallest mean absolute percentage error = {error_most_probable: .3e},\n" + f"GrainLearning ensemble percentage error = {gl_error: .3e}\n\n" + f"Ending Bayesian calibration.") + def run_one_iteration(self, index: int = -1): """Run Bayesian calibration for one iteration. @@ -150,6 +167,31 @@ def run_one_iteration(self, index: int = -1): # Generate some plots self.plot_uq_in_time() + self.compute_errors() + + # Defining stopping criterion + error_most_probable = min(self.error_arrays) + gl_error = self.gl_errors[-1] + + # If any stopping condition is met + # Check stopping criteria + normalized_sigma_met = self.system.sigma_max < self.system.sigma_tol + most_probable_error_met = error_most_probable < self.error_tol if self.error_tol is not None else False + ensemble_error_met = gl_error < self.gl_error_tol if self.gl_error_tol is not None else False + + if normalized_sigma_met or most_probable_error_met or ensemble_error_met: + print(f"\n" + f"Stopping criteria met: \n" + f"sigma = {self.system.sigma_max},\n" + f"Smallest mean absolute percentage error = {error_most_probable: .3e},\n" + f"GrainLearning ensemble percentage error = {gl_error: .3e}\n\n" + f"Ending Bayesian calibration.") + self.num_iter = self.curr_iter + 1 + return True + else: + self.increase_curr_iter() + return False + def run_callback(self): """ Run the callback function @@ -282,6 +324,13 @@ def plot_uq_in_time(self): close_plots(self.save_fig) + def get_most_prob_params_id(self): + """Return the most probable set of parameters + + :return: Estimated parameter values + """ + return argmax(self.calibration.posterior) + def get_most_prob_params(self): """Return the most probable set of parameters @@ -296,6 +345,34 @@ def increase_curr_iter(self): self.system.curr_iter += 1 self.curr_iter += 1 + + def compute_errors(self): + """Compute the mean absolute percentage error per sample and the ensemble error + """ + from sklearn.metrics import mean_absolute_error + import numpy as np + + # compute mean absolute percentage error + sim_data = self.system.sim_data + num_samples = self.system.num_samples + + # compute GrainLearning errors + self.error_arrays = np.zeros(num_samples) + # loop over all samples to compute sample percentage errors + # if observation data is a time series + if self.system.num_steps == 1: + obs_maxs = np.max(self.system.obs_data) + obs_mins = np.min(self.system.obs_data) + else: + obs_maxs = np.max(self.system.obs_data, axis=1) + obs_mins = np.min(self.system.obs_data, axis=1) + obs_range = obs_maxs - obs_mins + for i in range(num_samples): + self.error_arrays[i] = mean_absolute_error(self.system.obs_data.T/obs_range, sim_data[i, :, :].T/obs_range) + + # compute the ensemble error + self.gl_errors.append(np.dot(self.error_arrays, self.calibration.posterior)) + @classmethod def from_dict( cls: Type["BayesianCalibration"], @@ -324,6 +401,8 @@ def from_dict( calibration=calibration, num_iter=obj["num_iter"], curr_iter=obj.get("curr_iter", 0), + error_tol=obj.get("error_tol", None), + gl_error_tol=obj.get("gl_error_tol", None), save_fig=obj.get("save_fig", -1), callback=obj.get("callback", None) ) diff --git a/grainlearning/dynamic_systems.py b/grainlearning/dynamic_systems.py index d8714e75..4eb25f99 100644 --- a/grainlearning/dynamic_systems.py +++ b/grainlearning/dynamic_systems.py @@ -100,7 +100,7 @@ class DynamicSystem: :param sigma_max: Maximum uncertainty, defaults to 1.0e6, optional :param sigma_tol: Tolerance of the estimated uncertainty, defaults to 1.0e-3, optional :param sim_name: Name of the simulation, defaults to 'sim', optional - :param sigma_min: Minimum uncertainty, defaults to 1.0e-6, optional + :param sigma_lower_bound: Minimum uncertainty, defaults to 1.0e-6, optional :param _inv_normalized_sigma: Calculated normalized sigma to weigh the covariance matrix :param estimated_params: Estimated parameter as the first moment of the distribution (:math:`x_\mu = \sum_i w_i * x_i`), defaults to None, optional :param estimated_params_cv: Estimated parameter coefficient of variation as the second moment of the distribution (:math:`x_\sigma = \sqrt{\sum_i w_i * (x_i - x_\mu)^2} / x_\mu`), defaults to None, optional @@ -122,7 +122,7 @@ def __init__( param_names: List[str] = None, sigma_max: float = 1.0e6, sigma_tol: float = 1.0e-3, - sigma_min: float = 1.0e-6 + sigma_lower_bound: float = 1.0e-6 ): """Initialize the dynamic system class""" #### Observations #### @@ -176,7 +176,7 @@ def __init__( #### Uncertainty #### - self.sigma_min = sigma_min + self.sigma_lower_bound = sigma_lower_bound self.sigma_max = sigma_max @@ -216,9 +216,9 @@ def from_dict(cls: Type["DynamicSystem"], obj: dict): sim_data=obj.get("sim_data", None), param_data=obj.get("param_data", None), param_names=obj.get("param_names", None), - sigma_tol=obj.get("sigma_tol", 0.001), + sigma_tol=obj.get("sigma_tol", 1.0e-3), sigma_max=obj.get("sigma_max", 1.0e6), - sigma_min=obj.get("sigma_min", 1.0e-6), + sigma_lower_bound=obj.get("sigma_lower_bound", 1.0e-6), ) def set_sim_data(self, data: list): @@ -401,7 +401,7 @@ def __init__( param_names: List[str] = None, sigma_max=1.0e6, sigma_tol=1.0e-3, - sigma_min=1.0e-6 + sigma_lower_bound=1.0e-6 ): """Initialize the IO dynamic system class""" @@ -423,7 +423,7 @@ def __init__( param_names, sigma_max, sigma_tol, - sigma_min + sigma_lower_bound ) # TODO: reuse initialization from base class @@ -495,7 +495,7 @@ def from_dict(cls: Type["IODynamicSystem"], obj: dict): param_names=obj.get("param_names", None), sigma_tol=obj.get("sigma_tol", 0.001), sigma_max=obj.get("sigma_max", 1.0e6), - sigma_min=obj.get("sigma_min", 1.0e-6), + sigma_lower_bound=obj.get("sigma_lower_bound", 1.0e-6), ) def get_obs_data(self): diff --git a/grainlearning/iterative_bayesian_filter.py b/grainlearning/iterative_bayesian_filter.py index 43b984f3..1f2d98ed 100644 --- a/grainlearning/iterative_bayesian_filter.py +++ b/grainlearning/iterative_bayesian_filter.py @@ -145,7 +145,7 @@ def run_inference(self, system: Type["DynamicSystem"]): args=(system, self.proposal), method="bounded", # tol=self.ess_tol, - bounds=(system.sigma_min, system.sigma_max), + bounds=(system.sigma_lower_bound, system.sigma_max), ) system.sigma_max = result.x @@ -204,15 +204,15 @@ def load_proposal_from_file(self, system: Type["IODynamicSystem"]): self.sampling.load_gmm_from_file(f'{system.sim_data_dir}/iter{system.curr_iter-1}/{self.proposal_data_file}') samples = np.copy(system.param_data) - + # normalize the parameter samples samples_normalized = self.sampling.normalize(samples) self.proposal = np.exp(self.sampling.gmm.score_samples(samples_normalized)) - + self.proposal /= self.proposal.sum() - ## turn off the proposal density to probability mass correction because quai-random sampling is used by default + ## turn off the proposal density to probability mass correction because quasi-random sampling is used by default # proposal *= voronoi_vols(samples) # # assign the maximum vol to open regions (use a uniform proposal distribution if Voronoi fails) diff --git a/tests/integration/test_lenreg.py b/tests/integration/test_lenreg.py index 655df6ed..68fd248b 100644 --- a/tests/integration/test_lenreg.py +++ b/tests/integration/test_lenreg.py @@ -62,9 +62,6 @@ def test_lenreg(): # %% most_prob_params = calibration.system.param_data[most_prob] - print(f'Most probable parameter values: {most_prob_params}') - # %% - # tests error_tolerance = 0.01 @@ -78,6 +75,64 @@ def test_lenreg(): # 2. Checking sigma assert calibration.calibration.sigma_list[-1] < error_tolerance, "Final sigma is bigger than tolerance." + # %% Test other stopping criteria + calibration = BayesianCalibration.from_dict( + { + "num_iter": 10, + "error_tol": 0.01, + "callback": run_sim, + "system": { + "param_min": [0.1, 0.1], + "param_max": [1, 10], + "param_names": ['a', 'b'], + "num_samples": 20, + "obs_data": y_obs, + "ctrl_data": x_obs, + "sim_name": 'linear', + }, + "calibration": { + "inference": {"ess_target": 0.3}, + "sampling": { + "max_num_components": 1, + "n_init": 1, + "covariance_type": "full", + "random_state": 0, + } + } + } + ) + calibration.run() + assert np.min(calibration.error_array) < 0.01, "Error tolerance is not met." + + + # %% Test other stopping criteria + calibration = BayesianCalibration.from_dict( + { + "num_iter": 10, + "gl_error_tol": 0.01, + "callback": run_sim, + "system": { + "param_min": [0.1, 0.1], + "param_max": [1, 10], + "param_names": ['a', 'b'], + "num_samples": 20, + "obs_data": y_obs, + "ctrl_data": x_obs, + "sim_name": 'linear', + }, + "calibration": { + "inference": {"ess_target": 0.3}, + "sampling": { + "max_num_components": 1, + "n_init": 1, + "covariance_type": "full", + "random_state": 0, + } + } + } + ) + calibration.run() + assert calibration.gl_errors[-1] < 0.01, "Error tolerance is not met." # %% test_lenreg() diff --git a/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py b/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py index 4e41cc0b..3d451fad 100644 --- a/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py +++ b/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py @@ -133,8 +133,7 @@ def set_normalization_factor(self): "sim_name": sim_name, "sim_data_dir": PATH + '/sim_data/', "sim_data_file_ext": '.txt', - "sigma_tol": 0.1, - "sigma_min": 0.1, + "sigma_tol": 0.01, "sigma_max": 10, }, "calibration": { diff --git a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py index 8381526e..2fbf802d 100644 --- a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py +++ b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py @@ -134,7 +134,7 @@ def run_sim(calib): calibration = BayesianCalibration.from_dict( { - "num_iter": 8, + "num_iter": 10, "callback": run_sim, "system": { "param_min": [0.7, 0.01, 1.2, 0.2, 25, 2, 100, 0.1], diff --git a/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py b/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py index 7760d71b..ddbd010f 100644 --- a/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py +++ b/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py @@ -28,6 +28,8 @@ def linear(x, params): calibration = BayesianCalibration.from_dict( { "num_iter": 10, +# "error_tol": 0.01, +# "gl_error_tol": 0.01, "callback": run_sim, "system": { "param_min": [0.001, 0.001], From 6b3f5146e1f8873a422a41a13f484cb038f377a9 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Sat, 25 Jan 2025 08:02:53 +0100 Subject: [PATCH 03/29] change some names and surpress tolerance error from root_scalar --- docs/source/bayesian_filtering.rst | 2 +- docs/source/examples.rst | 4 +- docs/source/tutorials.rst | 20 ++--- grainlearning/bayesian_calibration.py | 80 +++++++++---------- grainlearning/iterative_bayesian_filter.py | 22 ++--- grainlearning/sampling.py | 16 +++- tests/integration/test_gmm.py | 8 +- tests/integration/test_lenreg.py | 16 ++-- tests/integration/test_lenreg_IO.py | 4 +- tests/integration/test_smc.py | 8 +- tests/integration/test_smc_mse.py | 6 +- tests/unit/test_iterative_bayesian_filter.py | 20 ++--- .../LSTM/hyperbola_calibration_lstm.py | 4 +- .../LSTM/hyperbola_calibration_mixed.py | 4 +- ...hyperbola_calibration_mixed_hypertuning.py | 4 +- .../LSTM/norsand_triax_calibration_mixed.py | 8 +- .../2D_dike_model_inference.py | 12 +-- .../triax_calibration.py | 4 +- .../triax_calibration_load_and_run.py | 4 +- .../norsand_triax_calibration.py | 11 ++- .../oedo_load_and_resample.py | 4 +- .../collision_calibration.py | 4 +- .../linear_reg_one_iteration.py | 8 +- .../linear_regression_load_all.py | 4 +- .../linear_regression_solve.py | 4 +- .../python_linear_regression_solve.py | 4 +- .../python_hyperbola_regression_solve.py | 4 +- 27 files changed, 149 insertions(+), 140 deletions(-) diff --git a/docs/source/bayesian_filtering.rst b/docs/source/bayesian_filtering.rst index 773a4abc..7c4bcbf8 100644 --- a/docs/source/bayesian_filtering.rst +++ b/docs/source/bayesian_filtering.rst @@ -178,7 +178,7 @@ You can choose one of the sampling methods when initializing a :class:`.Iterativ ibf_cls = IterativeBayesianFilter.from_dict( { - "inference":{ + "Bayes_filter":{ "ess_target": 0.3, }, "sampling":{ diff --git a/docs/source/examples.rst b/docs/source/examples.rst index e5d41d51..b86b28c0 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -57,8 +57,8 @@ Below is a piece of code that performs Bayesian calibration of four DEM paramete "param_max": [200e9, 0.5, 1e4, 0.5], "inv_obs_weight": [1, 1, 0.01], }, - "calibration": { - "inference": {"ess_target": 0.2}, + "inference": { + "Bayes_filter": {"ess_target": 0.2}, "sampling": { "max_num_components": 10, "prior_weight": 0.01, diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 21d3471f..91cfd7e5 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -73,8 +73,8 @@ Check out the documentation of :class:`.BayesianCalibration` for more details. "obs_data": y_obs, "ctrl_data": x_obs, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, } @@ -169,8 +169,8 @@ Now let us define the calibration tool. Note that the system type is changed :cl "sim_data_dir": './sim_data/', "sim_data_file_ext": '.txt', }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "random_state": 0, @@ -249,8 +249,8 @@ Open a Python console in the same directory where you executed the previous tuto "sim_data_file_ext": sim_data_file_ext, "param_names": ['a', 'b'], }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, }, @@ -305,8 +305,8 @@ and then create a new `calibration` object using :class:`.DynamicSystem`. "sim_name": 'linear', "sim_data": sim_data, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, }, @@ -333,8 +333,8 @@ This can be done by setting :attr:`.GaussianMixtureModel.random_state` to a cons .. code-block:: python # create a calibration tool - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "random_state": 0, diff --git a/grainlearning/bayesian_calibration.py b/grainlearning/bayesian_calibration.py index fd3cca2a..33f3d4cd 100644 --- a/grainlearning/bayesian_calibration.py +++ b/grainlearning/bayesian_calibration.py @@ -50,8 +50,8 @@ class BayesianCalibration: "obs_data": [2,4,8,16], "ctrl_data": [1,2,3,4], }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": {"max_num_components": 1}, }, "save_fig": -1, @@ -68,12 +68,12 @@ class BayesianCalibration: bayesian_calibration = BayesianCalibration( num_iter = 8, system = DynamicSystem(...), - calibration = IterativeBayesianFilter(...) + inference = IterativeBayesianFilter(...) save_fig = -1 ) :param system: A `dynamic system `_ whose observables and hidden states evolve dynamically over "time" - :param calibration: An iterative Bayesian Filter that iteratively sample the parameter space + :param inference: An inference method to determine unknown parameters in state-parameter space. Currently, only the iterative Bayesian Filter is available. :param num_iter: Number of iteration steps :param curr_iter: Current iteration step :param error_tol: Tolerance to check the sample with the smallest mean absolute percentage error @@ -84,7 +84,7 @@ class BayesianCalibration: def __init__( self, system: Type["DynamicSystem"], - calibration: Type["IterativeBayesianFilter"], + inference: Type["IterativeBayesianFilter"], num_iter: int = 1, curr_iter: int = 0, error_tol: float = None, @@ -108,11 +108,11 @@ def __init__( self.gl_error_tol = gl_error_tol - self.calibration = calibration + self.inference = inference self.callback = callback - self.error_arrays = None + self.error_array = None self.gl_errors = [] @@ -133,10 +133,10 @@ def run(self): # Print the errors after all iterations are done if not stopping_criteria_met: - error_most_probable = min(self.error_arrays) + error_most_probable = min(self.error_array) gl_error = self.gl_errors[-1] print(f"\n" - f"Stopping criteria met: \n" + f"Stopping criteria NOT met: \n" f"sigma = {self.system.sigma_max},\n" f"Smallest mean absolute percentage error = {error_most_probable: .3e},\n" f"GrainLearning ensemble percentage error = {gl_error: .3e}\n\n" @@ -149,9 +149,9 @@ def run_one_iteration(self, index: int = -1): """ # Initialize the samples if it is the first iteration if self.curr_iter == 0: - self.calibration.initialize(self.system) + self.inference.initialize(self.system) # Fetch the parameter values from a stored list - self.system.param_data = self.calibration.param_data_list[index] + self.system.param_data = self.inference.param_data_list[index] self.system.num_samples = self.system.param_data.shape[0] # Run the model realizations @@ -161,8 +161,8 @@ def run_one_iteration(self, index: int = -1): self.load_system() # Estimate model parameters as a distribution - self.calibration.solve(self.system) - self.calibration.sigma_list.append(self.system.sigma_max) + self.inference.solve(self.system) + self.inference.sigma_list.append(self.system.sigma_max) # Generate some plots self.plot_uq_in_time() @@ -170,7 +170,7 @@ def run_one_iteration(self, index: int = -1): self.compute_errors() # Defining stopping criterion - error_most_probable = min(self.error_arrays) + error_most_probable = min(self.error_array) gl_error = self.gl_errors[-1] # If any stopping condition is met @@ -224,10 +224,10 @@ def load_and_run_one_iteration(self): unlike being assumed as an input for `load_and_process(...)` """ self.load_system() - self.calibration.add_curr_param_data_to_list(self.system.param_data) - self.calibration.solve(self.system) + self.inference.add_curr_param_data_to_list(self.system.param_data) + self.inference.solve(self.system) self.system.write_params_to_table() - self.calibration.sigma_list.append(self.system.sigma_max) + self.inference.sigma_list.append(self.system.sigma_max) self.plot_uq_in_time() def load_and_process(self, sigma: float = 0.1): @@ -236,23 +236,23 @@ def load_and_process(self, sigma: float = 0.1): :param sigma: assumed uncertainty coefficient, defaults to 0.1 """ self.load_system() - self.calibration.add_curr_param_data_to_list(self.system.param_data) - self.calibration.load_proposal_from_file(self.system) - self.calibration.inference.data_assimilation_loop(sigma, self.system) - self.system.compute_estimated_params(self.calibration.inference.posteriors) + self.inference.add_curr_param_data_to_list(self.system.param_data) + self.inference.load_proposal_from_file(self.system) + self.inference.Bayes_filter.data_assimilation_loop(sigma, self.system) + self.system.compute_estimated_params(self.inference.Bayes_filter.posteriors) def load_all(self): """Simply load all previous iterations of Bayesian calibration """ self.load_system() - self.calibration.add_curr_param_data_to_list(self.system.param_data) + self.inference.add_curr_param_data_to_list(self.system.param_data) self.increase_curr_iter() while self.curr_iter < self.num_iter: print(f"Bayesian calibration iter No. {self.curr_iter}") self.load_system() - self.calibration.add_curr_param_data_to_list(self.system.param_data) - self.calibration.run_inference(self.system) - self.calibration.sigma_list.append(self.system.sigma_max) + self.inference.add_curr_param_data_to_list(self.system.param_data) + self.inference.run_inference(self.system) + self.inference.sigma_list.append(self.system.sigma_max) self.plot_uq_in_time() self.increase_curr_iter() @@ -262,9 +262,9 @@ def resample(self): :return: Combinations of resampled parameter values """ - self.calibration.posterior = self.calibration.inference.get_posterior_at_time() - self.calibration.run_sampling(self.system, ) - resampled_param_data = self.calibration.param_data_list[-1] + self.inference.posterior = self.inference.Bayes_filter.get_posterior_at_time() + self.inference.run_sampling(self.system, ) + resampled_param_data = self.inference.param_data_list[-1] self.system.write_params_to_table() return resampled_param_data @@ -293,14 +293,14 @@ def plot_uq_in_time(self): fig_name, self.system.param_names, self.system.param_data, - self.calibration.inference.posteriors, + self.inference.Bayes_filter.posteriors, self.save_fig ) plot_param_data( fig_name, self.system.param_names, - self.calibration.param_data_list, + self.inference.param_data_list, self.save_fig ) @@ -311,14 +311,14 @@ def plot_uq_in_time(self): self.system.ctrl_data, self.system.obs_data, self.system.sim_data, - self.calibration.inference.posteriors, + self.inference.Bayes_filter.posteriors, self.save_fig ) plot_pdf( fig_name, self.system.param_names, - self.calibration.param_data_list, + self.inference.param_data_list, self.save_fig, ) @@ -329,14 +329,14 @@ def get_most_prob_params_id(self): :return: Estimated parameter values """ - return argmax(self.calibration.posterior) + return argmax(self.inference.posterior) def get_most_prob_params(self): """Return the most probable set of parameters :return: Estimated parameter values """ - most_prob = argmax(self.calibration.posterior) + most_prob = argmax(self.inference.posterior) return self.system.param_data[most_prob] def increase_curr_iter(self): @@ -357,7 +357,7 @@ def compute_errors(self): num_samples = self.system.num_samples # compute GrainLearning errors - self.error_arrays = np.zeros(num_samples) + self.error_array = np.zeros(num_samples) # loop over all samples to compute sample percentage errors # if observation data is a time series if self.system.num_steps == 1: @@ -368,10 +368,10 @@ def compute_errors(self): obs_mins = np.min(self.system.obs_data, axis=1) obs_range = obs_maxs - obs_mins for i in range(num_samples): - self.error_arrays[i] = mean_absolute_error(self.system.obs_data.T/obs_range, sim_data[i, :, :].T/obs_range) + self.error_array[i] = mean_absolute_error(self.system.obs_data.T/obs_range, sim_data[i, :, :].T/obs_range) # compute the ensemble error - self.gl_errors.append(np.dot(self.error_arrays, self.calibration.posterior)) + self.gl_errors.append(np.dot(self.error_array, self.inference.posterior)) @classmethod def from_dict( @@ -393,12 +393,12 @@ def from_dict( # Create a system object system = system_type.from_dict(obj["system"]) - # Create a calibration object - calibration = IterativeBayesianFilter.from_dict(obj["calibration"]) + # Create a inference object + inference = IterativeBayesianFilter.from_dict(obj["inference"]) return cls( system=system, - calibration=calibration, + inference=inference, num_iter=obj["num_iter"], curr_iter=obj.get("curr_iter", 0), error_tol=obj.get("error_tol", None), diff --git a/grainlearning/iterative_bayesian_filter.py b/grainlearning/iterative_bayesian_filter.py index 1f2d98ed..a1f945cd 100644 --- a/grainlearning/iterative_bayesian_filter.py +++ b/grainlearning/iterative_bayesian_filter.py @@ -45,7 +45,7 @@ class IterativeBayesianFilter: ibf_cls = IterativeBayesianFilter.from_dict( { - "inference":{ + "Bayes_filter":{ "ess_target": 0.3, "scale_cov_with_max": True }, @@ -63,11 +63,11 @@ class IterativeBayesianFilter: .. code-block:: python system_cls = IterativeBayesianFilter( - inference = SMC(...), + Bayes_filter = SMC(...), sampling = GaussianMixtureModel(...) ) - :param inference: Sequential Monte Carlo class (SMC) + :param Bayes_filter: A Bayesian filtering algorithm. Currently, only the sequential Monte Carlo class (SMC) is available :param sampling: Gaussian Mixture Model class (GMM) :param initial_sampling: The initial sampling method, defaults to Halton :param ess_tol: Tolerance for the target effective sample size to converge, defaults to 1.0e-2 @@ -80,7 +80,7 @@ class IterativeBayesianFilter: def __init__( self, - inference: Type["SMC"] = None, + Bayes_filter: Type["SMC"] = None, sampling: Type["GaussianMixtureModel"] = None, ess_tol: float = 1.0e-2, initial_sampling: str = 'halton', @@ -89,7 +89,7 @@ def __init__( ): """Initialize the Iterative Bayesian Filter.""" - self.inference = inference + self.Bayes_filter = Bayes_filter self.initial_sampling = initial_sampling @@ -115,7 +115,7 @@ def from_dict(cls: Type["IterativeBayesianFilter"], obj: dict): :return: an iBF object """ return cls( - inference=SMC.from_dict(obj["inference"]), + Bayes_filter=SMC.from_dict(obj["Bayes_filter"]), sampling=GaussianMixtureModel.from_dict(obj["sampling"]), ess_tol=obj.get("ess_tol", 1.0e-2), initial_sampling=obj.get("initial_sampling", "halton"), @@ -141,7 +141,7 @@ def run_inference(self, system: Type["DynamicSystem"]): self.load_proposal_from_file(system) result = optimize.minimize_scalar( - self.inference.data_assimilation_loop, + self.Bayes_filter.data_assimilation_loop, args=(system, self.proposal), method="bounded", # tol=self.ess_tol, @@ -151,15 +151,15 @@ def run_inference(self, system: Type["DynamicSystem"]): # use the optimized sigma value to compute the posterior distribution if system.sigma_max > system.sigma_tol: - self.inference.data_assimilation_loop(system.sigma_max, system, self.proposal) + self.Bayes_filter.data_assimilation_loop(system.sigma_max, system, self.proposal) else: - self.inference.data_assimilation_loop(system.sigma_tol, system, self.proposal) + self.Bayes_filter.data_assimilation_loop(system.sigma_tol, system, self.proposal) # get the posterior distribution at the last time step - self.posterior = self.inference.get_posterior_at_time(-1) + self.posterior = self.Bayes_filter.get_posterior_at_time(-1) # compute the estimated means and coefficient of variation from the posterior distribution - system.compute_estimated_params(self.inference.posteriors) + system.compute_estimated_params(self.Bayes_filter.posteriors) def run_sampling(self, system: Type["DynamicSystem"]): """Generate new samples from a proposal density. diff --git a/grainlearning/sampling.py b/grainlearning/sampling.py index 685dc8ad..4ee8cb36 100644 --- a/grainlearning/sampling.py +++ b/grainlearning/sampling.py @@ -4,9 +4,10 @@ from typing import Type from pickle import dump, load import numpy as np +import warnings from sklearn.mixture import BayesianGaussianMixture from scipy.stats.qmc import Sobol, Halton, LatinHypercube -from scipy.optimize import minimize_scalar, root_scalar +from scipy.optimize import root_scalar from grainlearning.dynamic_systems import DynamicSystem @@ -292,8 +293,17 @@ def sample_count_obj(num_samples): return valid_samples_count - minimum_num_samples # Perform optimization to find the minimum number of samples needed - result = root_scalar(sample_count_obj, x0=system.num_samples_max, x1=100*system.num_samples_max, - method='secant') + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + result = root_scalar( + sample_count_obj, + x0=system.num_samples_max, + x1=100*system.num_samples_max, + method='secant', + rtol=1e-3, + maxiter=int(1e5) + ) + system.num_samples_max = int(np.ceil(result.root)) # Draw the required number of samples diff --git a/tests/integration/test_gmm.py b/tests/integration/test_gmm.py index eb35a166..31bf1150 100644 --- a/tests/integration/test_gmm.py +++ b/tests/integration/test_gmm.py @@ -26,8 +26,8 @@ def test_gmm(): "sim_data_dir": sim_data_dir, "param_names": ['a', 'b'], }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True}, "sampling": { @@ -49,11 +49,11 @@ def test_gmm(): # reproduce the result with a given sigma value calibration.load_and_process(sigma_ref) resampled_param_data = calibration.resample() - posterior = calibration.calibration.inference.posteriors + posterior = calibration.inference.Bayes_filter.posteriors # %% # check (co)variance and posterior distribution - cov_matrices = calibration.calibration.inference.get_covariance_matrices(sigma_ref, calibration.system) + cov_matrices = calibration.inference.Bayes_filter.get_covariance_matrices(sigma_ref, calibration.system) np.testing.assert_allclose(cov_matrix_ref, cov_matrices[-1], err_msg="The (co)variances do not match.") np.testing.assert_allclose(posterior, posterior_ref, err_msg="The posterior distributions do not match.") diff --git a/tests/integration/test_lenreg.py b/tests/integration/test_lenreg.py index 68fd248b..4cb7d610 100644 --- a/tests/integration/test_lenreg.py +++ b/tests/integration/test_lenreg.py @@ -32,8 +32,8 @@ def test_lenreg(): "ctrl_data": x_obs, "sim_name": 'linear', }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "n_init": 1, @@ -57,7 +57,7 @@ def test_lenreg(): # print(calibration.sigma_list) # %% - most_prob = np.argmax(calibration.calibration.posterior) + most_prob = np.argmax(calibration.inference.posterior) # %% most_prob_params = calibration.system.param_data[most_prob] @@ -73,7 +73,7 @@ def test_lenreg(): f"Model parameters are not correct, expected 5.0 but got {most_prob_params[1]}" # 2. Checking sigma - assert calibration.calibration.sigma_list[-1] < error_tolerance, "Final sigma is bigger than tolerance." + assert calibration.inference.sigma_list[-1] < error_tolerance, "Final sigma is bigger than tolerance." # %% Test other stopping criteria calibration = BayesianCalibration.from_dict( @@ -90,8 +90,8 @@ def test_lenreg(): "ctrl_data": x_obs, "sim_name": 'linear', }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "n_init": 1, @@ -120,8 +120,8 @@ def test_lenreg(): "ctrl_data": x_obs, "sim_name": 'linear', }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "n_init": 1, diff --git a/tests/integration/test_lenreg_IO.py b/tests/integration/test_lenreg_IO.py index 21311294..55945909 100644 --- a/tests/integration/test_lenreg_IO.py +++ b/tests/integration/test_lenreg_IO.py @@ -49,8 +49,8 @@ def test_lenreg_IO(): "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tests/integration/test_smc.py b/tests/integration/test_smc.py index 2226679a..d2cea4c5 100644 --- a/tests/integration/test_smc.py +++ b/tests/integration/test_smc.py @@ -23,8 +23,8 @@ def test_smc(): "param_data_file": f'{sim_data_dir}/iter{curr_iter}/smcTable0.txt', "param_names": ['a', 'b'], }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True }, @@ -44,11 +44,11 @@ def test_smc(): # reproduce the result with a given sigma value calibration.load_and_process(sigma_ref) # ~ calibration.load_and_run_one_iteration() - posterior = calibration.calibration.inference.posteriors + posterior = calibration.inference.Bayes_filter.posteriors # %% # check (co)variance and posterior distribution - cov_matrices = calibration.calibration.inference.get_covariance_matrices(sigma_ref, calibration.system) + cov_matrices = calibration.inference.Bayes_filter.get_covariance_matrices(sigma_ref, calibration.system) np.testing.assert_allclose(cov_matrix_ref, cov_matrices[-1], err_msg="The (co)variances do not match.") np.testing.assert_allclose(posterior, posterior_ref, err_msg="The posterior distributions do not match.") diff --git a/tests/integration/test_smc_mse.py b/tests/integration/test_smc_mse.py index 44590bd3..97f8c9e6 100644 --- a/tests/integration/test_smc_mse.py +++ b/tests/integration/test_smc_mse.py @@ -32,8 +32,8 @@ def test_smc_mse(): "obs_data": y_obs, "ctrl_data": x_obs, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True }, @@ -43,7 +43,7 @@ def test_smc_mse(): ) calibration.run_one_iteration() - most_prob = np.argmax(calibration.calibration.posterior) + most_prob = np.argmax(calibration.inference.posterior) # most_prob_params = calibration.system.param_data[most_prob] least_err = np.argmin( [mse(calibration.system.sim_data[sid, 0, :], y_obs) for sid in range(calibration.system.num_samples)]) diff --git a/tests/unit/test_iterative_bayesian_filter.py b/tests/unit/test_iterative_bayesian_filter.py index 87c22c1b..b5d952e3 100644 --- a/tests/unit/test_iterative_bayesian_filter.py +++ b/tests/unit/test_iterative_bayesian_filter.py @@ -20,7 +20,7 @@ def test_init(): gmm_cls = GaussianMixtureModel(max_num_components=5) #: Create the iterative bayesian filter ibf_cls = IterativeBayesianFilter( - inference=smc_cls, + Bayes_filter=smc_cls, sampling=gmm_cls, initial_sampling='halton', proposal=np.ones(10), @@ -30,7 +30,7 @@ def test_init(): #: Create the iterative bayesian filter from a dictionary ibf_dct = IterativeBayesianFilter.from_dict( { - "inference": {"ess_target": 0.1}, + "Bayes_filter": {"ess_target": 0.1}, "sampling": {"max_num_components": 5}, "initial_sampling": 'halton', "proposal": np.ones(10), @@ -41,16 +41,16 @@ def test_init(): #: Assert that the object is of the correct type assert isinstance(ibf_dct, IterativeBayesianFilter) assert isinstance(ibf_dct.sampling, GaussianMixtureModel) - assert isinstance(ibf_dct.inference, SMC) + assert isinstance(ibf_dct.Bayes_filter, SMC) raw_ibf_dct = ibf_dct.__dict__ raw_ibf_cls = ibf_cls.__dict__ # TODO @Retief: why do we have to remove these member objects for the assert to work? - raw_ibf_dct.pop("inference") + raw_ibf_dct.pop("Bayes_filter") raw_ibf_dct.pop("sampling") raw_ibf_cls.pop("sampling") - raw_ibf_cls.pop("inference") + raw_ibf_cls.pop("Bayes_filter") #: Assert that the two iterative Bayesian filter objects are equal np.testing.assert_equal(raw_ibf_dct, raw_ibf_cls) @@ -61,7 +61,7 @@ def test_initialize(): #: Create the iterative bayesian filter from a dictionary ibf_dct = IterativeBayesianFilter.from_dict( { - "inference": {"ess_target": 0.1}, + "Bayes_filter": {"ess_target": 0.1}, "sampling": {"max_num_components": 5}, "initial_sampling": 'halton' } @@ -130,7 +130,7 @@ def test_run_inference(): #: Create the iterative bayesian filter from a dictionary ibf_cls = IterativeBayesianFilter.from_dict( { - "inference": {"ess_target": 0.5, "scale_cov_with_max": True}, + "Bayes_filter": {"ess_target": 0.5, "scale_cov_with_max": True}, "sampling": {"max_num_components": 5}, "initial_sampling": 'halton', } @@ -151,7 +151,7 @@ def test_run_inference(): #: Assert that the inference runs correctly if a proposal density is provided ibf_cls = IterativeBayesianFilter.from_dict( { - "inference": {"ess_target": 0.5, "scale_cov_with_max": True}, + "Bayes_filter": {"ess_target": 0.5, "scale_cov_with_max": True}, "sampling": {"max_num_components": 5}, "initial_sampling": 'halton', "proposal": np.array([0.5, 0.2, 0.3]) @@ -182,7 +182,7 @@ def test_save_and_load_proposal(): #: Assert that the inference runs correctly if a proposal density is provided ibf_cls = IterativeBayesianFilter.from_dict( { - "inference": {"ess_target": 1.0}, + "Bayes_filter": {"ess_target": 1.0}, "sampling": {"max_num_components": 1, "expand_factor": 100}, "initial_sampling": "halton", "proposal_data_file": "test_proposal.pkl" @@ -205,7 +205,7 @@ def test_save_and_load_proposal(): #: Correct the scores covs_ref = dummy_proposal.dot((system_cls.param_data - dummy_proposal.dot(system_cls.param_data))**2) ibf_cls.sampling.scores = ibf_cls.sampling.correct_scores(covs_ref, system_cls, tol=1e-6) - + #: Assert that empirical covariance and the covariance esimated by GMM are the same for one Gaussian component empirical_cov = np.cov(ibf_cls.sampling.expanded_normalized_params.T) gmm_cov = ibf_cls.sampling.gmm.covariances_ diff --git a/tutorials/data_driven/LSTM/hyperbola_calibration_lstm.py b/tutorials/data_driven/LSTM/hyperbola_calibration_lstm.py index c968c63f..7763e39f 100644 --- a/tutorials/data_driven/LSTM/hyperbola_calibration_lstm.py +++ b/tutorials/data_driven/LSTM/hyperbola_calibration_lstm.py @@ -43,8 +43,8 @@ def nonlinear(x, params): "sim_name": 'hyperbola', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, diff --git a/tutorials/data_driven/LSTM/hyperbola_calibration_mixed.py b/tutorials/data_driven/LSTM/hyperbola_calibration_mixed.py index 4ae806f5..9180c79e 100644 --- a/tutorials/data_driven/LSTM/hyperbola_calibration_mixed.py +++ b/tutorials/data_driven/LSTM/hyperbola_calibration_mixed.py @@ -126,8 +126,8 @@ def run_sim_mixed(calib): "sim_name": 'hyperbola', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, diff --git a/tutorials/data_driven/LSTM/hyperbola_calibration_mixed_hypertuning.py b/tutorials/data_driven/LSTM/hyperbola_calibration_mixed_hypertuning.py index 266a240f..08f902f0 100644 --- a/tutorials/data_driven/LSTM/hyperbola_calibration_mixed_hypertuning.py +++ b/tutorials/data_driven/LSTM/hyperbola_calibration_mixed_hypertuning.py @@ -180,8 +180,8 @@ def run_sim_mixed(calib): "sim_name": 'hyperbola', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, diff --git a/tutorials/data_driven/LSTM/norsand_triax_calibration_mixed.py b/tutorials/data_driven/LSTM/norsand_triax_calibration_mixed.py index fa7510dc..2ae2a0c9 100644 --- a/tutorials/data_driven/LSTM/norsand_triax_calibration_mixed.py +++ b/tutorials/data_driven/LSTM/norsand_triax_calibration_mixed.py @@ -233,8 +233,8 @@ def run_sim_mixed(calib): "sim_name": 'triax', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, @@ -256,9 +256,9 @@ def run_sim_mixed(calib): from grainlearning.tools import plot_posterior, plot_param_data, plot_pdf plot_posterior('test', param_names, calibration.system.param_data[calibration.ids_surrogate], - calibration.calibration.inference.posteriors[:, calibration.ids_surrogate]) + calibration.inference.Bayes_filter.posteriors[:, calibration.ids_surrogate]) plot_posterior('test', param_names, calibration.system.param_data[calibration.ids_origin], - calibration.calibration.inference.posteriors[:, calibration.ids_origin]) + calibration.inference.Bayes_filter.posteriors[:, calibration.ids_origin]) plt.show() plot_pdf('test', param_names, [calibration.system.param_data[calibration.ids_surrogate], diff --git a/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py b/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py index 3d451fad..c16bf046 100644 --- a/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py +++ b/tutorials/physics_based/2D_dike_stability/2D_dike_model_inference.py @@ -44,7 +44,7 @@ def run_sim(calib): fig_name = f'{path}/{system.sim_name}' # get the id of sample that has the highest probability - most_prob_params_id = np.argmax(calib.calibration.posterior) + most_prob_params_id = np.argmax(calib.inference.posterior) sim_data = system.sim_data[most_prob_params_id, :, :] obs_data = system.obs_data @@ -73,7 +73,7 @@ def run_sim(calib): plot_param_data( fig_name, system.param_names, - calib.calibration.param_data_list, + calib.inference.param_data_list, save_fig=0 ) plt.show() @@ -133,11 +133,11 @@ def set_normalization_factor(self): "sim_name": sim_name, "sim_data_dir": PATH + '/sim_data/', "sim_data_file_ext": '.txt', - "sigma_tol": 0.01, + "sigma_tol": 0.1, "sigma_max": 10, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "random_state": 0, @@ -163,6 +163,6 @@ def set_normalization_factor(self): error) / true_param < error_tolerance, \ f"Model parameters are not correct, expected {true_param} but got {prob_param}" -plot_pdf('2D_dike', param_names, calibration.calibration.param_data_list, save_fig=0, +plot_pdf('2D_dike', param_names, calibration.inference.param_data_list, save_fig=0, true_params=true_params) plt.show() diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py index 9a8ecc46..6e25853e 100644 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py @@ -39,8 +39,8 @@ def run_sim(calib): "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3, "scale_cov_with_max": True}, + "inference": { + "Bayes_filter": {"ess_target": 0.3, "scale_cov_with_max": True}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py index c2c00fee..5889db7f 100644 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py @@ -29,8 +29,8 @@ "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3, "scale_cov_with_max": True}, + "inference": { + "Bayes_filter": {"ess_target": 0.3, "scale_cov_with_max": True}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py index 2fbf802d..d90a335c 100644 --- a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py +++ b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py @@ -148,25 +148,24 @@ def run_sim(calib): "sim_name": 'triax', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, "sampling": { - "max_num_components": 10, - "n_init": 1, + "max_num_components": 1, "random_state": 0, "slice_sampling": False, }, "initial_sampling": "LH", }, - "save_fig": 0, + "save_fig": -1, } ) calibration.run() true_params = [gamma, lambda_val, M_tc, N, H, Xim_tc, Ir, nu] -plot_pdf('norsand_triax', param_names, calibration.calibration.param_data_list, save_fig=0, true_params=true_params) +plot_pdf('norsand_triax', param_names, calibration.inference.param_data_list, save_fig=0, true_params=true_params) plt.show() diff --git a/tutorials/physics_based/oedometric_compression/oedo_load_and_resample.py b/tutorials/physics_based/oedometric_compression/oedo_load_and_resample.py index 4078bc8b..c6ea863d 100644 --- a/tutorials/physics_based/oedometric_compression/oedo_load_and_resample.py +++ b/tutorials/physics_based/oedometric_compression/oedo_load_and_resample.py @@ -26,8 +26,8 @@ "param_max": [200e9, 0.5, 1e4, 0.5], "inv_obs_weight": [1, 1, 0.01], }, - "calibration": { - "inference": {"ess_target": 0.2}, + "inference": { + "Bayes_filter": {"ess_target": 0.2}, "sampling": { "max_num_components": 10, "weight_concentration_prior": 0.01, diff --git a/tutorials/physics_based/two_particle_collision/collision_calibration.py b/tutorials/physics_based/two_particle_collision/collision_calibration.py index f4bcfa11..9d4f5b3f 100644 --- a/tutorials/physics_based/two_particle_collision/collision_calibration.py +++ b/tutorials/physics_based/two_particle_collision/collision_calibration.py @@ -38,8 +38,8 @@ def run_sim(calib): "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tutorials/simple_regression/linear_regression/linear_reg_one_iteration.py b/tutorials/simple_regression/linear_regression/linear_reg_one_iteration.py index 5982bdde..1b5645d0 100644 --- a/tutorials/simple_regression/linear_regression/linear_reg_one_iteration.py +++ b/tutorials/simple_regression/linear_regression/linear_reg_one_iteration.py @@ -26,8 +26,8 @@ "sim_data_file_ext": ".npy", "param_names": ['a', 'b'], }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "covariance_type": "full", @@ -64,8 +64,8 @@ "sim_data": sim_data, "callback": None, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, }, diff --git a/tutorials/simple_regression/linear_regression/linear_regression_load_all.py b/tutorials/simple_regression/linear_regression/linear_regression_load_all.py index b435c1b1..ff30d0f9 100644 --- a/tutorials/simple_regression/linear_regression/linear_regression_load_all.py +++ b/tutorials/simple_regression/linear_regression/linear_regression_load_all.py @@ -24,8 +24,8 @@ "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 2, "n_init": 1, diff --git a/tutorials/simple_regression/linear_regression/linear_regression_solve.py b/tutorials/simple_regression/linear_regression/linear_regression_solve.py index 30769d1c..94600da9 100644 --- a/tutorials/simple_regression/linear_regression/linear_regression_solve.py +++ b/tutorials/simple_regression/linear_regression/linear_regression_solve.py @@ -49,8 +49,8 @@ def linear(executable, params, sim_name, description): "sim_data_file_ext": '.txt', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "n_init": 1, diff --git a/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py b/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py index ddbd010f..db9cce88 100644 --- a/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py +++ b/tutorials/simple_regression/linear_regression/python_linear_regression_solve.py @@ -43,8 +43,8 @@ def linear(x, params): "sim_name": 'linear', "sigma_tol": 0.01, }, - "calibration": { - "inference": {"ess_target": 0.3}, + "inference": { + "Bayes_filter": {"ess_target": 0.3}, "sampling": { "max_num_components": 1, "n_init": 1, diff --git a/tutorials/simple_regression/nonlinear_regression/python_hyperbola_regression_solve.py b/tutorials/simple_regression/nonlinear_regression/python_hyperbola_regression_solve.py index 0a910a67..154c73d2 100644 --- a/tutorials/simple_regression/nonlinear_regression/python_hyperbola_regression_solve.py +++ b/tutorials/simple_regression/nonlinear_regression/python_hyperbola_regression_solve.py @@ -57,8 +57,8 @@ def nonlinear(x, params): "sim_name": 'nonlinear', "sigma_tol": 0.01, }, - "calibration": { - "inference": { + "inference": { + "Bayes_filter": { "ess_target": 0.3, "scale_cov_with_max": True, }, From f1c615ca2410909c1d037aa3ac9778e6e010ea7e Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Sat, 25 Jan 2025 15:41:04 +0100 Subject: [PATCH 04/29] do not show detailed statistics be default --- grainlearning/bayesian_calibration.py | 48 +++++++++++++++------------ grainlearning/sampling.py | 2 +- tests/integration/test_lenreg_IO.py | 19 +++++++---- tests/unit/test_sampling.py | 1 - 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/grainlearning/bayesian_calibration.py b/grainlearning/bayesian_calibration.py index 33f3d4cd..4cd891b1 100644 --- a/grainlearning/bayesian_calibration.py +++ b/grainlearning/bayesian_calibration.py @@ -268,8 +268,10 @@ def resample(self): self.system.write_params_to_table() return resampled_param_data - def plot_uq_in_time(self): + def plot_uq_in_time(self, verbose: bool = False): """Plot the evolution of uncertainty moments and distribution over time + + param verbose: plot also the detailed statistics, defaults to False """ if self.save_fig < 0: return @@ -282,27 +284,29 @@ def plot_uq_in_time(self): os.makedirs(path) fig_name = f'{path}/{self.system.sim_name}' - plot_param_stats( - fig_name, self.system.param_names, - self.system.estimated_params, - self.system.estimated_params_cv, - self.save_fig - ) - - plot_posterior( - fig_name, - self.system.param_names, - self.system.param_data, - self.inference.Bayes_filter.posteriors, - self.save_fig - ) - - plot_param_data( - fig_name, - self.system.param_names, - self.inference.param_data_list, - self.save_fig - ) + + if verbose: + plot_param_stats( + fig_name, self.system.param_names, + self.system.estimated_params, + self.system.estimated_params_cv, + self.save_fig + ) + + plot_posterior( + fig_name, + self.system.param_names, + self.system.param_data, + self.inference.Bayes_filter.posteriors, + self.save_fig + ) + + plot_param_data( + fig_name, + self.system.param_names, + self.inference.param_data_list, + self.save_fig + ) plot_obs_and_sim( fig_name, diff --git a/grainlearning/sampling.py b/grainlearning/sampling.py index 4ee8cb36..e86616b1 100644 --- a/grainlearning/sampling.py +++ b/grainlearning/sampling.py @@ -228,7 +228,7 @@ def train(self, weight: np.ndarray, system: Type["DynamicSystem"]): self.gmm.fit(self.expanded_normalized_params) - self.scores = self.gmm.score_samples(np.unique(self.expanded_normalized_params, axis=1)) + self.scores = self.gmm.score_samples(np.unique(self.expanded_normalized_params, axis=0)) # FIXME: Gaussian mixture model introduces bias leading to covariances resulting from the trained probability density not matching the sample covariances # means_ref = weight.dot(system.param_data) # covs_ref = weight.dot((system.param_data - means_ref)**2) diff --git a/tests/integration/test_lenreg_IO.py b/tests/integration/test_lenreg_IO.py index 55945909..e2d33b20 100644 --- a/tests/integration/test_lenreg_IO.py +++ b/tests/integration/test_lenreg_IO.py @@ -58,11 +58,15 @@ def test_lenreg_IO(): "covariance_type": "full", } }, - "save_fig": 1, + "save_fig": -1, } ) calibration.run() + + calibration.save_fig = 1 + + calibration.plot_uq_in_time(verbose=True) most_prob_params = calibration.get_most_prob_params() print(f'Most probable parameter values: {most_prob_params}') @@ -77,18 +81,19 @@ def test_lenreg_IO(): error[ 1]) / 5.0 < error_tolerance, f"Model parameters are not correct, expected 5.0 but got {most_prob_params[1]}" #: Check if the figures are saved + sub_dir = f'/sim_data/iter{calibration.curr_iter}/' assert os.path.isfile( - PATH + '/sim_data/iter0/linear_param_means.png'), "Figure is not saved" + PATH + sub_dir + '/linear_param_means.png'), "Figure is not saved" assert os.path.isfile( - PATH + '/sim_data/iter0/linear_param_covs.png'), "Figure is not saved" + PATH + sub_dir + '/linear_param_covs.png'), "Figure is not saved" assert os.path.isfile( - PATH + '/sim_data/iter0/linear_posterior_a.png'), "Figure is not saved" + PATH + sub_dir + '/linear_posterior_a.png'), "Figure is not saved" assert os.path.isfile( - PATH + '/sim_data/iter0/linear_posterior_b.png'), "Figure is not saved" + PATH + sub_dir + '/linear_posterior_b.png'), "Figure is not saved" assert os.path.isfile( - PATH + '/sim_data/iter0/linear_param_space.png'), "Figure is not saved" + PATH + sub_dir + '/linear_param_space.png'), "Figure is not saved" assert os.path.isfile( - PATH + '/sim_data/iter0/linear_obs_and_sim.png'), "Figure is not saved" + PATH + sub_dir + '/linear_obs_and_sim.png'), "Figure is not saved" # Remove the generated files rmtree(PATH + '/sim_data/') diff --git a/tests/unit/test_sampling.py b/tests/unit/test_sampling.py index 0ce8d23c..62376544 100644 --- a/tests/unit/test_sampling.py +++ b/tests/unit/test_sampling.py @@ -155,7 +155,6 @@ def test_regenerate_params(): new_params, np.array( [ - [3.26138980e+06, 4.04612304e-01], [5.05962209e+06, 3.27993805e-01], [4.16050594e+06, 3.50128038e-01], [2.81183173e+06, 4.11422838e-01] From fe8b0aad2ea245e9d31d520d9d249a419ae674fb Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Sun, 26 Jan 2025 10:52:51 +0100 Subject: [PATCH 05/29] more explanation for two_particle_collision tutorial --- grainlearning/bayesian_calibration.py | 10 ++++-- grainlearning/dynamic_systems.py | 17 ++++++---- grainlearning/inference.py | 4 +-- grainlearning/sampling.py | 4 +-- tests/integration/test_gmm.py | 2 +- .../{test_lenreg_IO.py => test_linreg_IO.py} | 0 .../norsand_triax_calibration.py | 1 + .../two_particle_collision/Collision.py | 2 +- .../collision_calibration.py | 32 +++++++++++++++---- 9 files changed, 50 insertions(+), 22 deletions(-) rename tests/integration/{test_lenreg_IO.py => test_linreg_IO.py} (100%) diff --git a/grainlearning/bayesian_calibration.py b/grainlearning/bayesian_calibration.py index 4cd891b1..e0fccaab 100644 --- a/grainlearning/bayesian_calibration.py +++ b/grainlearning/bayesian_calibration.py @@ -90,6 +90,7 @@ def __init__( error_tol: float = None, gl_error_tol: float = None, save_fig: int = -1, + threads: int = 4, callback: Callable = None, ): """Initialize the Bayesian calibration class""" @@ -98,6 +99,8 @@ def __init__( self.save_fig = save_fig + self.threads = threads + self.system = system self.system.curr_iter = curr_iter @@ -199,7 +202,7 @@ def run_callback(self): if self.callback is not None: if isinstance(self.system, IODynamicSystem): - self.system.set_up_sim_dir() + self.system.set_up_sim_dir(self.threads) self.callback(self) self.system.move_data_to_sim_dir() else: @@ -226,7 +229,7 @@ def load_and_run_one_iteration(self): self.load_system() self.inference.add_curr_param_data_to_list(self.system.param_data) self.inference.solve(self.system) - self.system.write_params_to_table() + self.system.write_params_to_table(self.threads) self.inference.sigma_list.append(self.system.sigma_max) self.plot_uq_in_time() @@ -265,7 +268,7 @@ def resample(self): self.inference.posterior = self.inference.Bayes_filter.get_posterior_at_time() self.inference.run_sampling(self.system, ) resampled_param_data = self.inference.param_data_list[-1] - self.system.write_params_to_table() + self.system.write_params_to_table(self.threads) return resampled_param_data def plot_uq_in_time(self, verbose: bool = False): @@ -408,5 +411,6 @@ def from_dict( error_tol=obj.get("error_tol", None), gl_error_tol=obj.get("gl_error_tol", None), save_fig=obj.get("save_fig", -1), + threads=obj.get("threads", 4), callback=obj.get("callback", None) ) diff --git a/grainlearning/dynamic_systems.py b/grainlearning/dynamic_systems.py index 4eb25f99..44bff9ac 100644 --- a/grainlearning/dynamic_systems.py +++ b/grainlearning/dynamic_systems.py @@ -295,8 +295,11 @@ def load_sim_data(cls: Type["DynamicSystem"]): """Virtual function to load simulation data""" @classmethod - def write_params_to_table(cls: Type["DynamicSystem"]): - """Write the parameter data into a text file""" + def write_params_to_table(cls: Type["DynamicSystem"], threads: int): + """Write the parameter data into a text file + + :param threads: Number of threads to use + """ @classmethod def backup_sim_data(cls: Type["DynamicSystem"]): @@ -598,7 +601,7 @@ def load_param_data(self): raise RuntimeError(f'No data found for iteration {self.curr_iter}') self.num_samples_max = self.num_samples - def set_up_sim_dir(self): + def set_up_sim_dir(self, threads: int): """ Create a directory to store simulation data and write the parameter data into a text file """ @@ -609,7 +612,7 @@ def set_up_sim_dir(self): os.makedirs(sim_data_sub_dir) # write the parameter data into a text file - self.write_params_to_table() + self.write_params_to_table(threads) def move_data_to_sim_dir(self): """ @@ -624,7 +627,7 @@ def move_data_to_sim_dir(self): # redefine the parameter data file since its location is changed self.param_data_file = f'{self.sim_data_sub_dir}/' + os.path.relpath(self.param_data_file, os.getcwd()) - def write_params_to_table(self): + def write_params_to_table(self, threads: int): """Write the parameter data into a text file. :return param_data_file: The name of the parameter data file @@ -633,7 +636,9 @@ def write_params_to_table(self): self.sim_name, self.param_data, self.param_names, - self.curr_iter) + self.curr_iter, + threads=threads, + ) def backup_sim_data(self): """Backup simulation data files to a backup directory.""" diff --git a/grainlearning/inference.py b/grainlearning/inference.py index 7c25b7a1..82832057 100644 --- a/grainlearning/inference.py +++ b/grainlearning/inference.py @@ -49,7 +49,7 @@ class SMC: """ def __init__( self, - ess_target: float, + ess_target: float = 0.3, scale_cov_with_max: bool = False, cov_matrices: np.array = None, ): @@ -74,7 +74,7 @@ def from_dict(cls: Type["SMC"], obj: dict): :return: an SMC object """ return cls( - ess_target=obj["ess_target"], + ess_target=obj.get("ess_target", 0.3), scale_cov_with_max=obj.get("scale_cov_with_max", False), cov_matrices=obj.get("cov_matrices", None), ) diff --git a/grainlearning/sampling.py b/grainlearning/sampling.py index e86616b1..158fca7a 100644 --- a/grainlearning/sampling.py +++ b/grainlearning/sampling.py @@ -98,7 +98,7 @@ class GaussianMixtureModel: def __init__( self, - max_num_components = 0, + max_num_components: int = 1, weight_concentration_prior: float = 0.0, covariance_type: str = "tied", n_init: int = 1, @@ -157,7 +157,7 @@ def from_dict(cls: Type["GaussianMixtureModel"], obj: dict): :return: a GMM object """ return cls( - max_num_components=obj["max_num_components"], + max_num_components=obj.get("max_num_components", 1), weight_concentration_prior=obj.get("weight_concentration_prior", None), covariance_type=obj.get("covariance_type", "tied"), n_init=obj.get("n_init", 10), diff --git a/tests/integration/test_gmm.py b/tests/integration/test_gmm.py index 31bf1150..0732cb34 100644 --- a/tests/integration/test_gmm.py +++ b/tests/integration/test_gmm.py @@ -59,7 +59,7 @@ def test_gmm(): # %% # write new parameter table to the simulation directory - calibration.system.write_params_to_table() + calibration.system.write_params_to_table(calibration.threads) # %% check_list = np.isclose(resampled_param_data_ref, resampled_param_data) diff --git a/tests/integration/test_lenreg_IO.py b/tests/integration/test_linreg_IO.py similarity index 100% rename from tests/integration/test_lenreg_IO.py rename to tests/integration/test_linreg_IO.py diff --git a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py index d90a335c..b5f40237 100644 --- a/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py +++ b/tutorials/physics_based/NorSand_triax_compression/norsand_triax_calibration.py @@ -156,6 +156,7 @@ def run_sim(calib): "sampling": { "max_num_components": 1, "random_state": 0, + # FIXME slice sampling requires rejecting samples whose likelihood are low. However, this process becomes very slow if dimensionality is high. "slice_sampling": False, }, "initial_sampling": "LH", diff --git a/tutorials/physics_based/two_particle_collision/Collision.py b/tutorials/physics_based/two_particle_collision/Collision.py index 97ba6672..7ecb4aed 100644 --- a/tutorials/physics_based/two_particle_collision/Collision.py +++ b/tutorials/physics_based/two_particle_collision/Collision.py @@ -95,7 +95,7 @@ def add_sim_data(): # set initial timestep O.dt = table.safe * PWaveTimeStep() # move particle 1 -O.bodies[1].state.vel = Vector3(0, 0, -0.01) +O.bodies[1].state.vel = Vector3(0, 0, -1) # run DEM simulation O.run() diff --git a/tutorials/physics_based/two_particle_collision/collision_calibration.py b/tutorials/physics_based/two_particle_collision/collision_calibration.py index 9d4f5b3f..f756de14 100644 --- a/tutorials/physics_based/two_particle_collision/collision_calibration.py +++ b/tutorials/physics_based/two_particle_collision/collision_calibration.py @@ -8,10 +8,10 @@ from grainlearning.dynamic_systems import IODynamicSystem PATH = os.path.abspath(os.path.dirname(__file__)) -executable = 'yade-batch' +executable = 'yadedaily-batch' yade_script = f'{PATH}/Collision.py' - +# define the callback function which GrainLearning uses to run YADE simulations def run_sim(calib): """ Run the external executable and passes the parameter sample to generate the output file. @@ -19,16 +19,24 @@ def run_sim(calib): print("*** Running external software YADE ... ***\n") os.system(' '.join([executable, calib.system.param_data_file, yade_script])) +param_names = ['E_m', 'nu'] +num_samples = int(5 * len(param_names) * log(len(param_names))) +# define the Bayesian Calibration object calibration = BayesianCalibration.from_dict( { - "num_iter": 4, + # maximum number of GL iterations + "num_iter": 5, + # error tolerance to stop the calibration + "error_tol": 0.1, + # call back function to run YADE "callback": run_sim, + # DEM model as a dynamic system "system": { "system_type": IODynamicSystem, "param_min": [7, 0.0], "param_max": [11, 0.5], - "param_names": ['E_m', 'nu'], + "param_names": param_names, "num_samples": 10, "obs_data_file": PATH + '/collision_obs.dat', "obs_names": ['f'], @@ -39,15 +47,25 @@ def run_sim(calib): "sigma_tol": 0.01, }, "inference": { - "Bayes_filter": {"ess_target": 0.3}, + "Bayes_filter": { + # scale the covariance matrix with the maximum observation data or not + "scale_cov_with_max": False, + }, "sampling": { - "max_num_components": 2, - "n_init": 1, + # maximum number of distribution components + "max_num_components": 1, + # fix the seed for reproducibility "random_state": 0, + # type of covariance matrix "covariance_type": "full", + # use slice sampling (set to False if faster convergence is designed. However, the results could be biased) + "slice_sampling": True, } }, + # flag to save the figures (-1: no, 0: yes but only show the figures , 1: yes) "save_fig": 0, + # number of threads to be used by the external software + "threads": 16, } ) From da45cb18dcf30391234e21cfac6633356045ea4e Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Tue, 28 Jan 2025 20:34:50 +0100 Subject: [PATCH 06/29] improve tutorials for YADE DEM calibration --- grainlearning/bayesian_calibration.py | 22 +++--- grainlearning/iterative_bayesian_filter.py | 2 +- .../triax_YADE_DEM_model.py | 62 ++++++++++------ .../triax_calibration.py | 72 +++++++++++++------ .../triax_calibration_load_and_run.py | 49 ++++++++----- .../two_particle_collision/Collision.py | 12 ++-- .../collision_calibration.py | 47 +++++++++--- 7 files changed, 179 insertions(+), 87 deletions(-) diff --git a/grainlearning/bayesian_calibration.py b/grainlearning/bayesian_calibration.py index e0fccaab..95e5ec94 100644 --- a/grainlearning/bayesian_calibration.py +++ b/grainlearning/bayesian_calibration.py @@ -20,14 +20,13 @@ class BayesianCalibration: 2. The inference method, for example, :class:`.IterativeBayesianFilter`, - 3. The number of iterations + 3. The number of iterations, - 4. The current iteration number if the user simply wants to process their data with GrainLearning for one iteration - # or continue from that iteration (TODO) + 4. The current iteration number if the user simply wants to process their data with GrainLearning for one iteration, - # 5. A tolerance to stop the iterations if the maximum uncertainty is below the tolerance - - 5. and the flag for skipping (-1), showing (0), or saving (1) the figures. + 5. the flag for skipping (-1), showing (0), or saving (1) the figures, + + 6. and the tolerances for stopping the calibration based on the mean absolute percentage error and the ensemble percentage error. There are two ways of initializing a calibration toolbox class. @@ -175,6 +174,10 @@ def run_one_iteration(self, index: int = -1): # Defining stopping criterion error_most_probable = min(self.error_array) gl_error = self.gl_errors[-1] + print(f"\n" + f"sigma = {self.system.sigma_max},\n" + f"Smallest mean absolute percentage error = {error_most_probable: .3e},\n" + f"GrainLearning ensemble percentage error = {gl_error: .3e}\n") # If any stopping condition is met # Check stopping criteria @@ -183,12 +186,7 @@ def run_one_iteration(self, index: int = -1): ensemble_error_met = gl_error < self.gl_error_tol if self.gl_error_tol is not None else False if normalized_sigma_met or most_probable_error_met or ensemble_error_met: - print(f"\n" - f"Stopping criteria met: \n" - f"sigma = {self.system.sigma_max},\n" - f"Smallest mean absolute percentage error = {error_most_probable: .3e},\n" - f"GrainLearning ensemble percentage error = {gl_error: .3e}\n\n" - f"Ending Bayesian calibration.") + print(f"\nStopping criteria are met. Ending Bayesian calibration!\n") self.num_iter = self.curr_iter + 1 return True else: diff --git a/grainlearning/iterative_bayesian_filter.py b/grainlearning/iterative_bayesian_filter.py index a1f945cd..274e08b0 100644 --- a/grainlearning/iterative_bayesian_filter.py +++ b/grainlearning/iterative_bayesian_filter.py @@ -62,7 +62,7 @@ class IterativeBayesianFilter: .. highlight:: python .. code-block:: python - system_cls = IterativeBayesianFilter( + ibf_cls = IterativeBayesianFilter( Bayes_filter = SMC(...), sampling = GaussianMixtureModel(...) ) diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py b/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py index f5f2121b..48bcf3a0 100755 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_YADE_DEM_model.py @@ -5,15 +5,15 @@ # no. of your simulation key=0, # Young's modulus - E_m=8.8328129646e+00, + E_m=9, # Poisson's ratio - v=1.1768868293e-01, + v=0.1, # rolling/bending stiffness - kr=1.0968388788e-01, + kr=0.1, # rolling/bending plastic limit - eta=7.3928627878e-01, + eta=0.7, # final friction coefficient - mu=2.9648597170e+01, + mu=30, # number of particles num=1000, # initial confining pressure @@ -23,7 +23,7 @@ import numpy as np from yade.params import table -from yade import pack +from yade import pack, plot from grainlearning.tools import get_keys_and_data, write_dict_to_file # check if run in batch mode @@ -37,6 +37,7 @@ num = table.num # number of soil particles dScaling = 1e3 # density scaling e = 0.68 # initial void ratio +conf_init = 1e3 # very small initial confining pressure conf = table.conf # confining pressure rate = 0.1 # strain rate (decrease this for serious calculations) damp = 0.2 # damping coefficient @@ -49,9 +50,7 @@ debug = False #: load strain/stress data for quasi-static loading -obs_file_name = "triax_data_DEM.dat" -obs_data = get_keys_and_data(obs_file_name) -obs_ctrl_data = list(0.01 * obs_data[obsCtrl]) +obs_ctrl_data = np.linspace(0.01, 0.4, 40).tolist() obs_ctrl_data.reverse() #: Soil sphere parameters @@ -59,20 +58,17 @@ v = table.v # micro Poisson's ratio kr = table.kr # rolling/bending stiffness eta = table.eta # rolling/bending plastic limit -mu = table.mu # contact friction during shear -ctrMu = table.mu # use small mu to prepare dense packing? +mu = radians(table.mu) # contact friction during shear +ctrMu = radians(table.mu) # use small mu to prepare dense packing? rho = 2650 * dScaling # soil density #: create materials spMat = O.materials.append( - CohFrictMat(young=E, poisson=v, frictionAngle=radians(ctrMu), density=rho, isCohesive=False, + CohFrictMat(young=E, poisson=v, frictionAngle=ctrMu, density=rho, isCohesive=False, alphaKr=kr, alphaKtw=kr, momentRotationLaw=True, etaRoll=eta, etaTwist=eta)) # create dictionary to store simulation data -sim_data = {} -sim_data['e_z'] = [] -sim_data['e_v'] = [] -sim_data['s33_over_s11'] = [] +plot.plots={'e_z': ('e', 'e_v', 's33_over_s11')} #: define the periodic box where a certain configuration of particles is loaded O.periodic = True @@ -105,6 +101,7 @@ # whether they are strains or stresses stressMask=7, # confining stress + # goal=(-conf_init, -conf_init, -conf_init), goal=(-conf, -conf, -conf), # strain rate maxStrainRate=(10. * rate, 10. * rate, 10. * rate), @@ -115,12 +112,32 @@ # turn on checkVoidRatio after finishing initial compression relStressTol=stressTolRatio, # function to call after the goal is reached - doneHook='compactionFinished()', + # doneHook='init_porosity_checker.dead = False', + doneHook='compactionFinished()', ), NewtonIntegrator(damping=damp, label='newton'), + # PyRunner(command="check_init_porosity()", iterPeriod=1000, dead=True, label='init_porosity_checker'), ] +# Check if the initial void ratio is reached at a low initial confining pressure (turned off because this takes too long) +def check_init_porosity(): + global ctrMu + n = porosity() + # reduce inter-particle friction if e is still big + if n/(1.-n) > e: + ctrMu *= 0.9 + print('Frcition coefficient reduces to: ', tan(ctrMu), 'Void ratio: ', n/(1.-n)) + setContactFriction(ctrMu) + init_porosity_checker.dead = True + else: + # now start isotropic compression + triax.goal = (-conf,-conf,-conf) + triax.doneHook = "compactionFinished()" + # set inter-particle friction to correct level + setContactFriction(mu) + + def compactionFinished(): if unbalancedForce() < stabilityRatio: if debug: print('compaction finished') @@ -168,9 +185,12 @@ def addPlotData(): e_v = e_x + e_y + e_z n = porosity() print("Triax goal is: ", triax.goal, "dt=", O.dt, "numIter=", O.iter, "real time=", O.realtime) # e = n/(1.-n) - sim_data['e_z'].append(100 * e_z) - sim_data['e_v'].append(100 * e_v) - sim_data['s33_over_s11'].append(s33_over_s11) + plot.addData( + e=porosity()/ (1. - porosity()), + e_z=100 * e_z, + e_v=100 * e_v, + s33_over_s11=s33_over_s11 + ) if len(obs_ctrl_data) != 0: startLoading() else: @@ -182,7 +202,7 @@ def addPlotData(): for name in table.__all__: param_data[name] = eval('table.' + name) # write simulation data into a text file - write_dict_to_file(sim_data, data_file_name) + write_dict_to_file(plot.data, data_file_name) write_dict_to_file(param_data, data_param_name) O.pause() diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py index 6e25853e..2d922868 100644 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration.py @@ -1,11 +1,10 @@ """ - This tutorial shows how to perform iterative Bayesian calibration for a DEM simulation of two particle colliding - using GrainLearning. The simulation is performed using Yade on a desktop computer. + This tutorial shows how to perform iterative Bayesian calibration for a DEM simulation of triaxial compression + using GrainLearning. The simulations can be performed using Yade on a desktop computer. """ import os from math import floor, log -from grainlearning import BayesianCalibration -from grainlearning.dynamic_systems import IODynamicSystem +from grainlearning import BayesianCalibration, IODynamicSystem, IterativeBayesianFilter, SMC, GaussianMixtureModel PATH = os.path.abspath(os.path.dirname(__file__)) executable = 'yade-batch' @@ -20,42 +19,73 @@ def run_sim(calib): os.system(' '.join([executable, calib.system.param_data_file, yade_script])) +param_names = ['kr', 'eta', 'mu'] +num_samples = int(5 * len(param_names) * log(len(param_names))) calibration = BayesianCalibration.from_dict( { "curr_iter": 0, - "num_iter": 4, + "num_iter": 5, + "error_tol": 0.1, "callback": run_sim, "system": { "system_type": IODynamicSystem, - "param_min": [7, 0.0, 0.0, 0.0, 10.0], - "param_max": [11, 0.5, 1.0, 1.0, 50.0], - "param_names": ['E_m', 'v', 'kr', 'eta', 'mu'], - "num_samples": 15, - "obs_data_file": PATH + '/triax_data_DEM.dat', - "obs_names": ['e_v', 's33_over_s11'], + "param_min": [0.0, 0.0, 1.0], + "param_max": [1.0, 1.0, 60.0], + "param_names": param_names, + "num_samples": num_samples, + "obs_data_file": PATH + '/triax_DEM_test_run_sim.txt', + "obs_names": ['e', 's33_over_s11'], "ctrl_name": 'e_z', "sim_name": 'triax', "sim_data_dir": PATH + '/sim_data/', "sim_data_file_ext": '.txt', - "sigma_tol": 0.01, }, "inference": { - "Bayes_filter": {"ess_target": 0.3, "scale_cov_with_max": True}, + "Bayes_filter": {"scale_cov_with_max": True}, "sampling": { - "max_num_components": 2, - "n_init": 1, - "random_state": 0, - "covariance_type": "full", - } + "max_num_components": 5, + "slice_sampling": True, + }, }, - "save_fig": 0, + "save_fig": -1, + "threads": 1, } ) +# calibration = BayesianCalibration( +# num_iter=5, +# error_tol=0.1, +# callback=run_sim, +# save_fig=-1, +# threads=1, +# system=IODynamicSystem( +# param_min=[0.0, 0.0, 10.0], +# param_max=[10.0, 1.0, 60.0], +# param_names=param_names, +# num_samples=num_samples, +# obs_data_file=PATH + '/triax_DEM_test_run_sim.txt', +# obs_names=['e_v', 's33_over_s11'], +# ctrl_name='e_z', +# sim_name='triax', +# sim_data_dir=PATH + '/sim_data/', +# sim_data_file_ext='.txt', +# ), +# inference=IterativeBayesianFilter( +# SMC( +# scale_cov_with_max=True, +# ), +# GaussianMixtureModel( +# max_num_components=5, +# slice_sampling=True, +# ), +# initial_sampling="LH", +# ), +# ) + calibration.run() most_prob_params = calibration.get_most_prob_params() print(f'Most probable parameter values: {most_prob_params}') -# calibration.load_and_run_one_iteration() -# resampled_param_data = calibration.resample() +calibration.save_fig = 0 +calibration.plot_uq_in_time() \ No newline at end of file diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py index 5889db7f..2208068e 100644 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py @@ -1,44 +1,54 @@ """ - This tutorial shows how to perform iterative Bayesian calibration for a DEM simulation of two particle colliding - using GrainLearning. The simulation is performed using Yade on a desktop computer. + This tutorial shows how to perform iterative Bayesian calibration for a DEM simulation of triaxial compression + using GrainLearning. The simulations can be performed using Yade on a desktop computer. """ import os from math import floor, log -from grainlearning import BayesianCalibration -from grainlearning.dynamic_systems import IODynamicSystem +from grainlearning import BayesianCalibration, IODynamicSystem, IterativeBayesianFilter, SMC, GaussianMixtureModel PATH = os.path.abspath(os.path.dirname(__file__)) +executable = 'yade-batch' +yade_script = f'{PATH}/triax_YADE_DEM_model.py' curr_iter = 0 +def run_sim(calib): + """ + Run the external executable and passes the parameter sample to generate the output file. + """ + print("*** Running external software YADE ... ***\n") + os.system(' '.join([executable, calib.system.param_data_file, yade_script])) + + +param_names = ['kr', 'eta', 'mu'] +num_samples = int(5 * len(param_names) * log(len(param_names))) calibration = BayesianCalibration.from_dict( { "curr_iter": curr_iter, - "num_iter": 0, + "num_iter": 5, + "error_tol": 0.1, + "callback": run_sim, "system": { "system_type": IODynamicSystem, - "param_min": [7, 0.0, 0.0, 0.0, 10.0], - "param_max": [11, 0.5, 1.0, 1.0, 50.0], - "param_names": ['E_m', 'v', 'kr', 'eta', 'mu'], - "num_samples": 15, - "obs_data_file": PATH + '/triax_data_DEM.dat', - "obs_names": ['e_v', 's33_over_s11'], + "param_names": param_names, + "num_samples": num_samples, + "obs_data_file": PATH + '/triax_DEM_test_run_sim.txt', + "obs_names": ['e', 's33_over_s11'], "ctrl_name": 'e_z', "sim_name": 'triax', - "sim_data_dir": PATH + '/sim_data_backup_2025_01_13_20_58_49/', + "sim_data_dir": PATH + '/sim_data/', "sim_data_file_ext": '.txt', - "sigma_tol": 0.01, }, "inference": { - "Bayes_filter": {"ess_target": 0.3, "scale_cov_with_max": True}, + "Bayes_filter": {"scale_cov_with_max": True}, "sampling": { - "max_num_components": 2, - "n_init": 1, + "max_num_components": 5, "random_state": 0, - "covariance_type": "full", + "slice_sampling": True, } }, "save_fig": 0, + "threads": 1, } ) @@ -46,3 +56,8 @@ most_prob_params = calibration.get_most_prob_params() print(f'Most probable parameter values: {most_prob_params}') + +# Turn off the following if you want to continue the calibration. Always increase the current iteration number before continuing the calibration +calibration.increase_curr_iter() +calibration.save_fig = -1 +calibration.run() \ No newline at end of file diff --git a/tutorials/physics_based/two_particle_collision/Collision.py b/tutorials/physics_based/two_particle_collision/Collision.py index 7ecb4aed..dc7be5ac 100644 --- a/tutorials/physics_based/two_particle_collision/Collision.py +++ b/tutorials/physics_based/two_particle_collision/Collision.py @@ -18,7 +18,10 @@ # import modules import numpy as np + from yade.params import table +from yade import plot + from grainlearning.tools import write_dict_to_file # check if run in batch mode @@ -42,8 +45,7 @@ def add_sim_data(): u = inter.geom.penetrationDepth # check current penetration depth against the target value if u > obs_ctrl_data[-1]: - sim_data['u'].append(u) - sim_data['f'].append(inter.phys.normalForce.norm()) + plot.addData(u=u, f=inter.phys.normalForce.norm()) obs_ctrl_data.pop() if not obs_ctrl_data: data_file_name = f'{description}_sim.txt' @@ -53,7 +55,7 @@ def add_sim_data(): for name in table.__all__: param_data[name] = eval('table.' + name) # write simulation data into a text file - write_dict_to_file(sim_data, data_file_name) + write_dict_to_file(plot.data, data_file_name) write_dict_to_file(param_data, data_param_name) O.pause() @@ -66,9 +68,7 @@ def add_sim_data(): obs_ctrl_data.reverse() # create dictionary to store simulation data -sim_data = {} -sim_data['u'] = [] -sim_data['f'] = [] +plot.plots={'u':'f'} # create materials O.materials.append( diff --git a/tutorials/physics_based/two_particle_collision/collision_calibration.py b/tutorials/physics_based/two_particle_collision/collision_calibration.py index f756de14..81102e03 100644 --- a/tutorials/physics_based/two_particle_collision/collision_calibration.py +++ b/tutorials/physics_based/two_particle_collision/collision_calibration.py @@ -4,11 +4,10 @@ """ import os from math import floor, log -from grainlearning import BayesianCalibration -from grainlearning.dynamic_systems import IODynamicSystem +from grainlearning import BayesianCalibration, IODynamicSystem, IterativeBayesianFilter, SMC, GaussianMixtureModel PATH = os.path.abspath(os.path.dirname(__file__)) -executable = 'yadedaily-batch' +executable = 'yade-batch' yade_script = f'{PATH}/Collision.py' # define the callback function which GrainLearning uses to run YADE simulations @@ -20,7 +19,7 @@ def run_sim(calib): os.system(' '.join([executable, calib.system.param_data_file, yade_script])) param_names = ['E_m', 'nu'] -num_samples = int(5 * len(param_names) * log(len(param_names))) +num_samples = int(6 * len(param_names) * log(len(param_names))) # define the Bayesian Calibration object calibration = BayesianCalibration.from_dict( @@ -37,19 +36,18 @@ def run_sim(calib): "param_min": [7, 0.0], "param_max": [11, 0.5], "param_names": param_names, - "num_samples": 10, + "num_samples": num_samples, "obs_data_file": PATH + '/collision_obs.dat', "obs_names": ['f'], "ctrl_name": 'u', "sim_name": 'collision', "sim_data_dir": PATH + '/sim_data/', "sim_data_file_ext": '.txt', - "sigma_tol": 0.01, }, "inference": { "Bayes_filter": { # scale the covariance matrix with the maximum observation data or not - "scale_cov_with_max": False, + "scale_cov_with_max": True, }, "sampling": { # maximum number of distribution components @@ -64,11 +62,42 @@ def run_sim(calib): }, # flag to save the figures (-1: no, 0: yes but only show the figures , 1: yes) "save_fig": 0, - # number of threads to be used by the external software - "threads": 16, + # number of threads to be used per simulation + "threads": 1, } ) +# calibration = BayesianCalibration( +# num_iter=5, +# error_tol=0.1, +# callback=run_sim, +# save_fig=0, +# threads=1, +# system=IODynamicSystem( +# param_min=[7, 0.0], +# param_max=[11, 0.5], +# param_names=param_names, +# num_samples=num_samples, +# obs_data_file=PATH + '/collision_obs.dat', +# obs_names=['f'], +# ctrl_name='u', +# sim_name='collision', +# sim_data_dir=PATH + '/sim_data/', +# sim_data_file_ext='.txt', +# ), +# inference=IterativeBayesianFilter( +# SMC( +# scale_cov_with_max=True, +# ), +# GaussianMixtureModel( +# max_num_components=1, +# random_state=0, +# covariance_type="full", +# slice_sampling=True, +# ), +# ), +# ) + calibration.run() most_prob_params = calibration.get_most_prob_params() From 0c19f5989f423164ba1c1be65d7c3dcc2551850b Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Tue, 28 Jan 2025 22:44:31 +0100 Subject: [PATCH 07/29] do not maximize figures --- grainlearning/tools.py | 10 ---------- .../triax_calibration_load_and_run.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/grainlearning/tools.py b/grainlearning/tools.py index 02d6d29c..42c02572 100644 --- a/grainlearning/tools.py +++ b/grainlearning/tools.py @@ -411,8 +411,6 @@ def plot_param_stats(fig_name, param_names, means, covs, save_fig=0): plt.xlabel("'Time' step") plt.ylabel(f'Mean of {param_names[i]}') plt.grid(True) - mng = plt.get_current_fig_manager() - mng.full_screen_toggle() if save_fig: plt.savefig(f'{fig_name}_param_means.png') @@ -423,8 +421,6 @@ def plot_param_stats(fig_name, param_names, means, covs, save_fig=0): plt.xlabel("'Time' step") plt.ylabel(f'Coefficient of variation of {param_names[i]}') plt.grid(True) - mng = plt.get_current_fig_manager() - mng.full_screen_toggle() if save_fig: plt.savefig(f'{fig_name}_param_covs.png') @@ -453,8 +449,6 @@ def plot_posterior(fig_name, param_names, param_data, posterior, save_fig=0): plt.xlabel(r'$' + name + '$') plt.ylabel('Posterior probability mass') plt.grid(True) - mng = plt.get_current_fig_manager() - mng.full_screen_toggle() if save_fig: plt.savefig(f'{fig_name}_posterior_{name}.png') @@ -476,8 +470,6 @@ def plot_param_data(fig_name, param_names, param_data_list, save_fig=0): plt.xlabel(r'$' + param_names[j] + '$') plt.ylabel(r'$' + param_names[j + 1] + '$') plt.legend() - mng = plt.get_current_fig_manager() - mng.full_screen_toggle() if save_fig: plt.savefig(f'{fig_name}_param_space.png') @@ -530,8 +522,6 @@ def plot_obs_and_sim(fig_name, ctrl_name, obs_names, ctrl_data, obs_data, sim_da plt.legend() plt.grid(True) - mng = plt.get_current_fig_manager() - mng.full_screen_toggle() if save_fig: plt.savefig(f'{fig_name}_obs_and_sim.png') diff --git a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py index 2208068e..9674d33f 100644 --- a/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py +++ b/tutorials/physics_based/DEM_triaxial_compression/triax_calibration_load_and_run.py @@ -60,4 +60,4 @@ def run_sim(calib): # Turn off the following if you want to continue the calibration. Always increase the current iteration number before continuing the calibration calibration.increase_curr_iter() calibration.save_fig = -1 -calibration.run() \ No newline at end of file +calibration.run() From ee834b802bb75dea414113d9082d1c67568b0143 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Tue, 28 Jan 2025 22:48:45 +0100 Subject: [PATCH 08/29] use extension .txt consistently --- .../two_particle_collision/collision_calibration.py | 4 ++-- .../{collision_obs.dat => collision_obs.txt} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename tutorials/physics_based/two_particle_collision/{collision_obs.dat => collision_obs.txt} (100%) diff --git a/tutorials/physics_based/two_particle_collision/collision_calibration.py b/tutorials/physics_based/two_particle_collision/collision_calibration.py index 81102e03..303bd210 100644 --- a/tutorials/physics_based/two_particle_collision/collision_calibration.py +++ b/tutorials/physics_based/two_particle_collision/collision_calibration.py @@ -37,7 +37,7 @@ def run_sim(calib): "param_max": [11, 0.5], "param_names": param_names, "num_samples": num_samples, - "obs_data_file": PATH + '/collision_obs.dat', + "obs_data_file": PATH + '/collision_obs.txt', "obs_names": ['f'], "ctrl_name": 'u', "sim_name": 'collision', @@ -78,7 +78,7 @@ def run_sim(calib): # param_max=[11, 0.5], # param_names=param_names, # num_samples=num_samples, -# obs_data_file=PATH + '/collision_obs.dat', +# obs_data_file=PATH + '/collision_obs.txt', # obs_names=['f'], # ctrl_name='u', # sim_name='collision', diff --git a/tutorials/physics_based/two_particle_collision/collision_obs.dat b/tutorials/physics_based/two_particle_collision/collision_obs.txt similarity index 100% rename from tutorials/physics_based/two_particle_collision/collision_obs.dat rename to tutorials/physics_based/two_particle_collision/collision_obs.txt From 9f0ef7b6657b7ea8a3faf07c86d7b3c359c36404 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 14:58:25 +0100 Subject: [PATCH 09/29] add option of using predefined shell scripts in /scripts_tools/platform_shells/ --- grainlearning/tools.py | 54 ++++++++----------- scripts_tools/platform_shells/aws/yadeAWS.sh | 42 +++++++++++++++ .../platform_shells/desktop/yadeDesktop.sh | 12 +++++ .../platform_shells/rcg/runSingleYade.sh | 49 +++++++++++++++++ scripts_tools/platform_shells/rcg/yadeRCG.sh | 44 +++++++++++++++ .../two_particle_collision/Collision.py | 2 +- .../collision_calibration.py | 8 ++- 7 files changed, 175 insertions(+), 36 deletions(-) create mode 100755 scripts_tools/platform_shells/aws/yadeAWS.sh create mode 100755 scripts_tools/platform_shells/desktop/yadeDesktop.sh create mode 100755 scripts_tools/platform_shells/rcg/runSingleYade.sh create mode 100755 scripts_tools/platform_shells/rcg/yadeRCG.sh diff --git a/grainlearning/tools.py b/grainlearning/tools.py index 42c02572..6f10b6d3 100644 --- a/grainlearning/tools.py +++ b/grainlearning/tools.py @@ -11,39 +11,27 @@ from scipy.spatial import Voronoi, ConvexHull -# def startSimulations(platform, software, tableName, fileName): -# # platform desktop, aws or rcg # software so far only yade -# argument = tableName + " " + fileName -# if platform == 'desktop': -# # Definition where shell script can be found -# path_to_shell = os.getcwd() + '/platform_shells/desktop' -# if software == 'yade': -# command = 'sh ' + path_to_shell + '/yadeDesktop.sh' + " " + argument -# subprocess.call(command, shell=True) -# else: -# print(Fore.RED + "Chosen 'software' has not been implemented yet. Check 'startSimulations()' in 'tools.py'") -# sys.exit -# -# elif platform == 'aws': -# path_to_shell = os.getcwd() + '/platform_shells/aws' -# if software == 'yade': -# command = 'sh ' + path_to_shell + '/yadeAWS.sh' + " " + argument -# subprocess.call(command, shell=True) -# else: -# print(Fore.RED + "Chosen 'software' has not been implemented yet. Check 'startSimulations()' in 'tools.py'") -# sys.exit -# -# elif platform == 'rcg': -# path_to_shell = os.getcwd() + '/platform_shells/rcg' -# if software == 'yade': -# command = 'sh ' + path_to_shell + '/yadeRCG.sh' + " " + argument -# subprocess.call(command, shell=True) -# else: -# print(Fore.RED + "Chosen 'software' has not been implemented yet. Check 'startSimulations()' in 'tools.py'") -# sys.exit -# else: -# print('Exit code. Hardware for yade simulations not properly defined') -# quit() +def run_yade_from_shell(table_file_name, model_script, path_to_shell=None, platform='desktop'): + # platform desktop, aws or rcg # software so far only yade + if not path_to_shell: + path_to_shell = os.getcwd() + argument = table_file_name + " " + model_script + if platform == 'desktop': + # Definition where shell script can be found + command = 'sh ' + path_to_shell + '/yadeDesktop.sh' + " " + argument + subprocess.call(command, shell=True) + + elif platform == 'aws': + command = 'sh ' + path_to_shell + '/yadeAWS.sh' + " " + argument + subprocess.call(command, shell=True) + + elif platform == 'rcg': + command = 'sh ' + path_to_shell + '/yadeRCG.sh' + " " + argument + subprocess.call(command, shell=True) + else: + RuntimeError( + "Chosen 'platform' has not been implemented yet. Check 'run_yade_from_shell()' in 'tools.py'") + exit() def write_to_table(sim_name, table, names, curr_iter=0, threads=8): diff --git a/scripts_tools/platform_shells/aws/yadeAWS.sh b/scripts_tools/platform_shells/aws/yadeAWS.sh new file mode 100755 index 00000000..e3c40321 --- /dev/null +++ b/scripts_tools/platform_shells/aws/yadeAWS.sh @@ -0,0 +1,42 @@ +#File information and yade version +yadeVersion="/apps/myYade/install/bin/yadeCloud" +#read table and file name +table="$1" +fileName="$2" + +# Get number of lines n (=number of samples) from table file (data is stored in lines 2:n) +n=$(wc -l < "$table") +lines=($(python tableReader.py $table | tr -d '[],')) + +# SLURM information +nodes="1" +cpus="1" +numThreads=$cpus + +slurm="sbatch --nodes=$nodes --cpus-per-task=$cpus" + + +for i in $(seq 2 $n); do + command="$slurm $yadeVersion-batch --job-threads=$numThreads --lines $i $table $fileName" + eval $command +done + + +# number of simulations nS: table file goes from 2:nS+1 example: line 2- line 81 for simulatios 0 to 79. Thus 80 simulations in total +nS=$((n-1)) + +simulationsRunning=true +while $simulationsRunning +do + echo "Simulations not done yet" + sleep 120s + # get number of .npy files + numNPY=`find $pwd -maxdepth 1 -iname '*.npy'| wc -l` + if [ "$nS" -eq "$numNPY" ];then + simulationsRunning=false + fi +done + + +echo "Simulations done" + diff --git a/scripts_tools/platform_shells/desktop/yadeDesktop.sh b/scripts_tools/platform_shells/desktop/yadeDesktop.sh new file mode 100755 index 00000000..fc77f99f --- /dev/null +++ b/scripts_tools/platform_shells/desktop/yadeDesktop.sh @@ -0,0 +1,12 @@ +#!/bin/sh +#File information and yade version +yadeVersion="yade" +table="$1" +fileName="$2" +# Hyperthreading information +cpus="1" +numThreads=$cpus + +command="$yadeVersion-batch --job-threads=$numThreads $table $fileName" +#echo $command +eval $command diff --git a/scripts_tools/platform_shells/rcg/runSingleYade.sh b/scripts_tools/platform_shells/rcg/runSingleYade.sh new file mode 100755 index 00000000..8702dce0 --- /dev/null +++ b/scripts_tools/platform_shells/rcg/runSingleYade.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# +#PBS -l select=1:mem=4gb:ncpus="1" +#PBS -l walltime=12:00:00 +#PBS -k oe + +module --silent load git/2.24.0 + +module --silent load cmake/3.16.4 + +module --silent load python/3.6.3 + +module --silent load gts/gcc8/0.7.6 + +module --silent load eigen/gcc8/3.3.7 + +module --silent load cgal/gcc8/4.8.2 + +module --silent load metis/gcc8/5.1.0 + +module --silent load openblas/gcc8/0.3.7 + +module --silent load loki/gcc8/0.1.7 + +module --silent load gmp/gcc8/6.1.2 + +module --silent load suitesparse/gcc8/4.4.4 + +module --silent load mpfr/gcc8/3.1.5 + +module --silent load vtk/gcc8/6.3.0 + +module --silent load boost/gcc8/1.72-python3.6 + +module --silent load graphviz/gcc8/2.44.0 + +module --silent load openmpi/gcc8/2.1.6 + +module --silent load gl2ps/gcc8/1.3.9 + +export PYTHONPATH=/cm/software/apps/yade/2020.01a/lib/python3.6/site-packages/ + +sleep 3 + +cd /home/ph344/grainLearning + +echo Command is = "${command}" +eval $command + diff --git a/scripts_tools/platform_shells/rcg/yadeRCG.sh b/scripts_tools/platform_shells/rcg/yadeRCG.sh new file mode 100755 index 00000000..346cd4d5 --- /dev/null +++ b/scripts_tools/platform_shells/rcg/yadeRCG.sh @@ -0,0 +1,44 @@ +#!/bin/bash +#yade version +yadeVersion="/home/ph344/myYade/install/bin/yadeCloud" +#read table and file name +table="$1" +fileName="$2" + +# PBS +cpus="1" +numThreads=$cpus +arg="$yadeVersion-batch --job-threads=$numThreads" + +# Get number of lines n from table file (data is stored in lines 2:n) +n=$(wc -l < "$table") + + +for i in $(seq 2 $n); do + command="$arg --lines $i $table $fileName" + echo $command + echo "qsub -v command=\"${command}\" platform_shells/rcg/runSingleYade.sh" > /tmp/jobscript.$$ + + cat /tmp/jobscript.$$ + source /tmp/jobscript.$$ + /bin/rm -f /tmp/jobscript.$$ +done + +# number of simulations nS: table file goes from 2:nS+1 example: line 2- line 81 for simulatios 0 to 79. Thus 80 simulations in total +nS=$((n-1)) + + +simulationsRunning=true +while $simulationsRunning +do + echo "Simulations not done yet" + sleep 120s + # get number of .npy files + numNPY=`find $pwd -maxdepth 1 -iname '*.npy'| wc -l` + if [ "$nS" -eq "$numNPY" ];then + simulationsRunning=false + fi +done + + +echo "Simulations done" diff --git a/tutorials/physics_based/two_particle_collision/Collision.py b/tutorials/physics_based/two_particle_collision/Collision.py index dc7be5ac..7065ee65 100644 --- a/tutorials/physics_based/two_particle_collision/Collision.py +++ b/tutorials/physics_based/two_particle_collision/Collision.py @@ -60,7 +60,7 @@ def add_sim_data(): O.pause() -obs_file = "collision_obs.dat" +obs_file = "collision_obs.txt" # get data for simulation control obs_ctrl_data = np.loadtxt(obs_file)[:, 0].tolist() diff --git a/tutorials/physics_based/two_particle_collision/collision_calibration.py b/tutorials/physics_based/two_particle_collision/collision_calibration.py index 303bd210..5e713b7a 100644 --- a/tutorials/physics_based/two_particle_collision/collision_calibration.py +++ b/tutorials/physics_based/two_particle_collision/collision_calibration.py @@ -5,6 +5,7 @@ import os from math import floor, log from grainlearning import BayesianCalibration, IODynamicSystem, IterativeBayesianFilter, SMC, GaussianMixtureModel +# from grainlearning.tools import run_yade_from_shell PATH = os.path.abspath(os.path.dirname(__file__)) executable = 'yade-batch' @@ -16,6 +17,9 @@ def run_sim(calib): Run the external executable and passes the parameter sample to generate the output file. """ print("*** Running external software YADE ... ***\n") + # alternatively, you can use shell scripts to run YADE (in /scripts_tools/platform_shells/) through the `run_yade_from_shell` function + # os.system('cp ../../../scripts_tools/platform_shells/desktop/yadeDesktop.sh .') + # run_yade_from_shell(calib.system.param_data_file, yade_script) os.system(' '.join([executable, calib.system.param_data_file, yade_script])) param_names = ['E_m', 'nu'] @@ -28,7 +32,7 @@ def run_sim(calib): "num_iter": 5, # error tolerance to stop the calibration "error_tol": 0.1, - # call back function to run YADE + # call back function to run YADE "callback": run_sim, # DEM model as a dynamic system "system": { @@ -95,7 +99,7 @@ def run_sim(calib): # covariance_type="full", # slice_sampling=True, # ), -# ), +# ), # ) calibration.run() From 267d7da3719a6942745967bc816fbd67767ad14d Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 15:13:39 +0100 Subject: [PATCH 10/29] remove --no-update from build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b82f5e9..e11dbecf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock --no-update + poetry lock poetry install -E dev -E rnn -E visuals - name: Verify that we can build the package run: poetry build From 4304d873633fec0841aadfd109bf9691dff6220c Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 15:25:04 +0100 Subject: [PATCH 11/29] try another hack --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e11dbecf..a9101fc8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock + rm poetry.lock poetry install -E dev -E rnn -E visuals - name: Verify that we can build the package run: poetry build From 1f2b27a8bcdfe028a0996d6df65da3acac4761b6 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 15:29:08 +0100 Subject: [PATCH 12/29] try another hack --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9101fc8..a33ee5da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - rm poetry.lock +# poetry lock --no-update poetry install -E dev -E rnn -E visuals - name: Verify that we can build the package run: poetry build From cb274f754230cc70ff91c7ab53ed8d19b7116c70 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 15:32:39 +0100 Subject: [PATCH 13/29] try another hack --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a33ee5da..2e2393e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry -# poetry lock --no-update + poetry shell poetry install -E dev -E rnn -E visuals - name: Verify that we can build the package run: poetry build From 6f8e0c075b1da72ff458f7a33d40b2d9291a9978 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 17:07:58 +0100 Subject: [PATCH 14/29] update build.yml --- .github/workflows/build.yml | 48 +++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e2393e2..72e8f928 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,6 @@ on: jobs: build: - name: Build for (${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: @@ -20,23 +19,30 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v3 - with: - path: ${{ env.pythonLocation }} - key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} - - - name: Install dependencies - run: | - python -m pip install poetry - poetry shell - poetry install -E dev -E rnn -E visuals - - name: Verify that we can build the package - run: poetry build - - name: Test with pytest - run: poetry run pytest + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Poetry + run: python -m pip install poetry + + - name: Cache Poetry dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pypoetry + key: poetry-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} + restore-keys: poetry-${{ runner.os }}-${{ matrix.python-version }}- + + - name: Install dependencies + run: | + poetry install --no-interaction --all-extras + + - name: Verify that we can build the package + run: poetry build + + - name: Test with pytest + run: poetry run pytest From df84da943b4297ffd598b95fdd4e4de04ea984c3 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 17:20:56 +0100 Subject: [PATCH 15/29] went back to old version --- .github/workflows/build.yml | 48 ++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72e8f928..aeefcad9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,7 @@ on: jobs: build: + name: Build for (${{ matrix.python-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: @@ -19,30 +20,23 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - - name: Install Poetry - run: python -m pip install poetry - - - name: Cache Poetry dependencies - uses: actions/cache@v3 - with: - path: ~/.cache/pypoetry - key: poetry-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - restore-keys: poetry-${{ runner.os }}-${{ matrix.python-version }}- - - - name: Install dependencies - run: | - poetry install --no-interaction --all-extras - - - name: Verify that we can build the package - run: poetry build - - - name: Test with pytest - run: poetry run pytest + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/cache@v3 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} + + - name: Install dependencies + run: | + python -m pip install poetry + poetry lock --check + poetry install --all-extras + - name: Verify that we can build the package + run: poetry build + - name: Test with pytest + run: poetry run pytest From e52be65175d3091204f2adf5b68a06ce17d87df9 Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Wed, 29 Jan 2025 17:23:28 +0100 Subject: [PATCH 16/29] remove --no-update --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aeefcad9..8d433b41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock --check + poetry lock poetry install --all-extras - name: Verify that we can build the package run: poetry build From 4308bae01d9d36dbdc7b4b0ee3a05e4b7bfd0aa8 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:21:53 +0100 Subject: [PATCH 17/29] Update Linter.yml --- .github/workflows/Linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 7ba4f8a7..b45db3db 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -25,7 +25,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock --no-update + poetry lock poetry install -E dev -E rnn - name: Check style against standards using prospector run: poetry run prospector From f88574e98f71b91d2de8dabeb11df10803120af0 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:31:44 +0100 Subject: [PATCH 18/29] Update build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d433b41..c0ea949a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry + poetry config keyring.enabled false poetry lock poetry install --all-extras - name: Verify that we can build the package From 3b602d2b60967647c491ee03308ce1ea12e7c027 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:34:41 +0100 Subject: [PATCH 19/29] Update build.yml update to actions/setup-python@v5 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0ea949a..88e55b21 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -34,7 +34,6 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry config keyring.enabled false poetry lock poetry install --all-extras - name: Verify that we can build the package From 14322491adc33729d17cb3ff7b122373e5cbb95c Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:36:11 +0100 Subject: [PATCH 20/29] Update Linter.yml --- .github/workflows/Linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index b45db3db..6eb0c9eb 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.12 - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.12 - uses: actions/cache@v3 From 27b8e09794ef34e9d37e8b827e1468c2d039e862 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:41:59 +0100 Subject: [PATCH 21/29] Update Linter.yml remove poetry lock --- .github/workflows/Linter.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 6eb0c9eb..7d77c883 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -25,7 +25,6 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock poetry install -E dev -E rnn - name: Check style against standards using prospector run: poetry run prospector From 65a35a4c1e69555e6802fd438c5222865bb7e9a8 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:51:56 +0100 Subject: [PATCH 22/29] Update Linter.yml --- .github/workflows/Linter.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 7d77c883..6ff1c751 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -24,7 +24,8 @@ jobs: key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} - name: Install dependencies run: | - python -m pip install poetry + # python -m pip install poetry + curl -sSL https://install.python-poetry.org | python3 - poetry install -E dev -E rnn - name: Check style against standards using prospector run: poetry run prospector From 7304e9d115ae00ce88b88f3bd46b1baa51d4a19c Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 21:56:14 +0100 Subject: [PATCH 23/29] Update Linter.yml --- .github/workflows/Linter.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 6ff1c751..61c81282 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -24,9 +24,8 @@ jobs: key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} - name: Install dependencies run: | - # python -m pip install poetry - curl -sSL https://install.python-poetry.org | python3 - - poetry install -E dev -E rnn + python -m pip install poetry + poetry install -E dev -E rnn -vvv - name: Check style against standards using prospector run: poetry run prospector - name: Check import order From ccfdd7fc5b47d1db6a60b4c4a04dc748a05b09bd Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:01:23 +0100 Subject: [PATCH 24/29] Update Linter.yml --- .github/workflows/Linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 61c81282..4aefb916 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -25,6 +25,7 @@ jobs: - name: Install dependencies run: | python -m pip install poetry + export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://github.com/python-poetry/poetry/issues/3365 poetry install -E dev -E rnn -vvv - name: Check style against standards using prospector run: poetry run prospector From 4dc9744f898841b97642048f15f89716d155d60c Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:06:59 +0100 Subject: [PATCH 25/29] Update Linter.yml --- .github/workflows/Linter.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 4aefb916..191d81d6 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -25,7 +25,8 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://github.com/python-poetry/poetry/issues/3365 + # export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://github.com/python-poetry/poetry/issues/3365 + poetry cache clear --all . poetry install -E dev -E rnn -vvv - name: Check style against standards using prospector run: poetry run prospector From 8e6f0a7349223ca9f640873aff99a13a266e0434 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:17:22 +0100 Subject: [PATCH 26/29] Update pyproject.toml Update h5py to 13.12.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 06d7662b..5e958470 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ pytest = {version = "^6.2.4", optional = true} pytest-cov = {version = "^2.12.1", optional = true} prospector = {version = "^1.7.6", optional = true, extras = ["with_pyroma"]} pyroma = {version = "^4.0", optional = true} -h5py = {version ="^3.7.0", optional = true} +h5py = {version ="^3.12.1", optional = true} wandb = {version ="^0.13.4", optional = true} tensorflow = {version ="^2.10.0", optional = true} ipykernel = {version = "*", optional = true} From ed4bd3fb01d174e206b497803cfd7907597ce5a2 Mon Sep 17 00:00:00 2001 From: Retief Lubbe <96757771+Retiefasaurus@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:27:10 +0100 Subject: [PATCH 27/29] Update Linter.yml Removing cache patch --- .github/workflows/Linter.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 191d81d6..338de757 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -18,15 +18,15 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.12 - - uses: actions/cache@v3 - with: - path: ${{ env.pythonLocation }} - key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} + # - uses: actions/cache@v3 + # with: + # path: ${{ env.pythonLocation }} + # key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} - name: Install dependencies run: | python -m pip install poetry # export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://github.com/python-poetry/poetry/issues/3365 - poetry cache clear --all . + # poetry cache clear --all . poetry install -E dev -E rnn -vvv - name: Check style against standards using prospector run: poetry run prospector From 2949bf52f11ff4819a6ce5986980f735df3e182a Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Fri, 31 Jan 2025 17:10:54 +0100 Subject: [PATCH 28/29] back to master version for github actions --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d433b41..4b82f5e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,8 +34,8 @@ jobs: - name: Install dependencies run: | python -m pip install poetry - poetry lock - poetry install --all-extras + poetry lock --no-update + poetry install -E dev -E rnn -E visuals - name: Verify that we can build the package run: poetry build - name: Test with pytest From 9ec049bd76f45361784c719d8c58a1cd2bcd3ddc Mon Sep 17 00:00:00 2001 From: chyalexcheng Date: Fri, 31 Jan 2025 17:14:27 +0100 Subject: [PATCH 29/29] back to main version for github actions --- .github/workflows/Linter.yml | 15 +++++++-------- .github/workflows/build.yml | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 338de757..7ba4f8a7 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -15,19 +15,18 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v3 with: python-version: 3.12 - # - uses: actions/cache@v3 - # with: - # path: ${{ env.pythonLocation }} - # key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} + - uses: actions/cache@v3 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }} - name: Install dependencies run: | python -m pip install poetry - # export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring #https://github.com/python-poetry/poetry/issues/3365 - # poetry cache clear --all . - poetry install -E dev -E rnn -vvv + poetry lock --no-update + poetry install -E dev -E rnn - name: Check style against standards using prospector run: poetry run prospector - name: Check import order diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3aff33d2..4b82f5e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }}