diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..695f4e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +undodir/ +*npy +*png +*dat +*ipynb +__pycache__ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2cb2f0f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: requirements-txt-fixer + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: debug-statements + - id: check-merge-conflict + +- repo: https://github.com/ambv/black + rev: 22.10.0 + hooks: + - id: black + language: python + types: [python] + args: ["--line-length=88"] + +- repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + args: ["--max-line-length=88", "--extend-ignore=E203, E402", "--doctests"] + +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v14.0.6 + hooks: + - id: clang-format diff --git a/examples/appendix_examples/forward_model/linear_gaussian.py b/examples/appendix_examples/forward_model/linear_gaussian.py new file mode 100644 index 0000000..545d9b5 --- /dev/null +++ b/examples/appendix_examples/forward_model/linear_gaussian.py @@ -0,0 +1,48 @@ +import numpy as np +import os +import shutil + + +class linear_gaussian: + def __init__(self, true_theta=np.arange(1, 4), spatial_res=100): + self.spatial_res = spatial_res + self.xtrain = np.linspace(-1, 1, self.spatial_res) + self.true_theta = true_theta + self.num_parameters = self.true_theta.shape[0] + self.vm = self.compute_vm() + + def compute_vm(self): + vm = np.tile(self.xtrain[:, None], (1, self.num_parameters)) + vm = np.cumprod(vm, axis=1) + return vm + + def compute_prediction(self, theta): + return self.vm @ theta.reshape(-1, 1) + + +def main(): + # Begin User Input + true_theta = np.array([1, 2, 3]) + spatial_res = 100 + # End User Input + model = linear_gaussian(true_theta=true_theta, spatial_res=spatial_res) + prediction = model.compute_prediction(true_theta) + + data = np.zeros((spatial_res, 2)) + data[:, 0] = model.xtrain + data[:, 1] = prediction[:, 0] + + # Create the folders + save_path = "./Output" + + if os.path.exists(save_path) is True: + shutil.rmtree(save_path) + + os.mkdir(save_path) + + data_save_path = os.path.join(save_path, "true_output.npy") + np.save(data_save_path, data) + + +if __name__ == "__main__": + main() diff --git a/examples/appendix_examples/learning_model/config.yaml b/examples/appendix_examples/learning_model/config.yaml new file mode 100644 index 0000000..7f09668 --- /dev/null +++ b/examples/appendix_examples/learning_model/config.yaml @@ -0,0 +1,19 @@ +campaign_id: 1 + +total_samples: 100 +num_samples: 100 + +model_noise_cov: 1.0e-1 +objective_scaling: 1.0e-10 + +# +compute_map: False +compute_mle: False +compute_post: False +compute_true_post: True + +# +compute_identifiability: True +global_num_outer_samples: 10000 +global_num_inner_samples: 5000 +restart_identifiability: False diff --git a/examples/appendix_examples/learning_model/create_new_campaign.sh b/examples/appendix_examples/learning_model/create_new_campaign.sh new file mode 100755 index 0000000..d993a0a --- /dev/null +++ b/examples/appendix_examples/learning_model/create_new_campaign.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +campaign_results_path=$PWD/campaign_results +dname=$campaign_results_path/campaign_${1} + +if [[ -d $dname ]]; then + + read -p "Campaign already exists! Recreate it? [y/yes/Y] : " flag + + if [[ $flag == "y" || $flag == "yes" || $flag == "Y" ]]; then + + rm -rf $dname + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" (Re)created using config.yaml" + + # Generating the data from using config file + python generate_training_data.py ${1} + + else + + echo "Campaign "${1}" was not removed" + + fi + +else + + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" created using config.yaml" + + # Generating the data from using config file + python generate_training_data.py ${1} + + +fi diff --git a/examples/appendix_examples/learning_model/generate_training_data.py b/examples/appendix_examples/learning_model/generate_training_data.py new file mode 100644 index 0000000..b7829a1 --- /dev/null +++ b/examples/appendix_examples/learning_model/generate_training_data.py @@ -0,0 +1,63 @@ +import numpy as np +import matplotlib.pyplot as plt +import os +import sys +import yaml + +sys.path.append("../forward_model/") +from linear_gaussian import linear_gaussian + + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) + +with open("./config.yaml", "r") as config_file: + config_data = yaml.safe_load(config_file) + +campaign_id = config_data["campaign_id"] + +assert campaign_id == int(sys.argv[1]), "Make sure the campaign id match" + +total_samples = config_data["total_samples"] +true_model = linear_gaussian(spatial_res=total_samples) +true_prediction = true_model.compute_prediction(theta=true_model.true_theta) + +# Subsampling +num_sub_samples = config_data["num_samples"] +assert num_sub_samples <= total_samples +sample_idx = np.arange(total_samples) +np.random.shuffle(sample_idx) +sample_idx = sample_idx[:num_sub_samples] + + +# Adding noise +model_noise_cov = config_data["model_noise_cov"] +noise = np.sqrt(model_noise_cov) * (np.random.randn(num_sub_samples)) +training_data = np.zeros((num_sub_samples, 2)) +training_data[:, 0] = true_model.xtrain[sample_idx] +training_data[:, 1] = true_prediction[sample_idx, 0] + noise + + +# Saving data +data_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/training_data.npy".format(campaign_id) +) +sample_idx_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/sample_idx.npy".format(campaign_id) +) +np.save(data_save_path, training_data) +np.save(sample_idx_save_path, sample_idx) + + +fig_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/Figures/training_data.png".format(campaign_id) +) +plt.figure(figsize=(10, 5)) +plt.plot(true_model.xtrain, true_prediction, label="True", c="r") +plt.scatter(training_data[:, 0], training_data[:, 1], label="Data", c="k") +plt.legend(loc="upper left") +plt.tight_layout() +plt.savefig(fig_save_path) +plt.close() diff --git a/examples/appendix_examples/learning_model/run.py b/examples/appendix_examples/learning_model/run.py new file mode 100644 index 0000000..9351249 --- /dev/null +++ b/examples/appendix_examples/learning_model/run.py @@ -0,0 +1,976 @@ +import numpy as np +from mpi4py import MPI +import yaml +import sys +import os +import matplotlib.pyplot as plt +from matplotlib.ticker import MultipleLocator +from scipy.optimize import minimize +from itertools import combinations + +sys.path.append("../../../information_metrics/") +sys.path.append("../forward_model/") +sys.path.append("../../../mcmc/") +from linear_gaussian import linear_gaussian +from compute_identifiability import conditional_mutual_information +from SobolIndex import SobolIndex +from mcmc import adaptive_metropolis_hastings +from mcmc_utils import sub_sample_data + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class learn_linear_gaussian: + def __init__( + self, + config_data, + campaign_path, + prior_mean, + prior_cov, + use_normalization_coeff=False, + normalization_coeff=None, + ): + self.prior_mean = prior_mean + self.prior_cov = prior_cov + + self.prior_cov = prior_cov + self.num_parameters = self.prior_mean.shape[0] + if use_normalization_coeff is True: + assert normalization_coeff is not None, "Normalization coeff is None" + self.normalization_coeff = normalization_coeff + else: + self.normalization_coeff = np.ones(self.num_parameters) + + # Extract configurations + self.campaign_path = campaign_path + self.model_noise_cov = config_data["model_noise_cov"] + + training_data_set = np.load( + os.path.join(self.campaign_path, "training_data.npy") + ) + self.xtrain = training_data_set[:, 0] + self.ytrain = training_data_set[:, 1].reshape(1, -1) + self.sample_idx = np.load(os.path.join(self.campaign_path, "sample_idx.npy")) + + self.model_noise_cov_mat = self.model_noise_cov * np.eye(self.ytrain.shape[1]) + + self.num_data_points = self.ytrain.shape[0] + self.spatial_resolution = self.ytrain.shape[1] + self.objective_scaling = config_data["objective_scaling"] + + # Model identifiability + self.global_num_outer_samples = config_data["global_num_outer_samples"] + self.global_num_inner_samples = config_data["global_num_inner_samples"] + self.restart_identifiability = config_data["restart_identifiability"] + if rank == 0: + log_file_path = os.path.join(self.campaign_path, "log_file.dat") + self.log_file = open(log_file_path, "w") + else: + self.log_file = None + + # Forward model + self.linear_gaussian_model = linear_gaussian( + spatial_res=self.spatial_resolution + ) + self.vm = self.linear_gaussian_model.compute_vm() + self.sub_sampled_vm = self.vm[self.sample_idx, :] + + self.sample_idx_mat = np.zeros( + (self.spatial_resolution, config_data["total_samples"]) + ) + self.sample_idx_mat[np.arange(self.spatial_resolution), self.sample_idx] = 1 + self.forward_model = self.linear_gaussian_model.compute_prediction + + def compute_model_prediction(self, theta, proc_log_file=None): + prediction = self.forward_model(theta)[self.sample_idx, :].T + return prediction + + def sort_prediction(self, prediction): + sorted_idx_id = np.argsort(self.sample_idx) + sorted_prediction = prediction[:, sorted_idx_id] + return sorted_prediction + + def sort_input(self): + sorted_idx_id = np.argsort(self.sample_idx) + return self.xtrain[sorted_idx_id] + + def compute_log_likelihood(self, theta): + """Function computes the log likelihood""" + prediction = self.compute_model_prediction(theta) + error = self.ytrain - prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + log_likelihood = -(0.5 / self.model_noise_cov) * np.sum(error_norm_sq, axis=0) + return log_likelihood + + def compute_mle(self): + """Function computes the mle estimate""" + + def negative_log_likelihood(theta): + objective_function = -self.objective_scaling * self.compute_log_likelihood( + theta + ) + print("MLE objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize( + negative_log_likelihood, + np.random.randn(self.num_parameters), + method="Nelder-Mead", + ) + res = minimize(negative_log_likelihood, res.x) + if res.success is False: + print("Numerical minima not found") + + theta_mle = res.x + + prediction = self.compute_model_prediction(theta=res.x) + prediction_data = np.zeros((self.spatial_resolution, 2)) + prediction_data[:, 0] = self.xtrain + prediction_data[:, 1] = prediction.ravel() + + save_prediction_path = os.path.join(self.campaign_path, "prediction_mle.npy") + save_mle_path = os.path.join(self.campaign_path, "theta_mle.npy") + + np.save(save_prediction_path, prediction_data) + np.save(save_mle_path, theta_mle) + + return theta_mle + + def compute_map(self): + """Function computes the map estimate""" + theta_init = np.random.randn(self.num_parameters) + + def objective_function(theta): + objective_function = ( + -self.objective_scaling * self.compute_unnormalized_posterior(theta) + ) + print("MAP objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize(objective_function, theta_init, method="Nelder-Mead") + res = minimize(objective_function, res.x) + + theta_map = res.x + theta_map_cov = res.hess_inv + + save_map_path = os.path.join(self.campaign_path, "theta_map.npy") + save_map_cov_path = os.path.join(self.campaign_path, "theta_map_cov.npy") + + np.save(save_map_path, theta_map.reshape(-1, 1)) + np.save(save_map_cov_path, theta_map_cov) + + return theta_map.reshape(-1, 1), theta_map_cov + + def compute_true_posterior(self): + evidence_mean, evidence_cov = self.compute_evidence_stats() + correlation = self.prior_cov @ self.sub_sampled_vm.T + delta = self.ytrain.T - evidence_mean + mean_solve = np.linalg.solve(evidence_cov, delta) + post_mean = self.prior_mean + correlation @ mean_solve + cov_solve = np.linalg.solve(evidence_cov, correlation.T) + post_cov = self.prior_cov - correlation @ cov_solve + + # cholesky = np.linalg.cholesky(post_cov + 1e-6*np.eye(self.num_parameters)) + # samples = post_mean + cholesky@np.random.randn(self.num_parameters, 1000) + + return post_mean, post_cov + + def compute_unnormalized_posterior(self, theta): + """Function computes the unnormalized log posterior""" + unnormalized_log_likelihood = self.compute_log_likelihood(theta) + unnormalized_log_prior = self.compute_log_prior(theta) + unnormalized_log_posterior = ( + unnormalized_log_likelihood + unnormalized_log_prior + ) + return unnormalized_log_posterior + + def compute_log_prior(self, theta): + """Function computes the log prior (unnormalized)""" + error = (theta - self.prior_mean.ravel()).reshape(-1, 1) + exp_term_solve = error.T @ np.linalg.solve(self.prior_cov, error) + exp_term = -0.5 * exp_term_solve + return exp_term.item() + + def plot_mle_estimate(self, theta_mle): + prediction = self.compute_model_prediction(theta_mle) + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta, true_theta=True + ) + + sorted_prediction = self.sort_prediction(prediction) + sorted_prediction_true = self.sort_prediction(prediction_true) + + save_fig_path = os.path.join(self.campaign_path, "Figures/prediction_mle.png") + + sorted_xtrain = self.sort_input() + fig, axs = plt.subplots(figsize=(10, 6)) + axs.scatter(self.xtrain, self.ytrain.ravel(), label="Data", c="k", alpha=0.8) + axs.plot( + sorted_xtrain, sorted_prediction.ravel(), label="Prediction", color="r" + ) + axs.plot( + sorted_xtrain, sorted_prediction_true.ravel(), "--", label="True", color="k" + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.legend(framealpha=1.0) + axs.set_title("M.L.E.") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def compute_evidence_stats(self, itheta=None): + """Fuction computes the evidence stats""" + if itheta is None: + evidence_mean = self.compute_model_prediction(theta=self.prior_mean).T + + evidence_cov = ( + self.sub_sampled_vm @ self.prior_cov @ self.sub_sampled_vm.T + + self.model_noise_cov * (self.sample_idx_mat @ self.sample_idx_mat.T) + ) + + else: + evidence_mean = self.compute_model_prediction(theta=self.prior_mean.ravel()) + + vm_selected = self.vm[:, itheta].reshape(-1, 1) + select_prior_cov = (np.diag(self.prior_cov)[itheta]).reshape(1, 1) + evidence_cov = vm_selected @ select_prior_cov @ vm_selected.T + + return evidence_mean, evidence_cov + + def compute_gaussian_entropy(self, cov): + """Function computes the entropy of a gaussian distribution""" + d = cov.shape[0] + return 0.5 * d * np.log(2 * np.pi * np.exp(1)) + 0.5 * np.log( + np.linalg.det(cov) + ) + + def compute_true_mutual_information(self): + """Function comptues the true mutual information""" + evidence_mean, evidence_cov = self.compute_evidence_stats() + evidence_entropy = self.compute_gaussian_entropy(cov=evidence_cov) + print("evidence entropy : {}".format(evidence_entropy)) + + prior_entropy = self.compute_gaussian_entropy(cov=self.prior_cov) + print("prior entropy : {}".format(prior_entropy)) + + correlation = self.prior_cov @ (self.sub_sampled_vm).T + d = self.num_parameters + self.spatial_resolution + joint_mean = np.zeros((d, 1)) + joint_cov = np.zeros((d, d)) + + joint_mean[: self.num_parameters, :] = self.prior_mean + joint_mean[self.num_parameters :, :] = evidence_mean + joint_cov[: self.num_parameters, : self.num_parameters] = self.prior_cov + joint_cov[: self.num_parameters, self.num_parameters :] = correlation + joint_cov[self.num_parameters :, : self.num_parameters] = correlation.T + joint_cov[self.num_parameters :, self.num_parameters :] = evidence_cov + joint_entropy = self.compute_gaussian_entropy(cov=joint_cov) + print("joint entropy : {}".format(joint_entropy)) + mutual_information = prior_entropy - joint_entropy + evidence_entropy + print("Theoretical mutual informaiton : {}".format(mutual_information)) + + def compute_true_individual_parameter_data_mutual_information(self): + """Function computes the true conditional mutual information for + each parameter + """ + # Definitions + individual_mutual_information = np.zeros(self.num_parameters) + evidence_mean, evidence_cov = self.compute_evidence_stats() + + total_corr = self.compute_correlation( + parameter_pair=np.arange(self.num_parameters) + ) + + total_joint_mean, total_joint_cov = self.build_joint( + parameter_mean=self.prior_mean, + parameter_cov=self.prior_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=total_corr, + ) + + total_joint_entropy = self.compute_gaussian_entropy(cov=total_joint_cov) + + for iparameter in range(self.num_parameters): + parameter_pair = np.array([iparameter]) + fixed_parameter_id = np.arange(self.num_parameters) != iparameter + + # selected_parameter_mean = self.prior_mean[parameter_pair].reshape( + # parameter_pair.shape[0], 1 + # ) + selected_parameter_cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + + fixed_parameter_mean = self.prior_mean[fixed_parameter_id].reshape( + sum(fixed_parameter_id), 1 + ) + fixed_parameter_cov = np.diag(np.diag(self.prior_cov)[fixed_parameter_id]) + + prior_entropy = self.compute_gaussian_entropy(cov=selected_parameter_cov) + + correlation = self.compute_correlation(parameter_pair=fixed_parameter_id) + + individual_joint_mean, individual_joint_cov = self.build_joint( + parameter_mean=fixed_parameter_mean, + parameter_cov=fixed_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation, + ) + individual_joint_entropy = self.compute_gaussian_entropy( + cov=individual_joint_cov + ) + individual_mutual_information[iparameter] = ( + prior_entropy - total_joint_entropy + individual_joint_entropy + ) + + print( + "True individual mutual information I(theta_i;Y|theta_not_i) : {}".format( + individual_mutual_information + ) + ) + + def compute_true_pair_parameter_data_mutual_information(self): + """Function computes the true entropy between the parameters given the data""" + evidence_mean, evidence_cov = self.compute_evidence_stats() + parameter_combinations = np.array( + list(combinations(np.arange(self.num_parameters), 2)) + ) + + def get_fixed_parameter_id(x): + parameter_id = np.arange(self.num_parameters) + return ~np.logical_or(parameter_id == x[0], parameter_id == x[1]) + + def get_selected_parameter_joint_id(x, select_id): + parameter_id = np.arange(self.num_parameters) + condition_1 = ~np.logical_or(parameter_id == x[0], parameter_id == x[1]) + condition_2 = parameter_id == select_id + return np.logical_or(condition_1, condition_2) + + for iparameter in parameter_combinations: + # h(y, \theta_k) + fixed_parameter_mean = self.prior_mean[ + get_fixed_parameter_id(iparameter), : + ] + fixed_parameter_cov = np.diag( + np.diag(self.prior_cov)[get_fixed_parameter_id(iparameter)] + ) + correlation_fixed_param_data = self.compute_correlation( + parameter_pair=get_fixed_parameter_id(iparameter) + ) + joint_fixed_param_mean, joint_fixed_param_cov = self.build_joint( + parameter_mean=fixed_parameter_mean, + parameter_cov=fixed_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_fixed_param_data, + ) + joint_fixed_param_entropy = self.compute_gaussian_entropy( + cov=joint_fixed_param_cov + ) + print("h(Y, theta_k) : {}".format(joint_fixed_param_entropy)) + + # h(y, theta_i, theta_k) + selected_parameter_id = get_selected_parameter_joint_id( + iparameter, iparameter[0] + ) + selected_parameter_mean = self.prior_mean[selected_parameter_id, :] + selected_parameter_cov = np.diag( + np.diag(self.prior_cov)[selected_parameter_id] + ) + correlation_pair_param_data = self.compute_correlation( + parameter_pair=selected_parameter_id + ) + + joint_pair_param_mean, joint_pair_param_cov = self.build_joint( + parameter_mean=selected_parameter_mean, + parameter_cov=selected_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_pair_param_data, + ) + joint_pair_param_one_entropy = self.compute_gaussian_entropy( + cov=joint_pair_param_cov + ) + print("h(Y, theta_i, theta_k) : {}".format(joint_pair_param_one_entropy)) + + # h(y, theta_j, theta_k) + selected_parameter_id = get_selected_parameter_joint_id( + iparameter, iparameter[1] + ) + selected_parameter_mean = self.prior_mean[selected_parameter_id, :] + selected_parameter_cov = np.diag( + np.diag(self.prior_cov)[selected_parameter_id] + ) + correlation_pair_param_data = self.compute_correlation( + parameter_pair=selected_parameter_id + ) + + joint_pair_param_mean, joint_pair_param_cov = self.build_joint( + parameter_mean=selected_parameter_mean, + parameter_cov=selected_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_pair_param_data, + ) + joint_pair_param_two_entropy = self.compute_gaussian_entropy( + cov=joint_pair_param_cov + ) + print("h(Y, theta_j, theta_k) : {}".format(joint_pair_param_two_entropy)) + + # h(y, theta_i, theta_j, theta_k) + total_correlation = self.compute_correlation( + parameter_pair=np.arange(self.num_parameters) + ) + + joint_mean, joint_cov = self.build_joint( + parameter_mean=self.prior_mean, + parameter_cov=self.prior_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=total_correlation, + ) + joint_entropy = self.compute_gaussian_entropy(cov=joint_cov) + print("h(Y, theta_i, theta_j, theta_k) : {}".format(joint_entropy)) + conditional_mutual_information = ( + joint_pair_param_one_entropy + + joint_pair_param_two_entropy + - joint_fixed_param_entropy + - joint_entropy + ) + + print( + "I(theta_{};theta_{} | y, theta_[not selected]) : {}".format( + iparameter[0], iparameter[1], conditional_mutual_information + ) + ) + print("------------") + + def compute_correlation(self, parameter_pair): + """Function computes the correlation""" + cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + vm_selected = self.sample_idx_mat @ self.vm[:, parameter_pair] + return cov @ vm_selected.T + + def build_joint( + self, parameter_mean, parameter_cov, evidence_mean, evidence_cov, correlation + ): + """Function builds the joint""" + num_parameters = parameter_mean.shape[0] + dim = num_parameters + evidence_mean.shape[0] + joint_mean = np.zeros((dim, 1)) + joint_cov = np.zeros((dim, dim)) + joint_mean[:num_parameters, :] = parameter_mean + joint_mean[num_parameters:, :] = evidence_mean + joint_cov[:num_parameters, :num_parameters] = parameter_cov + joint_cov[:num_parameters, num_parameters:] = correlation + joint_cov[num_parameters:, :num_parameters] = correlation.T + joint_cov[num_parameters:, num_parameters:] = evidence_cov + + return joint_mean, joint_cov + + def update_prior(self, theta_mean, theta_cov): + self.prior_mean = theta_mean + self.prior_cov = theta_cov + + def compute_esimated_mi(self): + mi_estimator = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + mi_estimator.compute_individual_parameter_data_mutual_information_via_mc( + use_quadrature=True, single_integral_gaussian_quad_pts=100 + ) + + # mi_estimator.compute_posterior_pair_parameter_mutual_information( + # use_quadrature=True, + # single_integral_gaussian_quad_pts=50, + # double_integral_gaussian_quad_pts=50, + # ) + + def compute_sobol_indices(self): + """Function computes the sobol indices""" + + def forward_model(theta): + return self.compute_model_prediction(theta) + + sobol_index = SobolIndex( + forward_model=forward_model, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + model_noise_cov_scalar=0.0, + data_shape=(self.num_data_points, self.spatial_resolution), + write_log_file=True, + save_path=os.path.join(self.campaign_path, "SobolIndex"), + ) + + sobol_index.comp_first_order_sobol_indices() + sobol_index.comp_total_effect_sobol_indices() + + def plot_map_estimate(self, theta_map, theta_map_cov): + num_samples = 1000 + theta = theta_map + np.linalg.cholesky(theta_map_cov) @ np.random.randn( + self.num_parameters, num_samples + ) + + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta, + true_theta=True, + ) + + prediction = np.zeros(self.ytrain.shape + (num_samples,)) + for isample in range(num_samples): + prediction[:, :, isample] = self.compute_model_prediction( + theta=theta[:, isample] + ) + + prediction_mean = np.mean(prediction, axis=-1) + prediction_std = np.std(prediction, axis=-1) + sorted_prediction_mean = self.sort_prediction(prediction=prediction_mean) + sorted_prediction_std = self.sort_prediction(prediction=prediction_std) + sorted_prediction_true = self.sort_prediction(prediction_true) + upper_lim = sorted_prediction_mean + sorted_prediction_std + lower_lim = sorted_prediction_mean - sorted_prediction_std + + sorted_input = self.sort_input() + + save_fig_path = os.path.join(self.campaign_path, "Figures/prediction_map.png") + fig, axs = plt.subplots(figsize=(12, 6)) + axs.scatter( + self.xtrain, self.ytrain.ravel(), c="k", s=30, zorder=-1, label="Data" + ) + axs.plot( + sorted_input, + sorted_prediction_mean.ravel(), + color="r", + label=r"$\mu_{prediction}$", + ) + axs.fill_between( + sorted_input, + upper_lim.ravel(), + lower_lim.ravel(), + ls="--", + lw=2, + alpha=0.3, + color="r", + label=r"$\pm\sigma$", + ) + + axs.plot( + sorted_input, sorted_prediction_true.ravel(), "--", label="True", color="k" + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.legend(framealpha=1.0) + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.set_ylim([-8, 8]) + axs.yaxis.set_minor_locator(MultipleLocator(1)) + axs.xaxis.set_minor_locator(MultipleLocator(0.25)) + # axs.set_title("M.A.P. Prediction") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def compute_mcmc_samples(self, theta_map, theta_map_cov): + """Function computes the mcmc samples""" + + def compute_post(theta): + return self.compute_unnormalized_posterior(theta) + + mcmc_sampler = adaptive_metropolis_hastings( + initial_sample=theta_map.ravel(), + target_log_pdf_evaluator=compute_post, + # num_samples=200000, + # adapt_sample_threshold=10000, + num_samples=20, + adapt_sample_threshold=10, + initial_cov=1e-2 * theta_map_cov, + ) + + mcmc_sampler.compute_mcmc_samples(verbose=True) + + # Compute acceptance rate + ar = mcmc_sampler.compute_acceptance_ratio() + if rank == 0: + print("Acceptance rate: ", ar) + + # Compute the burn in samples + burned_samples = sub_sample_data( + mcmc_sampler.samples, frac_burn=0.5, frac_use=0.7 + ) + + np.save( + os.path.join( + self.campaign_path, "burned_samples_rank_{}_.npy".format(rank) + ), + burned_samples, + ) + + return burned_samples + + def compute_variance_convergence(self): + """Function computes the variance convergence""" + + num_experiments = 10 + # eval_global_num_samples = np.logspace(1, 6, 6).astype(int) + eval_global_num_samples = np.logspace(1, 2, 2).astype(int) + + individual_mi = np.zeros( + (self.num_parameters, len(eval_global_num_samples), num_experiments) + ) + + num_combinations = len(list(combinations(np.arange(self.num_parameters), 2))) + pair_mi = np.zeros( + (num_combinations, eval_global_num_samples.shape[0], num_experiments) + ) + + for iexp in range(num_experiments): + if rank == 0: + print("Experiment: ", iexp) + + for ii, global_num_samples in enumerate(eval_global_num_samples): + + if rank == 0: + print("Global num samples: ", global_num_samples) + + est_cmi = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=global_num_samples, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + individual_estimator = ( + est_cmi.compute_individual_parameter_data_mutual_information_via_mc + ) + + pair_mi_estimator = ( + est_cmi.compute_posterior_pair_parameter_mutual_information + ) + + individual_mi[:, ii, iexp] = individual_estimator( + use_quadrature=True, single_integral_gaussian_quad_pts=50 + ) + + pair_mi[:, ii, iexp] = pair_mi_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=50, + double_integral_gaussian_quad_pts=50, + ) + + if rank == 0: + print("Individual MI: ", individual_mi[:, ii, iexp]) + print("Pair MI: ", pair_mi[:, ii, iexp]) + + if rank == 0: + np.save( + os.path.join( + self.campaign_path, "individual_mi_variance_convergence.npy" + ), + individual_mi, + ) + + np.save( + os.path.join( + self.campaign_path, "pair_mi_variance_convergence.npy" + ), + pair_mi, + ) + + def compute_bias_convergence(self): + """Function computes the bias convergence""" + + num_experiments = 10 + + eval_quad_points = np.array([5, 10]) + + individual_mi = np.zeros( + (self.num_parameters, len(eval_quad_points), num_experiments) + ) + num_combinations = len(list(combinations(np.arange(self.num_parameters), 2))) + pair_mi = np.zeros((num_combinations, len(eval_quad_points), num_experiments)) + + for iexp in range(num_experiments): + if rank == 0: + print("Experiment: ", iexp) + + for ii, quad_points in enumerate(eval_quad_points): + + if rank == 0: + print("Single quad pts: ", quad_points) + print( + "Total Double quad pts" "(after tensorize): ", quad_points**2 + ) + + est_cmi = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=10000, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + individual_estimator = ( + est_cmi.compute_individual_parameter_data_mutual_information_via_mc + ) + + pair_mi_estimator = ( + est_cmi.compute_posterior_pair_parameter_mutual_information + ) + + individual_mi[:, ii, iexp] = individual_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=quad_points, + ) + + pair_mi[:, ii, iexp] = pair_mi_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=quad_points, + double_integral_gaussian_quad_pts=quad_points, + ) + + if rank == 0: + print("Individual MI: ", individual_mi[:, ii, iexp]) + print("Pair MI: ", pair_mi[:, ii, iexp]) + + if rank == 0: + np.save( + os.path.join( + self.campaign_path, "individual_mi_bias_convergence.npy" + ), + individual_mi, + ) + + np.save( + os.path.join(self.campaign_path, "pair_mi_bias_convergence.npy"), + pair_mi, + ) + + def plot_aggregate_post(self, samples): + """Function plots the aggregate posterior""" + agg_prediction = np.zeros((self.ytrain.shape + (samples.shape[0],))) + + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta + ) + + for i, sample in enumerate(samples): + agg_prediction[:, :, i] = self.compute_model_prediction(sample) + + agg_prediction_list = comm.gather(agg_prediction, root=0) + + if rank == 0: + agg_prediction_global = np.concatenate(agg_prediction_list, axis=2) + + prediction_mean = np.mean(agg_prediction_global, axis=-1) + prediction_std = np.std(agg_prediction_global, axis=-1) + sorted_prediction_mean = self.sort_prediction(prediction=prediction_mean) + sorted_prediction_std = self.sort_prediction(prediction=prediction_std) + sorted_prediction_true = self.sort_prediction(prediction_true) + upper_lim = sorted_prediction_mean + sorted_prediction_std + lower_lim = sorted_prediction_mean - sorted_prediction_std + + np.save( + os.path.join(self.campaign_path, "agg_prediction_mean.npy"), + sorted_prediction_mean, + ) + np.save( + os.path.join(self.campaign_path, "agg_prediction_std.npy"), + sorted_prediction_std, + ) + + sorted_input = self.sort_input() + + save_fig_path = os.path.join( + self.campaign_path, "Figures/prediction_agg_post.png" + ) + fig, axs = plt.subplots(figsize=(12, 6)) + axs.scatter( + self.xtrain, self.ytrain.ravel(), c="k", s=30, zorder=-1, label="Data" + ) + axs.plot( + sorted_input, + sorted_prediction_mean.ravel(), + color="r", + label=r"$\mu_{prediction}$", + ) + axs.fill_between( + sorted_input, + upper_lim.ravel(), + lower_lim.ravel(), + ls="--", + lw=2, + alpha=0.3, + color="r", + label=r"$\pm\sigma$", + ) + + axs.plot( + sorted_input, + sorted_prediction_true.ravel(), + "--", + label="True", + color="k", + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.legend(framealpha=1.0, loc="lower right") + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.set_ylim([-8, 8]) + axs.yaxis.set_minor_locator(MultipleLocator(1)) + axs.yaxis.set_major_locator(MultipleLocator(5)) + axs.xaxis.set_minor_locator(MultipleLocator(0.25)) + # axs.set_title("M.A.P. Prediction") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + +def load_configuration_file(config_file_path="./config.yaml"): + """Function loads the configuration file""" + with open(config_file_path, "r") as config_file: + config_data = yaml.safe_load(config_file) + if rank == 0: + print("Loaded %s configuration file" % (config_file_path), flush=True) + return config_data + + +def compute_normalization_coefficient(individual_mi, pair_mi): + num_parameters = len(individual_mi) + alpha = individual_mi / np.sum(individual_mi) + + parameter_comb = np.array(list(combinations(np.arange(num_parameters), 2))) + mat = np.zeros((num_parameters, num_parameters)) + + for ii in range(parameter_comb.shape[0]): + + mat[parameter_comb[ii, 0], parameter_comb[ii, 1]] = pair_mi[ii] + mat[parameter_comb[ii, 1], parameter_comb[ii, 0]] = pair_mi[ii] + + beta = np.sum(mat, axis=1) / np.sum(mat) + gamma_unnormalized = alpha / (alpha + beta) + gamma = gamma_unnormalized / np.sum(gamma_unnormalized) + + return gamma + + +def main(): + prior_mean = np.zeros((3, 1)) + prior_cov = np.eye(3) + + gamma_coeff = compute_normalization_coefficient( + individual_mi=[3.43029824, 2.85712464, 2.59584844], + pair_mi=[-4.20996571e-18, 1.45196126e00, -9.15001408e-18], + ) + + # Load the config data + config_data = load_configuration_file() + + # Campaign path + campaign_path = os.path.join( + os.getcwd(), "campaign_results/campaign_%d" % (config_data["campaign_id"]) + ) + + # Leaning model + + learning_model = learn_linear_gaussian( + config_data=config_data, + campaign_path=campaign_path, + prior_mean=prior_mean, + prior_cov=prior_cov, + use_normalization_coeff=False, + normalization_coeff=gamma_coeff, + ) + + if config_data["compute_mle"]: + theta_mle = learning_model.compute_mle() + elif config_data["compute_map"]: + theta_map, theta_map_cov = learning_model.compute_map() + elif config_data["compute_post"]: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + samples = learning_model.compute_mcmc_samples( + theta_map=theta_map, theta_map_cov=theta_map_cov + ) + + learning_model.plot_aggregate_post(samples) + elif config_data["compute_true_post"]: + theta_post, theta_post_cov = learning_model.compute_true_posterior() + + if "--plotmle" in sys.argv: + theta_mle = np.load(os.path.join(campaign_path, "theta_mle.npy")) + learning_model.plot_mle_estimate( + theta_mle=theta_mle, + ) + + if "--plotmap" in sys.argv: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + + learning_model.plot_map_estimate( + theta_map=theta_map, theta_map_cov=theta_map_cov + ) + + # Identifiability + if config_data["compute_identifiability"]: + + # Update the prior distribution + # theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + # theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + # learning_model.update_prior(theta_mean=theta_map, theta_cov=theta_map_cov) + + # True MI + # learning_model.compute_true_mutual_information() + # learning_model.compute_true_individual_parameter_data_mutual_information() + # learning_model.compute_true_pair_parameter_data_mutual_information() + + # Estimated MI + learning_model.compute_esimated_mi() + + # Sobol indices + # learning_model.compute_sobol_indices() + + # Variance convergence + # learning_model.compute_variance_convergence() + + # Bias convergence + # learning_model.compute_bias_convergence() + + if rank == 0: + learning_model.log_file.close() + + +if __name__ == "__main__": + main() diff --git a/examples/ignition_model/forward_model/.ipynb_checkpoints/sahil-checkpoint.ipynb b/examples/ignition_model/forward_model/.ipynb_checkpoints/sahil-checkpoint.ipynb new file mode 100644 index 0000000..80b6737 --- /dev/null +++ b/examples/ignition_model/forward_model/.ipynb_checkpoints/sahil-checkpoint.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'tecplot'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mtime\u001b[39;00m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmatplotlib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mticker\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m AutoMinorLocator\n\u001b[0;32m---> 18\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtecplot\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mconstant\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n\u001b[1;32m 19\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mscipy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msignal\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m welch, periodogram, find_peaks, butter, filtfilt\n\u001b[1;32m 20\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mcopy\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m deepcopy\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'tecplot'" + ] + } + ], + "source": [ + "import os\n", + "\n", + "import cantera as ct\n", + "import numpy as np\n", + "import traceback\n", + "import glob\n", + "#import tecplot as tec\n", + "\n", + "\n", + "\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as plticker\n", + "import matplotlib.gridspec as gridspec\n", + "import time\n", + "\n", + "from matplotlib.ticker import AutoMinorLocator\n", + "from tecplot.constant import *\n", + "from scipy.signal import welch, periodogram, find_peaks, butter, filtfilt\n", + "from copy import deepcopy\n", + "\n", + "import nicks_funcs\n", + "\n", + "plt.rcParams[\"font.family\"] = \"serif\"\n", + "plt.rcParams[\"mathtext.fontset\"] = \"dejavuserif\"\n", + "plt.rcParams['lines.linewidth'] = 4.0\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "reactor=ct.Solution('./sahil.cti')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " gas:\n", + "\n", + " temperature 300 K\n", + " pressure 4.0856e+05 Pa\n", + " density 6.9195 kg/m^3\n", + " mean mol. weight 42.246 kg/kmol\n", + " phase of matter gas\n", + "\n", + " 1 kg 1 kmol \n", + " --------------- ---------------\n", + " enthalpy -5.0929e+05 -2.1515e+07 J\n", + " internal energy -5.6833e+05 -2.401e+07 J\n", + " entropy 5376.2 2.2712e+05 J/K\n", + " Gibbs function -2.1222e+06 -8.9652e+07 J\n", + " heat capacity c_p 1137.6 48060 J/K\n", + " heat capacity c_v 940.81 39745 J/K\n", + "\n", + " mass frac. Y mole frac. X chem. pot. / RT\n", + " --------------- --------------- ---------------\n", + " C12H26 0.29868 0.074074 -193.26\n", + " O2 0.70132 0.92593 -23.356\n", + " [ +4 minor] 0 0 \n", + "\n" + ] + } + ], + "source": [ + "reactor.TP= 300,101325\n", + "reactor.X={'C12H26':1,'O2':12.5}\n", + "reactor()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " gas:\n", + "\n", + " temperature 4155.1 K\n", + " pressure 4.0856e+05 Pa\n", + " density 0.26978 kg/m^3\n", + " mean mol. weight 22.813 kg/kmol\n", + " phase of matter gas\n", + "\n", + " 1 kg 1 kmol \n", + " --------------- ---------------\n", + " enthalpy -5.0929e+05 -1.1618e+07 J\n", + " internal energy -2.0237e+06 -4.6166e+07 J\n", + " entropy 12734 2.9049e+05 J/K\n", + " Gibbs function -5.3419e+07 -1.2186e+09 J\n", + " heat capacity c_p 2165.9 49411 J/K\n", + " heat capacity c_v 1801.5 41096 J/K\n", + "\n", + " mass frac. Y mole frac. X chem. pot. / RT\n", + " --------------- --------------- ---------------\n", + " C12H26 4.1121e-07 5.5071e-08 -215.34\n", + " O2 1.2994e-10 9.2641e-11 -53.32\n", + " CO 0.58936 0.48 -32.955\n", + " H2O 0.41064 0.52 -37.414\n", + " CO2 2.6557e-06 1.3766e-06 -59.615\n", + " [ +1 minor] 0 0 \n", + "\n" + ] + } + ], + "source": [ + "reactor.equilibrate(\"HP\")\n", + "reactor()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + }, + "vscode": { + "interpreter": { + "hash": "7bb84276cade4b44b83a6f05ca27e44eb21c34d5cdf42c7e55c2010933268c43" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/ignition_model/forward_model/gas_definitions/1S_CH4_MP1.yaml b/examples/ignition_model/forward_model/gas_definitions/1S_CH4_MP1.yaml new file mode 100644 index 0000000..5cf9d4f --- /dev/null +++ b/examples/ignition_model/forward_model/gas_definitions/1S_CH4_MP1.yaml @@ -0,0 +1,110 @@ +description: |- + Single step mechanism + +units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + +phases: +- name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + +species: +- name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 +- name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 +- name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 +- name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 +- name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 + +reactions: +- equation: CH4 + 2 O2 => CO2 + 2 H2O + rate-constant: {A: 1.1e+10, b: 0, Ea: 20000} + orders: {CH4: 1.0, O2: 0.5} diff --git a/examples/ignition_model/forward_model/gas_definitions/1S_CH4_Westbrook.yaml b/examples/ignition_model/forward_model/gas_definitions/1S_CH4_Westbrook.yaml new file mode 100644 index 0000000..c263a8f --- /dev/null +++ b/examples/ignition_model/forward_model/gas_definitions/1S_CH4_Westbrook.yaml @@ -0,0 +1,111 @@ +description: |- + Single step mechanism + +units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + +phases: +- name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + +species: +- name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 +- name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 +- name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 +- name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 +- name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 + +reactions: +- equation: CH4 + 2 O2 => CO2 + 2 H2O + rate-constant: {A: 8.3e+5, b: 0, Ea: 30000} + orders: {CH4: -0.3, O2: 1.3} + negative-orders: True diff --git a/examples/ignition_model/forward_model/gas_definitions/2S_CH4_Westbrook.yaml b/examples/ignition_model/forward_model/gas_definitions/2S_CH4_Westbrook.yaml new file mode 100644 index 0000000..7512279 --- /dev/null +++ b/examples/ignition_model/forward_model/gas_definitions/2S_CH4_Westbrook.yaml @@ -0,0 +1,136 @@ +description: |- + Single step mechanism + +units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + +phases: +- name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2, CO] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + +species: +- name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 +- name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 +- name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 +- name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 +- name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 +- name: CO + composition: {C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.57953347, -6.1035368e-04, 1.01681433e-06, 9.07005884e-10, -9.04424499e-13, + -1.4344086e+04, 3.50840928] + - [2.71518561, 2.06252743e-03, -9.98825771e-07, 2.30053008e-10, -2.03647716e-14, + -1.41518724e+04, 7.81868772] + note: TPIS79 + transport: + model: gas + geometry: linear + well-depth: 98.1 + diameter: 3.65 + polarizability: 1.95 + rotational-relaxation: 1.8 + +reactions: +- equation: CH4 + 1.5 O2 => CO + 2 H2O # Reaction 1 + rate-constant: {A: 2.8e+9, b: 0 , Ea: 48400} + orders: {CH4: -0.3, O2: 1.3} + negative-orders: true +- equation: CO + 0.5 O2 => CO2 # Reaction 2 + rate-constant: {A: 3.98e+14, b: 0 , Ea: 40000.0} + orders: {CO: 1, H2O: 0.5, O2: 0.25} + nonreactant-orders: true +- equation: CO2 => CO + 0.5 O2 # Reaction 3 + rate-constant: {A: 5.0e+8, b: 0, Ea: 40000.0} + orders: {CO2: 1} diff --git a/examples/ignition_model/forward_model/gas_definitions/gri30.yaml b/examples/ignition_model/forward_model/gas_definitions/gri30.yaml new file mode 100644 index 0000000..cbb6a0f --- /dev/null +++ b/examples/ignition_model/forward_model/gas_definitions/gri30.yaml @@ -0,0 +1,1767 @@ +description: |- + GRI-Mech Version 3.0 3/12/99 CHEMKIN-II format + See README30 file at anonymous FTP site unix.sri.com, directory gri; + WorldWideWeb home page http://www.me.berkeley.edu/gri_mech/ or + through http://www.gri.org , under 'Basic Research', + for additional information, contacts, and disclaimer + +units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + +phases: +- name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [H2, H, O, O2, OH, H2O, HO2, H2O2, C, CH, CH2, CH2(S), CH3, CH4, + CO, CO2, HCO, CH2O, CH2OH, CH3O, CH3OH, C2H, C2H2, C2H3, C2H4, C2H5, + C2H6, HCCO, CH2CO, HCCOH, N, NH, NH2, NH3, NNH, NO, NO2, N2O, HNO, CN, + HCN, H2CN, HCNN, HCNO, HOCN, HNCO, NCO, N2, AR, C3H7, C3H8, CH2CHO, + CH3CHO] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + +species: +- name: H2 + composition: {H: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.34433112, 7.98052075e-03, -1.9478151e-05, 2.01572094e-08, -7.37611761e-12, + -917.935173, 0.683010238] + - [3.3372792, -4.94024731e-05, 4.99456778e-07, -1.79566394e-10, 2.00255376e-14, + -950.158922, -3.20502331] + note: TPIS78 + transport: + model: gas + geometry: linear + well-depth: 38.0 + diameter: 2.92 + polarizability: 0.79 + rotational-relaxation: 280.0 +- name: H + composition: {H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.5, 7.05332819e-13, -1.99591964e-15, 2.30081632e-18, -9.27732332e-22, + 2.54736599e+04, -0.446682853] + - [2.50000001, -2.30842973e-11, 1.61561948e-14, -4.73515235e-18, 4.98197357e-22, + 2.54736599e+04, -0.446682914] + note: L7/88 + transport: + model: gas + geometry: atom + well-depth: 145.0 + diameter: 2.05 +- name: O + composition: {O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.1682671, -3.27931884e-03, 6.64306396e-06, -6.12806624e-09, 2.11265971e-12, + 2.91222592e+04, 2.05193346] + - [2.56942078, -8.59741137e-05, 4.19484589e-08, -1.00177799e-11, 1.22833691e-15, + 2.92175791e+04, 4.78433864] + note: L1/90 + transport: + model: gas + geometry: atom + well-depth: 80.0 + diameter: 2.75 +- name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 +- name: OH + composition: {O: 1, H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.99201543, -2.40131752e-03, 4.61793841e-06, -3.88113333e-09, 1.3641147e-12, + 3615.08056, -0.103925458] + - [3.09288767, 5.48429716e-04, 1.26505228e-07, -8.79461556e-11, 1.17412376e-14, + 3858.657, 4.4766961] + note: RUS78 + transport: + model: gas + geometry: linear + well-depth: 80.0 + diameter: 2.75 +- name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 +- name: HO2 + composition: {H: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.30179801, -4.74912051e-03, 2.11582891e-05, -2.42763894e-08, 9.29225124e-12, + 294.80804, 3.71666245] + - [4.0172109, 2.23982013e-03, -6.3365815e-07, 1.1424637e-10, -1.07908535e-14, + 111.856713, 3.78510215] + note: L5/89 + transport: + model: gas + geometry: nonlinear + well-depth: 107.4 + diameter: 3.458 + rotational-relaxation: 1.0 + note: '*' +- name: H2O2 + composition: {H: 2, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.27611269, -5.42822417e-04, 1.67335701e-05, -2.15770813e-08, 8.62454363e-12, + -1.77025821e+04, 3.43505074] + - [4.16500285, 4.90831694e-03, -1.90139225e-06, 3.71185986e-10, -2.87908305e-14, + -1.78617877e+04, 2.91615662] + note: L7/88 + transport: + model: gas + geometry: nonlinear + well-depth: 107.4 + diameter: 3.458 + rotational-relaxation: 3.8 +- name: C + composition: {C: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.55423955, -3.21537724e-04, 7.33792245e-07, -7.32234889e-10, 2.66521446e-13, + 8.54438832e+04, 4.53130848] + - [2.49266888, 4.79889284e-05, -7.2433502e-08, 3.74291029e-11, -4.87277893e-15, + 8.54512953e+04, 4.80150373] + note: L11/88 + transport: + model: gas + geometry: atom + well-depth: 71.4 + diameter: 3.298 + note: '*' +- name: CH + composition: {C: 1, H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.48981665, 3.23835541e-04, -1.68899065e-06, 3.16217327e-09, -1.40609067e-12, + 7.07972934e+04, 2.08401108] + - [2.87846473, 9.70913681e-04, 1.44445655e-07, -1.30687849e-10, 1.76079383e-14, + 7.10124364e+04, 5.48497999] + note: TPIS79 + transport: + model: gas + geometry: linear + well-depth: 80.0 + diameter: 2.75 +- name: CH2 + composition: {C: 1, H: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.76267867, 9.68872143e-04, 2.79489841e-06, -3.85091153e-09, 1.68741719e-12, + 4.60040401e+04, 1.56253185] + - [2.87410113, 3.65639292e-03, -1.40894597e-06, 2.60179549e-10, -1.87727567e-14, + 4.6263604e+04, 6.17119324] + note: LS/93 + transport: + model: gas + geometry: linear + well-depth: 144.0 + diameter: 3.8 +- name: CH2(S) + composition: {C: 1, H: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19860411, -2.36661419e-03, 8.2329622e-06, -6.68815981e-09, 1.94314737e-12, + 5.04968163e+04, -0.769118967] + - [2.29203842, 4.65588637e-03, -2.01191947e-06, 4.17906e-10, -3.39716365e-14, + 5.09259997e+04, 8.62650169] + note: LS/93 + transport: + model: gas + geometry: linear + well-depth: 144.0 + diameter: 3.8 +- name: CH3 + composition: {C: 1, H: 3} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.6735904, 2.01095175e-03, 5.73021856e-06, -6.87117425e-09, 2.54385734e-12, + 1.64449988e+04, 1.60456433] + - [2.28571772, 7.23990037e-03, -2.98714348e-06, 5.95684644e-10, -4.67154394e-14, + 1.67755843e+04, 8.48007179] + note: L11/89 + transport: + model: gas + geometry: linear + well-depth: 144.0 + diameter: 3.8 +- name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 +- name: CO + composition: {C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.57953347, -6.1035368e-04, 1.01681433e-06, 9.07005884e-10, -9.04424499e-13, + -1.4344086e+04, 3.50840928] + - [2.71518561, 2.06252743e-03, -9.98825771e-07, 2.30053008e-10, -2.03647716e-14, + -1.41518724e+04, 7.81868772] + note: TPIS79 + transport: + model: gas + geometry: linear + well-depth: 98.1 + diameter: 3.65 + polarizability: 1.95 + rotational-relaxation: 1.8 +- name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 +- name: HCO + composition: {H: 1, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.22118584, -3.24392532e-03, 1.37799446e-05, -1.33144093e-08, 4.33768865e-12, + 3839.56496, 3.39437243] + - [2.77217438, 4.95695526e-03, -2.48445613e-06, 5.89161778e-10, -5.33508711e-14, + 4011.91815, 9.79834492] + note: L12/89 + transport: + model: gas + geometry: nonlinear + well-depth: 498.0 + diameter: 3.59 +- name: CH2O + composition: {H: 2, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.79372315, -9.90833369e-03, 3.73220008e-05, -3.79285261e-08, 1.31772652e-11, + -1.43089567e+04, 0.6028129] + - [1.76069008, 9.20000082e-03, -4.42258813e-06, 1.00641212e-09, -8.8385564e-14, + -1.39958323e+04, 13.656323] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 498.0 + diameter: 3.59 + rotational-relaxation: 2.0 +- name: CH2OH + composition: {C: 1, H: 3, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.86388918, 5.59672304e-03, 5.93271791e-06, -1.04532012e-08, 4.36967278e-12, + -3193.91367, 5.47302243] + - [3.69266569, 8.64576797e-03, -3.7510112e-06, 7.87234636e-10, -6.48554201e-14, + -3242.50627, 5.81043215] + note: GUNL93 + transport: + model: gas + geometry: nonlinear + well-depth: 417.0 + diameter: 3.69 + dipole: 1.7 + rotational-relaxation: 2.0 +- name: CH3O + composition: {C: 1, H: 3, O: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 3000.0] + data: + - [2.106204, 7.216595e-03, 5.338472e-06, -7.377636e-09, 2.07561e-12, + 978.6011, 13.152177] + - [3.770799, 7.871497e-03, -2.656384e-06, 3.944431e-10, -2.112616e-14, + 127.83252, 2.929575] + note: '121686' + transport: + model: gas + geometry: nonlinear + well-depth: 417.0 + diameter: 3.69 + dipole: 1.7 + rotational-relaxation: 2.0 +- name: CH3OH + composition: {C: 1, H: 4, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.71539582, -0.0152309129, 6.52441155e-05, -7.10806889e-08, 2.61352698e-11, + -2.56427656e+04, -1.50409823] + - [1.78970791, 0.0140938292, -6.36500835e-06, 1.38171085e-09, -1.1706022e-13, + -2.53748747e+04, 14.5023623] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 481.8 + diameter: 3.626 + rotational-relaxation: 1.0 + note: SVE +- name: C2H + composition: {C: 2, H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.88965733, 0.0134099611, -2.84769501e-05, 2.94791045e-08, -1.09331511e-11, + 6.68393932e+04, 6.22296438] + - [3.16780652, 4.75221902e-03, -1.83787077e-06, 3.04190252e-10, -1.7723277e-14, + 6.7121065e+04, 6.63589475] + note: L1/91 + transport: + model: gas + geometry: linear + well-depth: 209.0 + diameter: 4.1 + rotational-relaxation: 2.5 +- name: C2H2 + composition: {C: 2, H: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [0.808681094, 0.0233615629, -3.55171815e-05, 2.80152437e-08, -8.50072974e-12, + 2.64289807e+04, 13.9397051] + - [4.14756964, 5.96166664e-03, -2.37294852e-06, 4.67412171e-10, -3.61235213e-14, + 2.59359992e+04, -1.23028121] + note: L1/91 + transport: + model: gas + geometry: linear + well-depth: 209.0 + diameter: 4.1 + rotational-relaxation: 2.5 +- name: C2H3 + composition: {C: 2, H: 3} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.21246645, 1.51479162e-03, 2.59209412e-05, -3.57657847e-08, 1.47150873e-11, + 3.48598468e+04, 8.51054025] + - [3.016724, 0.0103302292, -4.68082349e-06, 1.01763288e-09, -8.62607041e-14, + 3.46128739e+04, 7.78732378] + note: L2/92 + transport: + model: gas + geometry: nonlinear + well-depth: 209.0 + diameter: 4.1 + rotational-relaxation: 1.0 + note: '*' +- name: C2H4 + composition: {C: 2, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.95920148, -7.57052247e-03, 5.70990292e-05, -6.91588753e-08, 2.69884373e-11, + 5089.77593, 4.09733096] + - [2.03611116, 0.0146454151, -6.71077915e-06, 1.47222923e-09, -1.25706061e-13, + 4939.88614, 10.3053693] + note: L1/91 + transport: + model: gas + geometry: nonlinear + well-depth: 280.8 + diameter: 3.971 + rotational-relaxation: 1.5 +- name: C2H5 + composition: {C: 2, H: 5} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.30646568, -4.18658892e-03, 4.97142807e-05, -5.99126606e-08, 2.30509004e-11, + 1.28416265e+04, 4.70720924] + - [1.95465642, 0.0173972722, -7.98206668e-06, 1.75217689e-09, -1.49641576e-13, + 1.285752e+04, 13.4624343] + note: L12/92 + transport: + model: gas + geometry: nonlinear + well-depth: 252.3 + diameter: 4.302 + rotational-relaxation: 1.5 +- name: C2H6 + composition: {C: 2, H: 6} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.29142492, -5.5015427e-03, 5.99438288e-05, -7.08466285e-08, 2.68685771e-11, + -1.15222055e+04, 2.66682316] + - [1.0718815, 0.0216852677, -1.00256067e-05, 2.21412001e-09, -1.9000289e-13, + -1.14263932e+04, 15.1156107] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 252.3 + diameter: 4.302 + rotational-relaxation: 1.5 +- name: HCCO + composition: {H: 1, C: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 4000.0] + data: + - [2.2517214, 0.017655021, -2.3729101e-05, 1.7275759e-08, -5.0664811e-12, + 2.0059449e+04, 12.490417] + - [5.6282058, 4.0853401e-03, -1.5934547e-06, 2.8626052e-10, -1.9407832e-14, + 1.9327215e+04, -3.9302595] + note: SRIC91 + transport: + model: gas + geometry: nonlinear + well-depth: 150.0 + diameter: 2.5 + rotational-relaxation: 1.0 + note: '*' +- name: CH2CO + composition: {C: 2, H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.1358363, 0.0181188721, -1.73947474e-05, 9.34397568e-09, -2.01457615e-12, + -7042.91804, 12.215648] + - [4.51129732, 9.00359745e-03, -4.16939635e-06, 9.23345882e-10, -7.94838201e-14, + -7551.05311, 0.632247205] + note: L5/90 + transport: + model: gas + geometry: nonlinear + well-depth: 436.0 + diameter: 3.97 + rotational-relaxation: 2.0 +- name: HCCOH + composition: {C: 2, O: 1, H: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [1.2423733, 0.031072201, -5.0866864e-05, 4.3137131e-08, -1.4014594e-11, + 8031.6143, 13.874319] + - [5.9238291, 6.79236e-03, -2.5658564e-06, 4.4987841e-10, -2.9940101e-14, + 7264.626, -7.6017742] + note: SRI91 + transport: + model: gas + geometry: nonlinear + well-depth: 436.0 + diameter: 3.97 + rotational-relaxation: 2.0 +- name: N + composition: {N: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [2.5, 0.0, 0.0, 0.0, 0.0, 5.6104637e+04, 4.1939087] + - [2.4159429, 1.7489065e-04, -1.1902369e-07, 3.0226245e-11, -2.0360982e-15, + 5.6133773e+04, 4.6496096] + note: L6/88 + transport: + model: gas + geometry: atom + well-depth: 71.4 + diameter: 3.298 + note: '*' +- name: NH + composition: {N: 1, H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [3.4929085, 3.1179198e-04, -1.4890484e-06, 2.4816442e-09, -1.0356967e-12, + 4.1880629e+04, 1.8483278] + - [2.7836928, 1.329843e-03, -4.2478047e-07, 7.8348501e-11, -5.504447e-15, + 4.2120848e+04, 5.7407799] + note: And94 + transport: + model: gas + geometry: linear + well-depth: 80.0 + diameter: 2.65 + rotational-relaxation: 4.0 +- name: NH2 + composition: {N: 1, H: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.2040029, -2.1061385e-03, 7.1068348e-06, -5.6115197e-09, 1.6440717e-12, + 2.188591e+04, -0.14184248] + - [2.8347421, 3.2073082e-03, -9.3390804e-07, 1.3702953e-10, -7.9206144e-15, + 2.2171957e+04, 6.5204163] + note: And89 + transport: + model: gas + geometry: nonlinear + well-depth: 80.0 + diameter: 2.65 + polarizability: 2.26 + rotational-relaxation: 4.0 +- name: NH3 + composition: {N: 1, H: 3} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.2860274, -4.660523e-03, 2.1718513e-05, -2.2808887e-08, 8.2638046e-12, + -6741.7285, -0.62537277] + - [2.6344521, 5.666256e-03, -1.7278676e-06, 2.3867161e-10, -1.2578786e-14, + -6544.6958, 6.5662928] + note: J6/77 + transport: + model: gas + geometry: nonlinear + well-depth: 481.0 + diameter: 2.92 + dipole: 1.47 + rotational-relaxation: 10.0 +- name: NNH + composition: {N: 2, H: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.3446927, -4.8497072e-03, 2.0059459e-05, -2.1726464e-08, 7.9469539e-12, + 2.8791973e+04, 2.977941] + - [3.7667544, 2.8915082e-03, -1.041662e-06, 1.6842594e-10, -1.0091896e-14, + 2.8650697e+04, 4.4705067] + note: T07/93 + transport: + model: gas + geometry: nonlinear + well-depth: 71.4 + diameter: 3.798 + rotational-relaxation: 1.0 + note: '*' +- name: NO + composition: {N: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.2184763, -4.638976e-03, 1.1041022e-05, -9.3361354e-09, 2.803577e-12, + 9844.623, 2.2808464] + - [3.2606056, 1.1911043e-03, -4.2917048e-07, 6.9457669e-11, -4.0336099e-15, + 9920.9746, 6.3693027] + note: RUS78 + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 +- name: NO2 + composition: {N: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [3.9440312, -1.585429e-03, 1.6657812e-05, -2.0475426e-08, 7.8350564e-12, + 2896.6179, 6.3119917] + - [4.8847542, 2.1723956e-03, -8.2806906e-07, 1.574751e-10, -1.0510895e-14, + 2316.4983, -0.11741695] + note: L7/88 + transport: + model: gas + geometry: nonlinear + well-depth: 200.0 + diameter: 3.5 + rotational-relaxation: 1.0 + note: '*' +- name: N2O + composition: {N: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [2.2571502, 0.011304728, -1.3671319e-05, 9.6819806e-09, -2.9307182e-12, + 8741.7744, 10.757992] + - [4.8230729, 2.6270251e-03, -9.5850874e-07, 1.6000712e-10, -9.7752303e-15, + 8073.4048, -2.2017207] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 232.4 + diameter: 3.828 + rotational-relaxation: 1.0 + note: '*' +- name: HNO + composition: {H: 1, N: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.5334916, -5.6696171e-03, 1.8473207e-05, -1.7137094e-08, 5.5454573e-12, + 1.1548297e+04, 1.7498417] + - [2.9792509, 3.4944059e-03, -7.8549778e-07, 5.7479594e-11, -1.9335916e-16, + 1.1750582e+04, 8.6063728] + note: And93 + transport: + model: gas + geometry: nonlinear + well-depth: 116.7 + diameter: 3.492 + rotational-relaxation: 1.0 + note: '*' +- name: CN + composition: {C: 1, N: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [3.6129351, -9.5551327e-04, 2.1442977e-06, -3.1516323e-10, -4.6430356e-13, + 5.170834e+04, 3.9804995] + - [3.7459805, 4.3450775e-05, 2.9705984e-07, -6.8651806e-11, 4.4134173e-15, + 5.1536188e+04, 2.7867601] + note: HBH92 + transport: + model: gas + geometry: linear + well-depth: 75.0 + diameter: 3.856 + rotational-relaxation: 1.0 + note: OIS +- name: HCN + composition: {H: 1, C: 1, N: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [2.2589886, 0.01005117, -1.3351763e-05, 1.0092349e-08, -3.0089028e-12, + 1.4712633e+04, 8.9164419] + - [3.8022392, 3.1464228e-03, -1.0632185e-06, 1.6619757e-10, -9.799757e-15, + 1.4407292e+04, 1.5754601] + note: GRI/98 + transport: + model: gas + geometry: linear + well-depth: 569.0 + diameter: 3.63 + rotational-relaxation: 1.0 + note: OIS +- name: H2CN + composition: {H: 2, C: 1, N: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 4000.0] + data: + - [2.851661, 5.6952331e-03, 1.07114e-06, -1.622612e-09, -2.3511081e-13, + 2.863782e+04, 8.9927511] + - [5.209703, 2.9692911e-03, -2.8555891e-07, -1.63555e-10, 3.0432589e-14, + 2.7677109e+04, -4.444478] + note: '41687' + transport: + model: gas + geometry: linear + well-depth: 569.0 + diameter: 3.63 + rotational-relaxation: 1.0 + note: os/jm +- name: HCNN + composition: {C: 1, N: 2, H: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [2.5243194, 0.015960619, -1.8816354e-05, 1.212554e-08, -3.2357378e-12, + 5.4261984e+04, 11.67587] + - [5.8946362, 3.9895959e-03, -1.598238e-06, 2.9249395e-10, -2.0094686e-14, + 5.3452941e+04, -5.1030502] + note: SRI/94 + transport: + model: gas + geometry: nonlinear + well-depth: 150.0 + diameter: 2.5 + rotational-relaxation: 1.0 + note: '*' +- name: HCNO + composition: {H: 1, N: 1, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1382.0, 5000.0] + data: + - [2.64727989, 0.0127505342, -1.04794236e-05, 4.41432836e-09, -7.57521466e-13, + 1.92990252e+04, 10.7332972] + - [6.59860456, 3.02778626e-03, -1.07704346e-06, 1.71666528e-10, -1.01439391e-14, + 1.79661339e+04, -10.3306599] + note: BDEA94 + transport: + model: gas + geometry: nonlinear + well-depth: 232.4 + diameter: 3.828 + rotational-relaxation: 1.0 + note: JAM +- name: HOCN + composition: {H: 1, N: 1, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1368.0, 5000.0] + data: + - [3.78604952, 6.88667922e-03, -3.21487864e-06, 5.17195767e-10, 1.19360788e-14, + -2826.984, 5.63292162] + - [5.89784885, 3.16789393e-03, -1.11801064e-06, 1.77243144e-10, -1.04339177e-14, + -3706.53331, -6.18167825] + note: BDEA94 + transport: + model: gas + geometry: nonlinear + well-depth: 232.4 + diameter: 3.828 + rotational-relaxation: 1.0 + note: JAM +- name: HNCO + composition: {H: 1, N: 1, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1478.0, 5000.0] + data: + - [3.63096317, 7.30282357e-03, -2.28050003e-06, -6.61271298e-10, 3.62235752e-13, + -1.55873636e+04, 6.19457727] + - [6.22395134, 3.17864004e-03, -1.09378755e-06, 1.70735163e-10, -9.95021955e-15, + -1.66599344e+04, -8.38224741] + note: BDEA94 + transport: + model: gas + geometry: nonlinear + well-depth: 232.4 + diameter: 3.828 + rotational-relaxation: 1.0 + note: OIS +- name: NCO + composition: {N: 1, C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [2.8269308, 8.8051688e-03, -8.3866134e-06, 4.8016964e-09, -1.3313595e-12, + 1.4682477e+04, 9.5504646] + - [5.1521845, 2.3051761e-03, -8.8033153e-07, 1.4789098e-10, -9.0977996e-15, + 1.4004123e+04, -2.544266] + note: EA93 + transport: + model: gas + geometry: linear + well-depth: 232.4 + diameter: 3.828 + rotational-relaxation: 1.0 + note: OIS +- name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 +- name: AR + composition: {Ar: 1} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [2.5, 0.0, 0.0, 0.0, 0.0, -745.375, 4.366] + - [2.5, 0.0, 0.0, 0.0, 0.0, -745.375, 4.366] + note: '120186' + transport: + model: gas + geometry: atom + well-depth: 136.5 + diameter: 3.33 +- name: C3H7 + composition: {C: 3, H: 7} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [1.0515518, 0.02599198, 2.380054e-06, -1.9609569e-08, 9.373247e-12, + 1.0631863e+04, 21.122559] + - [7.7026987, 0.016044203, -5.283322e-06, 7.629859e-10, -3.9392284e-14, + 8298.4336, -15.48018] + note: L9/84 + transport: + model: gas + geometry: nonlinear + well-depth: 266.8 + diameter: 4.982 + rotational-relaxation: 1.0 +- name: C3H8 + composition: {C: 3, H: 8} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [0.93355381, 0.026424579, 6.1059727e-06, -2.1977499e-08, 9.5149253e-12, + -1.395852e+04, 19.201691] + - [7.5341368, 0.018872239, -6.2718491e-06, 9.1475649e-10, -4.7838069e-14, + -1.6467516e+04, -17.892349] + note: L4/85 + transport: + model: gas + geometry: nonlinear + well-depth: 266.8 + diameter: 4.982 + rotational-relaxation: 1.0 +- name: CH2CHO + composition: {O: 1, H: 3, C: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.409062, 0.010738574, 1.891492e-06, -7.158583e-09, 2.867385e-12, + 1521.4766, 9.55829] + - [5.97567, 8.130591e-03, -2.743624e-06, 4.070304e-10, -2.176017e-14, + 490.3218, -5.045251] + note: SAND86 + transport: + model: gas + geometry: nonlinear + well-depth: 436.0 + diameter: 3.97 + rotational-relaxation: 2.0 +- name: CH3CHO + composition: {C: 2, H: 4, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 6000.0] + data: + - [4.7294595, -3.1932858e-03, 4.7534921e-05, -5.7458611e-08, 2.1931112e-11, + -2.1572878e+04, 4.1030159] + - [5.4041108, 0.011723059, -4.2263137e-06, 6.8372451e-10, -4.0984863e-14, + -2.2593122e+04, -3.4807917] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 436.0 + diameter: 3.97 + rotational-relaxation: 2.0 + +reactions: +- equation: 2 O + M <=> O2 + M # Reaction 1 + type: three-body + rate-constant: {A: 1.2e+17, b: -1.0, Ea: 0.0} + efficiencies: {H2: 2.4, H2O: 15.4, CH4: 2.0, CO: 1.75, CO2: 3.6, C2H6: 3.0, + AR: 0.83} +- equation: O + H + M <=> OH + M # Reaction 2 + type: three-body + rate-constant: {A: 5.0e+17, b: -1.0, Ea: 0.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: O + H2 <=> H + OH # Reaction 3 + rate-constant: {A: 3.87e+04, b: 2.7, Ea: 6260.0} +- equation: O + HO2 <=> OH + O2 # Reaction 4 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: O + H2O2 <=> OH + HO2 # Reaction 5 + rate-constant: {A: 9.63e+06, b: 2.0, Ea: 4000.0} +- equation: O + CH <=> H + CO # Reaction 6 + rate-constant: {A: 5.7e+13, b: 0.0, Ea: 0.0} +- equation: O + CH2 <=> H + HCO # Reaction 7 + rate-constant: {A: 8.0e+13, b: 0.0, Ea: 0.0} +- equation: O + CH2(S) <=> H2 + CO # Reaction 8 + rate-constant: {A: 1.5e+13, b: 0.0, Ea: 0.0} +- equation: O + CH2(S) <=> H + HCO # Reaction 9 + rate-constant: {A: 1.5e+13, b: 0.0, Ea: 0.0} +- equation: O + CH3 <=> H + CH2O # Reaction 10 + rate-constant: {A: 5.06e+13, b: 0.0, Ea: 0.0} +- equation: O + CH4 <=> OH + CH3 # Reaction 11 + rate-constant: {A: 1.02e+09, b: 1.5, Ea: 8600.0} +- equation: O + CO (+M) <=> CO2 (+M) # Reaction 12 + type: falloff + low-P-rate-constant: {A: 6.02e+14, b: 0.0, Ea: 3000.0} + high-P-rate-constant: {A: 1.8e+10, b: 0.0, Ea: 2385.0} + efficiencies: {H2: 2.0, O2: 6.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 3.5, + C2H6: 3.0, AR: 0.5} +- equation: O + HCO <=> OH + CO # Reaction 13 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: O + HCO <=> H + CO2 # Reaction 14 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: O + CH2O <=> OH + HCO # Reaction 15 + rate-constant: {A: 3.9e+13, b: 0.0, Ea: 3540.0} +- equation: O + CH2OH <=> OH + CH2O # Reaction 16 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 0.0} +- equation: O + CH3O <=> OH + CH2O # Reaction 17 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 0.0} +- equation: O + CH3OH <=> OH + CH2OH # Reaction 18 + rate-constant: {A: 3.88e+05, b: 2.5, Ea: 3100.0} +- equation: O + CH3OH <=> OH + CH3O # Reaction 19 + rate-constant: {A: 1.3e+05, b: 2.5, Ea: 5000.0} +- equation: O + C2H <=> CH + CO # Reaction 20 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: O + C2H2 <=> H + HCCO # Reaction 21 + rate-constant: {A: 1.35e+07, b: 2.0, Ea: 1900.0} +- equation: O + C2H2 <=> OH + C2H # Reaction 22 + rate-constant: {A: 4.6e+19, b: -1.41, Ea: 2.895e+04} +- equation: O + C2H2 <=> CO + CH2 # Reaction 23 + rate-constant: {A: 6.94e+06, b: 2.0, Ea: 1900.0} +- equation: O + C2H3 <=> H + CH2CO # Reaction 24 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: O + C2H4 <=> CH3 + HCO # Reaction 25 + rate-constant: {A: 1.25e+07, b: 1.83, Ea: 220.0} +- equation: O + C2H5 <=> CH3 + CH2O # Reaction 26 + rate-constant: {A: 2.24e+13, b: 0.0, Ea: 0.0} +- equation: O + C2H6 <=> OH + C2H5 # Reaction 27 + rate-constant: {A: 8.98e+07, b: 1.92, Ea: 5690.0} +- equation: O + HCCO <=> H + 2 CO # Reaction 28 + rate-constant: {A: 1.0e+14, b: 0.0, Ea: 0.0} +- equation: O + CH2CO <=> OH + HCCO # Reaction 29 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 8000.0} +- equation: O + CH2CO <=> CH2 + CO2 # Reaction 30 + rate-constant: {A: 1.75e+12, b: 0.0, Ea: 1350.0} +- equation: O2 + CO <=> O + CO2 # Reaction 31 + rate-constant: {A: 2.5e+12, b: 0.0, Ea: 4.78e+04} +- equation: O2 + CH2O <=> HO2 + HCO # Reaction 32 + rate-constant: {A: 1.0e+14, b: 0.0, Ea: 4.0e+04} +- equation: H + O2 + M <=> HO2 + M # Reaction 33 + type: three-body + rate-constant: {A: 2.8e+18, b: -0.86, Ea: 0.0} + efficiencies: {O2: 0.0, H2O: 0.0, CO: 0.75, CO2: 1.5, C2H6: 1.5, N2: 0.0, + AR: 0.0} +- equation: H + 2 O2 <=> HO2 + O2 # Reaction 34 + rate-constant: {A: 2.08e+19, b: -1.24, Ea: 0.0} +- equation: H + O2 + H2O <=> HO2 + H2O # Reaction 35 + rate-constant: {A: 1.126e+19, b: -0.76, Ea: 0.0} +- equation: H + O2 + N2 <=> HO2 + N2 # Reaction 36 + rate-constant: {A: 2.6e+19, b: -1.24, Ea: 0.0} +- equation: H + O2 + AR <=> HO2 + AR # Reaction 37 + rate-constant: {A: 7.0e+17, b: -0.8, Ea: 0.0} +- equation: H + O2 <=> O + OH # Reaction 38 + rate-constant: {A: 2.65e+16, b: -0.6707, Ea: 1.7041e+04} +- equation: 2 H + M <=> H2 + M # Reaction 39 + type: three-body + rate-constant: {A: 1.0e+18, b: -1.0, Ea: 0.0} + efficiencies: {H2: 0.0, H2O: 0.0, CH4: 2.0, CO2: 0.0, C2H6: 3.0, AR: 0.63} +- equation: 2 H + H2 <=> 2 H2 # Reaction 40 + rate-constant: {A: 9.0e+16, b: -0.6, Ea: 0.0} +- equation: 2 H + H2O <=> H2 + H2O # Reaction 41 + rate-constant: {A: 6.0e+19, b: -1.25, Ea: 0.0} +- equation: 2 H + CO2 <=> H2 + CO2 # Reaction 42 + rate-constant: {A: 5.5e+20, b: -2.0, Ea: 0.0} +- equation: H + OH + M <=> H2O + M # Reaction 43 + type: three-body + rate-constant: {A: 2.2e+22, b: -2.0, Ea: 0.0} + efficiencies: {H2: 0.73, H2O: 3.65, CH4: 2.0, C2H6: 3.0, AR: 0.38} +- equation: H + HO2 <=> O + H2O # Reaction 44 + rate-constant: {A: 3.97e+12, b: 0.0, Ea: 671.0} +- equation: H + HO2 <=> O2 + H2 # Reaction 45 + rate-constant: {A: 4.48e+13, b: 0.0, Ea: 1068.0} +- equation: H + HO2 <=> 2 OH # Reaction 46 + rate-constant: {A: 8.4e+13, b: 0.0, Ea: 635.0} +- equation: H + H2O2 <=> HO2 + H2 # Reaction 47 + rate-constant: {A: 1.21e+07, b: 2.0, Ea: 5200.0} +- equation: H + H2O2 <=> OH + H2O # Reaction 48 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 3600.0} +- equation: H + CH <=> C + H2 # Reaction 49 + rate-constant: {A: 1.65e+14, b: 0.0, Ea: 0.0} +- equation: H + CH2 (+M) <=> CH3 (+M) # Reaction 50 + type: falloff + low-P-rate-constant: {A: 1.04e+26, b: -2.76, Ea: 1600.0} + high-P-rate-constant: {A: 6.0e+14, b: 0.0, Ea: 0.0} + Troe: {A: 0.562, T3: 91.0, T1: 5836.0, T2: 8552.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + CH2(S) <=> CH + H2 # Reaction 51 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: H + CH3 (+M) <=> CH4 (+M) # Reaction 52 + type: falloff + low-P-rate-constant: {A: 2.62e+33, b: -4.76, Ea: 2440.0} + high-P-rate-constant: {A: 1.39e+16, b: -0.534, Ea: 536.0} + Troe: {A: 0.783, T3: 74.0, T1: 2941.0, T2: 6964.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 3.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + CH4 <=> CH3 + H2 # Reaction 53 + rate-constant: {A: 6.6e+08, b: 1.62, Ea: 1.084e+04} +- equation: H + HCO (+M) <=> CH2O (+M) # Reaction 54 + type: falloff + low-P-rate-constant: {A: 2.47e+24, b: -2.57, Ea: 425.0} + high-P-rate-constant: {A: 1.09e+12, b: 0.48, Ea: -260.0} + Troe: {A: 0.7824, T3: 271.0, T1: 2755.0, T2: 6570.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + HCO <=> H2 + CO # Reaction 55 + rate-constant: {A: 7.34e+13, b: 0.0, Ea: 0.0} +- equation: H + CH2O (+M) <=> CH2OH (+M) # Reaction 56 + type: falloff + low-P-rate-constant: {A: 1.27e+32, b: -4.82, Ea: 6530.0} + high-P-rate-constant: {A: 5.4e+11, b: 0.454, Ea: 3600.0} + Troe: {A: 0.7187, T3: 103.0, T1: 1291.0, T2: 4160.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: H + CH2O (+M) <=> CH3O (+M) # Reaction 57 + type: falloff + low-P-rate-constant: {A: 2.2e+30, b: -4.8, Ea: 5560.0} + high-P-rate-constant: {A: 5.4e+11, b: 0.454, Ea: 2600.0} + Troe: {A: 0.758, T3: 94.0, T1: 1555.0, T2: 4200.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: H + CH2O <=> HCO + H2 # Reaction 58 + rate-constant: {A: 5.74e+07, b: 1.9, Ea: 2742.0} +- equation: H + CH2OH (+M) <=> CH3OH (+M) # Reaction 59 + type: falloff + low-P-rate-constant: {A: 4.36e+31, b: -4.65, Ea: 5080.0} + high-P-rate-constant: {A: 1.055e+12, b: 0.5, Ea: 86.0} + Troe: {A: 0.6, T3: 100.0, T1: 9.0e+04, T2: 1.0e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: H + CH2OH <=> H2 + CH2O # Reaction 60 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: H + CH2OH <=> OH + CH3 # Reaction 61 + rate-constant: {A: 1.65e+11, b: 0.65, Ea: -284.0} +- equation: H + CH2OH <=> CH2(S) + H2O # Reaction 62 + rate-constant: {A: 3.28e+13, b: -0.09, Ea: 610.0} +- equation: H + CH3O (+M) <=> CH3OH (+M) # Reaction 63 + type: falloff + low-P-rate-constant: {A: 4.66e+41, b: -7.44, Ea: 1.408e+04} + high-P-rate-constant: {A: 2.43e+12, b: 0.515, Ea: 50.0} + Troe: {A: 0.7, T3: 100.0, T1: 9.0e+04, T2: 1.0e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: H + CH3O <=> H + CH2OH # Reaction 64 + rate-constant: {A: 4.15e+07, b: 1.63, Ea: 1924.0} +- equation: H + CH3O <=> H2 + CH2O # Reaction 65 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: H + CH3O <=> OH + CH3 # Reaction 66 + rate-constant: {A: 1.5e+12, b: 0.5, Ea: -110.0} +- equation: H + CH3O <=> CH2(S) + H2O # Reaction 67 + rate-constant: {A: 2.62e+14, b: -0.23, Ea: 1070.0} +- equation: H + CH3OH <=> CH2OH + H2 # Reaction 68 + rate-constant: {A: 1.7e+07, b: 2.1, Ea: 4870.0} +- equation: H + CH3OH <=> CH3O + H2 # Reaction 69 + rate-constant: {A: 4.2e+06, b: 2.1, Ea: 4870.0} +- equation: H + C2H (+M) <=> C2H2 (+M) # Reaction 70 + type: falloff + low-P-rate-constant: {A: 3.75e+33, b: -4.8, Ea: 1900.0} + high-P-rate-constant: {A: 1.0e+17, b: -1.0, Ea: 0.0} + Troe: {A: 0.6464, T3: 132.0, T1: 1315.0, T2: 5566.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C2H2 (+M) <=> C2H3 (+M) # Reaction 71 + type: falloff + low-P-rate-constant: {A: 3.8e+40, b: -7.27, Ea: 7220.0} + high-P-rate-constant: {A: 5.6e+12, b: 0.0, Ea: 2400.0} + Troe: {A: 0.7507, T3: 98.5, T1: 1302.0, T2: 4167.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C2H3 (+M) <=> C2H4 (+M) # Reaction 72 + type: falloff + low-P-rate-constant: {A: 1.4e+30, b: -3.86, Ea: 3320.0} + high-P-rate-constant: {A: 6.08e+12, b: 0.27, Ea: 280.0} + Troe: {A: 0.782, T3: 207.5, T1: 2663.0, T2: 6095.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C2H3 <=> H2 + C2H2 # Reaction 73 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: H + C2H4 (+M) <=> C2H5 (+M) # Reaction 74 + type: falloff + low-P-rate-constant: {A: 6.0e+41, b: -7.62, Ea: 6970.0} + high-P-rate-constant: {A: 5.4e+11, b: 0.454, Ea: 1820.0} + Troe: {A: 0.9753, T3: 210.0, T1: 984.0, T2: 4374.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C2H4 <=> C2H3 + H2 # Reaction 75 + rate-constant: {A: 1.325e+06, b: 2.53, Ea: 1.224e+04} +- equation: H + C2H5 (+M) <=> C2H6 (+M) # Reaction 76 + type: falloff + low-P-rate-constant: {A: 1.99e+41, b: -7.08, Ea: 6685.0} + high-P-rate-constant: {A: 5.21e+17, b: -0.99, Ea: 1580.0} + Troe: {A: 0.8422, T3: 125.0, T1: 2219.0, T2: 6882.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C2H5 <=> H2 + C2H4 # Reaction 77 + rate-constant: {A: 2.0e+12, b: 0.0, Ea: 0.0} +- equation: H + C2H6 <=> C2H5 + H2 # Reaction 78 + rate-constant: {A: 1.15e+08, b: 1.9, Ea: 7530.0} +- equation: H + HCCO <=> CH2(S) + CO # Reaction 79 + rate-constant: {A: 1.0e+14, b: 0.0, Ea: 0.0} +- equation: H + CH2CO <=> HCCO + H2 # Reaction 80 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 8000.0} +- equation: H + CH2CO <=> CH3 + CO # Reaction 81 + rate-constant: {A: 1.13e+13, b: 0.0, Ea: 3428.0} +- equation: H + HCCOH <=> H + CH2CO # Reaction 82 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 0.0} +- equation: H2 + CO (+M) <=> CH2O (+M) # Reaction 83 + type: falloff + low-P-rate-constant: {A: 5.07e+27, b: -3.42, Ea: 8.435e+04} + high-P-rate-constant: {A: 4.3e+07, b: 1.5, Ea: 7.96e+04} + Troe: {A: 0.932, T3: 197.0, T1: 1540.0, T2: 1.03e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: OH + H2 <=> H + H2O # Reaction 84 + rate-constant: {A: 2.16e+08, b: 1.51, Ea: 3430.0} +- equation: 2 OH (+M) <=> H2O2 (+M) # Reaction 85 + type: falloff + low-P-rate-constant: {A: 2.3e+18, b: -0.9, Ea: -1700.0} + high-P-rate-constant: {A: 7.4e+13, b: -0.37, Ea: 0.0} + Troe: {A: 0.7346, T3: 94.0, T1: 1756.0, T2: 5182.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: 2 OH <=> O + H2O # Reaction 86 + rate-constant: {A: 3.57e+04, b: 2.4, Ea: -2110.0} +- equation: OH + HO2 <=> O2 + H2O # Reaction 87 + duplicate: true + rate-constant: {A: 1.45e+13, b: 0.0, Ea: -500.0} +- equation: OH + H2O2 <=> HO2 + H2O # Reaction 88 + duplicate: true + rate-constant: {A: 2.0e+12, b: 0.0, Ea: 427.0} +- equation: OH + H2O2 <=> HO2 + H2O # Reaction 89 + duplicate: true + rate-constant: {A: 1.7e+18, b: 0.0, Ea: 2.941e+04} +- equation: OH + C <=> H + CO # Reaction 90 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH <=> H + HCO # Reaction 91 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH2 <=> H + CH2O # Reaction 92 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH2 <=> CH + H2O # Reaction 93 + rate-constant: {A: 1.13e+07, b: 2.0, Ea: 3000.0} +- equation: OH + CH2(S) <=> H + CH2O # Reaction 94 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH3 (+M) <=> CH3OH (+M) # Reaction 95 + type: falloff + low-P-rate-constant: {A: 4.0e+36, b: -5.92, Ea: 3140.0} + high-P-rate-constant: {A: 2.79e+18, b: -1.43, Ea: 1330.0} + Troe: {A: 0.412, T3: 195.0, T1: 5900.0, T2: 6394.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: OH + CH3 <=> CH2 + H2O # Reaction 96 + rate-constant: {A: 5.6e+07, b: 1.6, Ea: 5420.0} +- equation: OH + CH3 <=> CH2(S) + H2O # Reaction 97 + rate-constant: {A: 6.44e+17, b: -1.34, Ea: 1417.0} +- equation: OH + CH4 <=> CH3 + H2O # Reaction 98 + rate-constant: {A: 1.0e+08, b: 1.6, Ea: 3120.0} +- equation: OH + CO <=> H + CO2 # Reaction 99 + rate-constant: {A: 4.76e+07, b: 1.228, Ea: 70.0} +- equation: OH + HCO <=> H2O + CO # Reaction 100 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH2O <=> HCO + H2O # Reaction 101 + rate-constant: {A: 3.43e+09, b: 1.18, Ea: -447.0} +- equation: OH + CH2OH <=> H2O + CH2O # Reaction 102 + rate-constant: {A: 5.0e+12, b: 0.0, Ea: 0.0} +- equation: OH + CH3O <=> H2O + CH2O # Reaction 103 + rate-constant: {A: 5.0e+12, b: 0.0, Ea: 0.0} +- equation: OH + CH3OH <=> CH2OH + H2O # Reaction 104 + rate-constant: {A: 1.44e+06, b: 2.0, Ea: -840.0} +- equation: OH + CH3OH <=> CH3O + H2O # Reaction 105 + rate-constant: {A: 6.3e+06, b: 2.0, Ea: 1500.0} +- equation: OH + C2H <=> H + HCCO # Reaction 106 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: OH + C2H2 <=> H + CH2CO # Reaction 107 + rate-constant: {A: 2.18e-04, b: 4.5, Ea: -1000.0} +- equation: OH + C2H2 <=> H + HCCOH # Reaction 108 + rate-constant: {A: 5.04e+05, b: 2.3, Ea: 1.35e+04} +- equation: OH + C2H2 <=> C2H + H2O # Reaction 109 + rate-constant: {A: 3.37e+07, b: 2.0, Ea: 1.4e+04} +- equation: OH + C2H2 <=> CH3 + CO # Reaction 110 + rate-constant: {A: 4.83e-04, b: 4.0, Ea: -2000.0} +- equation: OH + C2H3 <=> H2O + C2H2 # Reaction 111 + rate-constant: {A: 5.0e+12, b: 0.0, Ea: 0.0} +- equation: OH + C2H4 <=> C2H3 + H2O # Reaction 112 + rate-constant: {A: 3.6e+06, b: 2.0, Ea: 2500.0} +- equation: OH + C2H6 <=> C2H5 + H2O # Reaction 113 + rate-constant: {A: 3.54e+06, b: 2.12, Ea: 870.0} +- equation: OH + CH2CO <=> HCCO + H2O # Reaction 114 + rate-constant: {A: 7.5e+12, b: 0.0, Ea: 2000.0} +- equation: 2 HO2 <=> O2 + H2O2 # Reaction 115 + duplicate: true + rate-constant: {A: 1.3e+11, b: 0.0, Ea: -1630.0} +- equation: 2 HO2 <=> O2 + H2O2 # Reaction 116 + duplicate: true + rate-constant: {A: 4.2e+14, b: 0.0, Ea: 1.2e+04} +- equation: HO2 + CH2 <=> OH + CH2O # Reaction 117 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: HO2 + CH3 <=> O2 + CH4 # Reaction 118 + rate-constant: {A: 1.0e+12, b: 0.0, Ea: 0.0} +- equation: HO2 + CH3 <=> OH + CH3O # Reaction 119 + rate-constant: {A: 3.78e+13, b: 0.0, Ea: 0.0} +- equation: HO2 + CO <=> OH + CO2 # Reaction 120 + rate-constant: {A: 1.5e+14, b: 0.0, Ea: 2.36e+04} +- equation: HO2 + CH2O <=> HCO + H2O2 # Reaction 121 + rate-constant: {A: 5.6e+06, b: 2.0, Ea: 1.2e+04} +- equation: C + O2 <=> O + CO # Reaction 122 + rate-constant: {A: 5.8e+13, b: 0.0, Ea: 576.0} +- equation: C + CH2 <=> H + C2H # Reaction 123 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: C + CH3 <=> H + C2H2 # Reaction 124 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: CH + O2 <=> O + HCO # Reaction 125 + rate-constant: {A: 6.71e+13, b: 0.0, Ea: 0.0} +- equation: CH + H2 <=> H + CH2 # Reaction 126 + rate-constant: {A: 1.08e+14, b: 0.0, Ea: 3110.0} +- equation: CH + H2O <=> H + CH2O # Reaction 127 + rate-constant: {A: 5.71e+12, b: 0.0, Ea: -755.0} +- equation: CH + CH2 <=> H + C2H2 # Reaction 128 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: 0.0} +- equation: CH + CH3 <=> H + C2H3 # Reaction 129 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: CH + CH4 <=> H + C2H4 # Reaction 130 + rate-constant: {A: 6.0e+13, b: 0.0, Ea: 0.0} +- equation: CH + CO (+M) <=> HCCO (+M) # Reaction 131 + type: falloff + low-P-rate-constant: {A: 2.69e+28, b: -3.74, Ea: 1936.0} + high-P-rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} + Troe: {A: 0.5757, T3: 237.0, T1: 1652.0, T2: 5069.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: CH + CO2 <=> HCO + CO # Reaction 132 + rate-constant: {A: 1.9e+14, b: 0.0, Ea: 1.5792e+04} +- equation: CH + CH2O <=> H + CH2CO # Reaction 133 + rate-constant: {A: 9.46e+13, b: 0.0, Ea: -515.0} +- equation: CH + HCCO <=> CO + C2H2 # Reaction 134 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: CH2 + O2 => OH + H + CO # Reaction 135 + rate-constant: {A: 5.0e+12, b: 0.0, Ea: 1500.0} +- equation: CH2 + H2 <=> H + CH3 # Reaction 136 + rate-constant: {A: 5.0e+05, b: 2.0, Ea: 7230.0} +- equation: 2 CH2 <=> H2 + C2H2 # Reaction 137 + rate-constant: {A: 1.6e+15, b: 0.0, Ea: 1.1944e+04} +- equation: CH2 + CH3 <=> H + C2H4 # Reaction 138 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: 0.0} +- equation: CH2 + CH4 <=> 2 CH3 # Reaction 139 + rate-constant: {A: 2.46e+06, b: 2.0, Ea: 8270.0} +- equation: CH2 + CO (+M) <=> CH2CO (+M) # Reaction 140 + type: falloff + low-P-rate-constant: {A: 2.69e+33, b: -5.11, Ea: 7095.0} + high-P-rate-constant: {A: 8.1e+11, b: 0.5, Ea: 4510.0} + Troe: {A: 0.5907, T3: 275.0, T1: 1226.0, T2: 5185.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: CH2 + HCCO <=> C2H3 + CO # Reaction 141 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + N2 <=> CH2 + N2 # Reaction 142 + rate-constant: {A: 1.5e+13, b: 0.0, Ea: 600.0} +- equation: CH2(S) + AR <=> CH2 + AR # Reaction 143 + rate-constant: {A: 9.0e+12, b: 0.0, Ea: 600.0} +- equation: CH2(S) + O2 <=> H + OH + CO # Reaction 144 + rate-constant: {A: 2.8e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + O2 <=> CO + H2O # Reaction 145 + rate-constant: {A: 1.2e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + H2 <=> CH3 + H # Reaction 146 + rate-constant: {A: 7.0e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + H2O (+M) <=> CH3OH (+M) # Reaction 147 + type: falloff + low-P-rate-constant: {A: 1.88e+38, b: -6.36, Ea: 5040.0} + high-P-rate-constant: {A: 4.82e+17, b: -1.16, Ea: 1145.0} + Troe: {A: 0.6027, T3: 208.0, T1: 3922.0, T2: 1.018e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: CH2(S) + H2O <=> CH2 + H2O # Reaction 148 + rate-constant: {A: 3.0e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + CH3 <=> H + C2H4 # Reaction 149 + rate-constant: {A: 1.2e+13, b: 0.0, Ea: -570.0} +- equation: CH2(S) + CH4 <=> 2 CH3 # Reaction 150 + rate-constant: {A: 1.6e+13, b: 0.0, Ea: -570.0} +- equation: CH2(S) + CO <=> CH2 + CO # Reaction 151 + rate-constant: {A: 9.0e+12, b: 0.0, Ea: 0.0} +- equation: CH2(S) + CO2 <=> CH2 + CO2 # Reaction 152 + rate-constant: {A: 7.0e+12, b: 0.0, Ea: 0.0} +- equation: CH2(S) + CO2 <=> CO + CH2O # Reaction 153 + rate-constant: {A: 1.4e+13, b: 0.0, Ea: 0.0} +- equation: CH2(S) + C2H6 <=> CH3 + C2H5 # Reaction 154 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: -550.0} +- equation: CH3 + O2 <=> O + CH3O # Reaction 155 + rate-constant: {A: 3.56e+13, b: 0.0, Ea: 3.048e+04} +- equation: CH3 + O2 <=> OH + CH2O # Reaction 156 + rate-constant: {A: 2.31e+12, b: 0.0, Ea: 2.0315e+04} +- equation: CH3 + H2O2 <=> HO2 + CH4 # Reaction 157 + rate-constant: {A: 2.45e+04, b: 2.47, Ea: 5180.0} +- equation: 2 CH3 (+M) <=> C2H6 (+M) # Reaction 158 + type: falloff + low-P-rate-constant: {A: 3.4e+41, b: -7.03, Ea: 2762.0} + high-P-rate-constant: {A: 6.77e+16, b: -1.18, Ea: 654.0} + Troe: {A: 0.619, T3: 73.2, T1: 1180.0, T2: 9999.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: 2 CH3 <=> H + C2H5 # Reaction 159 + rate-constant: {A: 6.84e+12, b: 0.1, Ea: 1.06e+04} +- equation: CH3 + HCO <=> CH4 + CO # Reaction 160 + rate-constant: {A: 2.648e+13, b: 0.0, Ea: 0.0} +- equation: CH3 + CH2O <=> HCO + CH4 # Reaction 161 + rate-constant: {A: 3320.0, b: 2.81, Ea: 5860.0} +- equation: CH3 + CH3OH <=> CH2OH + CH4 # Reaction 162 + rate-constant: {A: 3.0e+07, b: 1.5, Ea: 9940.0} +- equation: CH3 + CH3OH <=> CH3O + CH4 # Reaction 163 + rate-constant: {A: 1.0e+07, b: 1.5, Ea: 9940.0} +- equation: CH3 + C2H4 <=> C2H3 + CH4 # Reaction 164 + rate-constant: {A: 2.27e+05, b: 2.0, Ea: 9200.0} +- equation: CH3 + C2H6 <=> C2H5 + CH4 # Reaction 165 + rate-constant: {A: 6.14e+06, b: 1.74, Ea: 1.045e+04} +- equation: HCO + H2O <=> H + CO + H2O # Reaction 166 + rate-constant: {A: 1.5e+18, b: -1.0, Ea: 1.7e+04} +- equation: HCO + M <=> H + CO + M # Reaction 167 + type: three-body + rate-constant: {A: 1.87e+17, b: -1.0, Ea: 1.7e+04} + efficiencies: {H2: 2.0, H2O: 0.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0} +- equation: HCO + O2 <=> HO2 + CO # Reaction 168 + rate-constant: {A: 1.345e+13, b: 0.0, Ea: 400.0} +- equation: CH2OH + O2 <=> HO2 + CH2O # Reaction 169 + rate-constant: {A: 1.8e+13, b: 0.0, Ea: 900.0} +- equation: CH3O + O2 <=> HO2 + CH2O # Reaction 170 + rate-constant: {A: 4.28e-13, b: 7.6, Ea: -3530.0} +- equation: C2H + O2 <=> HCO + CO # Reaction 171 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: -755.0} +- equation: C2H + H2 <=> H + C2H2 # Reaction 172 + rate-constant: {A: 5.68e+10, b: 0.9, Ea: 1993.0} +- equation: C2H3 + O2 <=> HCO + CH2O # Reaction 173 + rate-constant: {A: 4.58e+16, b: -1.39, Ea: 1015.0} +- equation: C2H4 (+M) <=> H2 + C2H2 (+M) # Reaction 174 + type: falloff + low-P-rate-constant: {A: 1.58e+51, b: -9.3, Ea: 9.78e+04} + high-P-rate-constant: {A: 8.0e+12, b: 0.44, Ea: 8.677e+04} + Troe: {A: 0.7345, T3: 180.0, T1: 1035.0, T2: 5417.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: C2H5 + O2 <=> HO2 + C2H4 # Reaction 175 + rate-constant: {A: 8.4e+11, b: 0.0, Ea: 3875.0} +- equation: HCCO + O2 <=> OH + 2 CO # Reaction 176 + rate-constant: {A: 3.2e+12, b: 0.0, Ea: 854.0} +- equation: 2 HCCO <=> 2 CO + C2H2 # Reaction 177 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 0.0} +- equation: N + NO <=> N2 + O # Reaction 178 + rate-constant: {A: 2.7e+13, b: 0.0, Ea: 355.0} +- equation: N + O2 <=> NO + O # Reaction 179 + rate-constant: {A: 9.0e+09, b: 1.0, Ea: 6500.0} +- equation: N + OH <=> NO + H # Reaction 180 + rate-constant: {A: 3.36e+13, b: 0.0, Ea: 385.0} +- equation: N2O + O <=> N2 + O2 # Reaction 181 + rate-constant: {A: 1.4e+12, b: 0.0, Ea: 1.081e+04} +- equation: N2O + O <=> 2 NO # Reaction 182 + rate-constant: {A: 2.9e+13, b: 0.0, Ea: 2.315e+04} +- equation: N2O + H <=> N2 + OH # Reaction 183 + rate-constant: {A: 3.87e+14, b: 0.0, Ea: 1.888e+04} +- equation: N2O + OH <=> N2 + HO2 # Reaction 184 + rate-constant: {A: 2.0e+12, b: 0.0, Ea: 2.106e+04} +- equation: N2O (+M) <=> N2 + O (+M) # Reaction 185 + type: falloff + low-P-rate-constant: {A: 6.37e+14, b: 0.0, Ea: 5.664e+04} + high-P-rate-constant: {A: 7.91e+10, b: 0.0, Ea: 5.602e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.625} +- equation: HO2 + NO <=> NO2 + OH # Reaction 186 + rate-constant: {A: 2.11e+12, b: 0.0, Ea: -480.0} +- equation: NO + O + M <=> NO2 + M # Reaction 187 + type: three-body + rate-constant: {A: 1.06e+20, b: -1.41, Ea: 0.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: NO2 + O <=> NO + O2 # Reaction 188 + rate-constant: {A: 3.9e+12, b: 0.0, Ea: -240.0} +- equation: NO2 + H <=> NO + OH # Reaction 189 + rate-constant: {A: 1.32e+14, b: 0.0, Ea: 360.0} +- equation: NH + O <=> NO + H # Reaction 190 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: 0.0} +- equation: NH + H <=> N + H2 # Reaction 191 + rate-constant: {A: 3.2e+13, b: 0.0, Ea: 330.0} +- equation: NH + OH <=> HNO + H # Reaction 192 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: NH + OH <=> N + H2O # Reaction 193 + rate-constant: {A: 2.0e+09, b: 1.2, Ea: 0.0} +- equation: NH + O2 <=> HNO + O # Reaction 194 + rate-constant: {A: 4.61e+05, b: 2.0, Ea: 6500.0} +- equation: NH + O2 <=> NO + OH # Reaction 195 + rate-constant: {A: 1.28e+06, b: 1.5, Ea: 100.0} +- equation: NH + N <=> N2 + H # Reaction 196 + rate-constant: {A: 1.5e+13, b: 0.0, Ea: 0.0} +- equation: NH + H2O <=> HNO + H2 # Reaction 197 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 1.385e+04} +- equation: NH + NO <=> N2 + OH # Reaction 198 + rate-constant: {A: 2.16e+13, b: -0.23, Ea: 0.0} +- equation: NH + NO <=> N2O + H # Reaction 199 + rate-constant: {A: 3.65e+14, b: -0.45, Ea: 0.0} +- equation: NH2 + O <=> OH + NH # Reaction 200 + rate-constant: {A: 3.0e+12, b: 0.0, Ea: 0.0} +- equation: NH2 + O <=> H + HNO # Reaction 201 + rate-constant: {A: 3.9e+13, b: 0.0, Ea: 0.0} +- equation: NH2 + H <=> NH + H2 # Reaction 202 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: 3650.0} +- equation: NH2 + OH <=> NH + H2O # Reaction 203 + rate-constant: {A: 9.0e+07, b: 1.5, Ea: -460.0} +- equation: NNH <=> N2 + H # Reaction 204 + rate-constant: {A: 3.3e+08, b: 0.0, Ea: 0.0} +- equation: NNH + M <=> N2 + H + M # Reaction 205 + type: three-body + rate-constant: {A: 1.3e+14, b: -0.11, Ea: 4980.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: NNH + O2 <=> HO2 + N2 # Reaction 206 + rate-constant: {A: 5.0e+12, b: 0.0, Ea: 0.0} +- equation: NNH + O <=> OH + N2 # Reaction 207 + rate-constant: {A: 2.5e+13, b: 0.0, Ea: 0.0} +- equation: NNH + O <=> NH + NO # Reaction 208 + rate-constant: {A: 7.0e+13, b: 0.0, Ea: 0.0} +- equation: NNH + H <=> H2 + N2 # Reaction 209 + rate-constant: {A: 5.0e+13, b: 0.0, Ea: 0.0} +- equation: NNH + OH <=> H2O + N2 # Reaction 210 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: NNH + CH3 <=> CH4 + N2 # Reaction 211 + rate-constant: {A: 2.5e+13, b: 0.0, Ea: 0.0} +- equation: H + NO + M <=> HNO + M # Reaction 212 + type: three-body + rate-constant: {A: 4.48e+19, b: -1.32, Ea: 740.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: HNO + O <=> NO + OH # Reaction 213 + rate-constant: {A: 2.5e+13, b: 0.0, Ea: 0.0} +- equation: HNO + H <=> H2 + NO # Reaction 214 + rate-constant: {A: 9.0e+11, b: 0.72, Ea: 660.0} +- equation: HNO + OH <=> NO + H2O # Reaction 215 + rate-constant: {A: 1.3e+07, b: 1.9, Ea: -950.0} +- equation: HNO + O2 <=> HO2 + NO # Reaction 216 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 1.3e+04} +- equation: CN + O <=> CO + N # Reaction 217 + rate-constant: {A: 7.7e+13, b: 0.0, Ea: 0.0} +- equation: CN + OH <=> NCO + H # Reaction 218 + rate-constant: {A: 4.0e+13, b: 0.0, Ea: 0.0} +- equation: CN + H2O <=> HCN + OH # Reaction 219 + rate-constant: {A: 8.0e+12, b: 0.0, Ea: 7460.0} +- equation: CN + O2 <=> NCO + O # Reaction 220 + rate-constant: {A: 6.14e+12, b: 0.0, Ea: -440.0} +- equation: CN + H2 <=> HCN + H # Reaction 221 + rate-constant: {A: 2.95e+05, b: 2.45, Ea: 2240.0} +- equation: NCO + O <=> NO + CO # Reaction 222 + rate-constant: {A: 2.35e+13, b: 0.0, Ea: 0.0} +- equation: NCO + H <=> NH + CO # Reaction 223 + rate-constant: {A: 5.4e+13, b: 0.0, Ea: 0.0} +- equation: NCO + OH <=> NO + H + CO # Reaction 224 + rate-constant: {A: 2.5e+12, b: 0.0, Ea: 0.0} +- equation: NCO + N <=> N2 + CO # Reaction 225 + rate-constant: {A: 2.0e+13, b: 0.0, Ea: 0.0} +- equation: NCO + O2 <=> NO + CO2 # Reaction 226 + rate-constant: {A: 2.0e+12, b: 0.0, Ea: 2.0e+04} +- equation: NCO + M <=> N + CO + M # Reaction 227 + type: three-body + rate-constant: {A: 3.1e+14, b: 0.0, Ea: 5.405e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: NCO + NO <=> N2O + CO # Reaction 228 + rate-constant: {A: 1.9e+17, b: -1.52, Ea: 740.0} +- equation: NCO + NO <=> N2 + CO2 # Reaction 229 + rate-constant: {A: 3.8e+18, b: -2.0, Ea: 800.0} +- equation: HCN + M <=> H + CN + M # Reaction 230 + type: three-body + rate-constant: {A: 1.04e+29, b: -3.3, Ea: 1.266e+05} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: HCN + O <=> NCO + H # Reaction 231 + rate-constant: {A: 2.03e+04, b: 2.64, Ea: 4980.0} +- equation: HCN + O <=> NH + CO # Reaction 232 + rate-constant: {A: 5070.0, b: 2.64, Ea: 4980.0} +- equation: HCN + O <=> CN + OH # Reaction 233 + rate-constant: {A: 3.91e+09, b: 1.58, Ea: 2.66e+04} +- equation: HCN + OH <=> HOCN + H # Reaction 234 + rate-constant: {A: 1.1e+06, b: 2.03, Ea: 1.337e+04} +- equation: HCN + OH <=> HNCO + H # Reaction 235 + rate-constant: {A: 4400.0, b: 2.26, Ea: 6400.0} +- equation: HCN + OH <=> NH2 + CO # Reaction 236 + rate-constant: {A: 160.0, b: 2.56, Ea: 9000.0} +- equation: H + HCN (+M) <=> H2CN (+M) # Reaction 237 + type: falloff + low-P-rate-constant: {A: 1.4e+26, b: -3.4, Ea: 1900.0} + high-P-rate-constant: {A: 3.3e+13, b: 0.0, Ea: 0.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H2CN + N <=> N2 + CH2 # Reaction 238 + rate-constant: {A: 6.0e+13, b: 0.0, Ea: 400.0} +- equation: C + N2 <=> CN + N # Reaction 239 + rate-constant: {A: 6.3e+13, b: 0.0, Ea: 4.602e+04} +- equation: CH + N2 <=> HCN + N # Reaction 240 + rate-constant: {A: 3.12e+09, b: 0.88, Ea: 2.013e+04} +- equation: CH + N2 (+M) <=> HCNN (+M) # Reaction 241 + type: falloff + low-P-rate-constant: {A: 1.3e+25, b: -3.16, Ea: 740.0} + high-P-rate-constant: {A: 3.1e+12, b: 0.15, Ea: 0.0} + Troe: {A: 0.667, T3: 235.0, T1: 2117.0, T2: 4536.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 1.0} +- equation: CH2 + N2 <=> HCN + NH # Reaction 242 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 7.4e+04} +- equation: CH2(S) + N2 <=> NH + HCN # Reaction 243 + rate-constant: {A: 1.0e+11, b: 0.0, Ea: 6.5e+04} +- equation: C + NO <=> CN + O # Reaction 244 + rate-constant: {A: 1.9e+13, b: 0.0, Ea: 0.0} +- equation: C + NO <=> CO + N # Reaction 245 + rate-constant: {A: 2.9e+13, b: 0.0, Ea: 0.0} +- equation: CH + NO <=> HCN + O # Reaction 246 + rate-constant: {A: 4.1e+13, b: 0.0, Ea: 0.0} +- equation: CH + NO <=> H + NCO # Reaction 247 + rate-constant: {A: 1.62e+13, b: 0.0, Ea: 0.0} +- equation: CH + NO <=> N + HCO # Reaction 248 + rate-constant: {A: 2.46e+13, b: 0.0, Ea: 0.0} +- equation: CH2 + NO <=> H + HNCO # Reaction 249 + rate-constant: {A: 3.1e+17, b: -1.38, Ea: 1270.0} +- equation: CH2 + NO <=> OH + HCN # Reaction 250 + rate-constant: {A: 2.9e+14, b: -0.69, Ea: 760.0} +- equation: CH2 + NO <=> H + HCNO # Reaction 251 + rate-constant: {A: 3.8e+13, b: -0.36, Ea: 580.0} +- equation: CH2(S) + NO <=> H + HNCO # Reaction 252 + rate-constant: {A: 3.1e+17, b: -1.38, Ea: 1270.0} +- equation: CH2(S) + NO <=> OH + HCN # Reaction 253 + rate-constant: {A: 2.9e+14, b: -0.69, Ea: 760.0} +- equation: CH2(S) + NO <=> H + HCNO # Reaction 254 + rate-constant: {A: 3.8e+13, b: -0.36, Ea: 580.0} +- equation: CH3 + NO <=> HCN + H2O # Reaction 255 + rate-constant: {A: 9.6e+13, b: 0.0, Ea: 2.88e+04} +- equation: CH3 + NO <=> H2CN + OH # Reaction 256 + rate-constant: {A: 1.0e+12, b: 0.0, Ea: 2.175e+04} +- equation: HCNN + O <=> CO + H + N2 # Reaction 257 + rate-constant: {A: 2.2e+13, b: 0.0, Ea: 0.0} +- equation: HCNN + O <=> HCN + NO # Reaction 258 + rate-constant: {A: 2.0e+12, b: 0.0, Ea: 0.0} +- equation: HCNN + O2 <=> O + HCO + N2 # Reaction 259 + rate-constant: {A: 1.2e+13, b: 0.0, Ea: 0.0} +- equation: HCNN + OH <=> H + HCO + N2 # Reaction 260 + rate-constant: {A: 1.2e+13, b: 0.0, Ea: 0.0} +- equation: HCNN + H <=> CH2 + N2 # Reaction 261 + rate-constant: {A: 1.0e+14, b: 0.0, Ea: 0.0} +- equation: HNCO + O <=> NH + CO2 # Reaction 262 + rate-constant: {A: 9.8e+07, b: 1.41, Ea: 8500.0} +- equation: HNCO + O <=> HNO + CO # Reaction 263 + rate-constant: {A: 1.5e+08, b: 1.57, Ea: 4.4e+04} +- equation: HNCO + O <=> NCO + OH # Reaction 264 + rate-constant: {A: 2.2e+06, b: 2.11, Ea: 1.14e+04} +- equation: HNCO + H <=> NH2 + CO # Reaction 265 + rate-constant: {A: 2.25e+07, b: 1.7, Ea: 3800.0} +- equation: HNCO + H <=> H2 + NCO # Reaction 266 + rate-constant: {A: 1.05e+05, b: 2.5, Ea: 1.33e+04} +- equation: HNCO + OH <=> NCO + H2O # Reaction 267 + rate-constant: {A: 3.3e+07, b: 1.5, Ea: 3600.0} +- equation: HNCO + OH <=> NH2 + CO2 # Reaction 268 + rate-constant: {A: 3.3e+06, b: 1.5, Ea: 3600.0} +- equation: HNCO + M <=> NH + CO + M # Reaction 269 + type: three-body + rate-constant: {A: 1.18e+16, b: 0.0, Ea: 8.472e+04} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: HCNO + H <=> H + HNCO # Reaction 270 + rate-constant: {A: 2.1e+15, b: -0.69, Ea: 2850.0} +- equation: HCNO + H <=> OH + HCN # Reaction 271 + rate-constant: {A: 2.7e+11, b: 0.18, Ea: 2120.0} +- equation: HCNO + H <=> NH2 + CO # Reaction 272 + rate-constant: {A: 1.7e+14, b: -0.75, Ea: 2890.0} +- equation: HOCN + H <=> H + HNCO # Reaction 273 + rate-constant: {A: 2.0e+07, b: 2.0, Ea: 2000.0} +- equation: HCCO + NO <=> HCNO + CO # Reaction 274 + rate-constant: {A: 9.0e+12, b: 0.0, Ea: 0.0} +- equation: CH3 + N <=> H2CN + H # Reaction 275 + rate-constant: {A: 6.1e+14, b: -0.31, Ea: 290.0} +- equation: CH3 + N <=> HCN + H2 # Reaction 276 + rate-constant: {A: 3.7e+12, b: 0.15, Ea: -90.0} +- equation: NH3 + H <=> NH2 + H2 # Reaction 277 + rate-constant: {A: 5.4e+05, b: 2.4, Ea: 9915.0} +- equation: NH3 + OH <=> NH2 + H2O # Reaction 278 + rate-constant: {A: 5.0e+07, b: 1.6, Ea: 955.0} +- equation: NH3 + O <=> NH2 + OH # Reaction 279 + rate-constant: {A: 9.4e+06, b: 1.94, Ea: 6460.0} +- equation: NH + CO2 <=> HNO + CO # Reaction 280 + rate-constant: {A: 1.0e+13, b: 0.0, Ea: 1.435e+04} +- equation: CN + NO2 <=> NCO + NO # Reaction 281 + rate-constant: {A: 6.16e+15, b: -0.752, Ea: 345.0} +- equation: NCO + NO2 <=> N2O + CO2 # Reaction 282 + rate-constant: {A: 3.25e+12, b: 0.0, Ea: -705.0} +- equation: N + CO2 <=> NO + CO # Reaction 283 + rate-constant: {A: 3.0e+12, b: 0.0, Ea: 1.13e+04} +- equation: O + CH3 => H + H2 + CO # Reaction 284 + rate-constant: {A: 3.37e+13, b: 0.0, Ea: 0.0} +- equation: O + C2H4 <=> H + CH2CHO # Reaction 285 + rate-constant: {A: 6.7e+06, b: 1.83, Ea: 220.0} +- equation: O + C2H5 <=> H + CH3CHO # Reaction 286 + rate-constant: {A: 1.096e+14, b: 0.0, Ea: 0.0} +- equation: OH + HO2 <=> O2 + H2O # Reaction 287 + duplicate: true + rate-constant: {A: 5.0e+15, b: 0.0, Ea: 1.733e+04} +- equation: OH + CH3 => H2 + CH2O # Reaction 288 + rate-constant: {A: 8.0e+09, b: 0.5, Ea: -1755.0} +- equation: CH + H2 (+M) <=> CH3 (+M) # Reaction 289 + type: falloff + low-P-rate-constant: {A: 4.82e+25, b: -2.8, Ea: 590.0} + high-P-rate-constant: {A: 1.97e+12, b: 0.43, Ea: -370.0} + Troe: {A: 0.578, T3: 122.0, T1: 2535.0, T2: 9365.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: CH2 + O2 => 2 H + CO2 # Reaction 290 + rate-constant: {A: 5.8e+12, b: 0.0, Ea: 1500.0} +- equation: CH2 + O2 <=> O + CH2O # Reaction 291 + rate-constant: {A: 2.4e+12, b: 0.0, Ea: 1500.0} +- equation: CH2 + CH2 => 2 H + C2H2 # Reaction 292 + rate-constant: {A: 2.0e+14, b: 0.0, Ea: 1.0989e+04} +- equation: CH2(S) + H2O => H2 + CH2O # Reaction 293 + rate-constant: {A: 6.82e+10, b: 0.25, Ea: -935.0} +- equation: C2H3 + O2 <=> O + CH2CHO # Reaction 294 + rate-constant: {A: 3.03e+11, b: 0.29, Ea: 11.0} +- equation: C2H3 + O2 <=> HO2 + C2H2 # Reaction 295 + rate-constant: {A: 1.337e+06, b: 1.61, Ea: -384.0} +- equation: O + CH3CHO <=> OH + CH2CHO # Reaction 296 + rate-constant: {A: 5.84e+12, b: 0.0, Ea: 1808.0} +- equation: O + CH3CHO => OH + CH3 + CO # Reaction 297 + rate-constant: {A: 5.84e+12, b: 0.0, Ea: 1808.0} +- equation: O2 + CH3CHO => HO2 + CH3 + CO # Reaction 298 + rate-constant: {A: 3.01e+13, b: 0.0, Ea: 3.915e+04} +- equation: H + CH3CHO <=> CH2CHO + H2 # Reaction 299 + rate-constant: {A: 2.05e+09, b: 1.16, Ea: 2405.0} +- equation: H + CH3CHO => CH3 + H2 + CO # Reaction 300 + rate-constant: {A: 2.05e+09, b: 1.16, Ea: 2405.0} +- equation: OH + CH3CHO => CH3 + H2O + CO # Reaction 301 + rate-constant: {A: 2.343e+10, b: 0.73, Ea: -1113.0} +- equation: HO2 + CH3CHO => CH3 + H2O2 + CO # Reaction 302 + rate-constant: {A: 3.01e+12, b: 0.0, Ea: 1.1923e+04} +- equation: CH3 + CH3CHO => CH3 + CH4 + CO # Reaction 303 + rate-constant: {A: 2.72e+06, b: 1.77, Ea: 5920.0} +- equation: H + CH2CO (+M) <=> CH2CHO (+M) # Reaction 304 + type: falloff + low-P-rate-constant: {A: 1.012e+42, b: -7.63, Ea: 3854.0} + high-P-rate-constant: {A: 4.865e+11, b: 0.422, Ea: -1755.0} + Troe: {A: 0.465, T3: 201.0, T1: 1773.0, T2: 5333.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: O + CH2CHO => H + CH2 + CO2 # Reaction 305 + rate-constant: {A: 1.5e+14, b: 0.0, Ea: 0.0} +- equation: O2 + CH2CHO => OH + CO + CH2O # Reaction 306 + rate-constant: {A: 1.81e+10, b: 0.0, Ea: 0.0} +- equation: O2 + CH2CHO => OH + 2 HCO # Reaction 307 + rate-constant: {A: 2.35e+10, b: 0.0, Ea: 0.0} +- equation: H + CH2CHO <=> CH3 + HCO # Reaction 308 + rate-constant: {A: 2.2e+13, b: 0.0, Ea: 0.0} +- equation: H + CH2CHO <=> CH2CO + H2 # Reaction 309 + rate-constant: {A: 1.1e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH2CHO <=> H2O + CH2CO # Reaction 310 + rate-constant: {A: 1.2e+13, b: 0.0, Ea: 0.0} +- equation: OH + CH2CHO <=> HCO + CH2OH # Reaction 311 + rate-constant: {A: 3.01e+13, b: 0.0, Ea: 0.0} +- equation: CH3 + C2H5 (+M) <=> C3H8 (+M) # Reaction 312 + type: falloff + low-P-rate-constant: {A: 2.71e+74, b: -16.82, Ea: 1.3065e+04} + high-P-rate-constant: {A: 9.43e+12, b: 0.0, Ea: 0.0} + Troe: {A: 0.1527, T3: 291.0, T1: 2742.0, T2: 7748.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: O + C3H8 <=> OH + C3H7 # Reaction 313 + rate-constant: {A: 1.93e+05, b: 2.68, Ea: 3716.0} +- equation: H + C3H8 <=> C3H7 + H2 # Reaction 314 + rate-constant: {A: 1.32e+06, b: 2.54, Ea: 6756.0} +- equation: OH + C3H8 <=> C3H7 + H2O # Reaction 315 + rate-constant: {A: 3.16e+07, b: 1.8, Ea: 934.0} +- equation: C3H7 + H2O2 <=> HO2 + C3H8 # Reaction 316 + rate-constant: {A: 378.0, b: 2.72, Ea: 1500.0} +- equation: CH3 + C3H8 <=> C3H7 + CH4 # Reaction 317 + rate-constant: {A: 0.903, b: 3.65, Ea: 7154.0} +- equation: CH3 + C2H4 (+M) <=> C3H7 (+M) # Reaction 318 + type: falloff + low-P-rate-constant: {A: 3.0e+63, b: -14.6, Ea: 1.817e+04} + high-P-rate-constant: {A: 2.55e+06, b: 1.6, Ea: 5700.0} + Troe: {A: 0.1894, T3: 277.0, T1: 8748.0, T2: 7891.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: O + C3H7 <=> C2H5 + CH2O # Reaction 319 + rate-constant: {A: 9.64e+13, b: 0.0, Ea: 0.0} +- equation: H + C3H7 (+M) <=> C3H8 (+M) # Reaction 320 + type: falloff + low-P-rate-constant: {A: 4.42e+61, b: -13.545, Ea: 1.1357e+04} + high-P-rate-constant: {A: 3.613e+13, b: 0.0, Ea: 0.0} + Troe: {A: 0.315, T3: 369.0, T1: 3285.0, T2: 6667.0} + efficiencies: {H2: 2.0, H2O: 6.0, CH4: 2.0, CO: 1.5, CO2: 2.0, C2H6: 3.0, + AR: 0.7} +- equation: H + C3H7 <=> CH3 + C2H5 # Reaction 321 + rate-constant: {A: 4.06e+06, b: 2.19, Ea: 890.0} +- equation: OH + C3H7 <=> C2H5 + CH2OH # Reaction 322 + rate-constant: {A: 2.41e+13, b: 0.0, Ea: 0.0} +- equation: HO2 + C3H7 <=> O2 + C3H8 # Reaction 323 + rate-constant: {A: 2.55e+10, b: 0.255, Ea: -943.0} +- equation: HO2 + C3H7 => OH + C2H5 + CH2O # Reaction 324 + rate-constant: {A: 2.41e+13, b: 0.0, Ea: 0.0} +- equation: CH3 + C3H7 <=> 2 C2H5 # Reaction 325 + rate-constant: {A: 1.927e+13, b: -0.32, Ea: 0.0} diff --git a/examples/ignition_model/forward_model/methane_combustion/combustion.py b/examples/ignition_model/forward_model/methane_combustion/combustion.py new file mode 100644 index 0000000..3595f4f --- /dev/null +++ b/examples/ignition_model/forward_model/methane_combustion/combustion.py @@ -0,0 +1,1394 @@ +import cantera as ct +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.ticker import MultipleLocator, AutoMinorLocator +import sys +import time +from mpi4py import MPI + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) +plt.rc("legend", fontsize=18, framealpha=1.0) +plt.rc("xtick.major", size=6) +plt.rc("xtick.minor", size=4) +plt.rc("ytick.major", size=6) +plt.rc("ytick.minor", size=4) + + +class mech_1S_CH4_MP1: + def __init__( + self, + initial_temperature, + initial_pressure, + equivalence_ratio, + Arrhenius_A=1.1e10, + Arrhenius_Ea=20000, + Arrhenius_a=1.0, + Arrhenius_b=0.5, + ): + self.initial_temperature = initial_temperature + self.initial_pressure = initial_pressure + self.equivalence_ratio = equivalence_ratio + + # Arrhenius parameters (Reaction 1) + self.Arrhenius_A = Arrhenius_A + self.Arrhenius_Ea = Arrhenius_Ea + self.Arrhenius_a = Arrhenius_a + self.Arrhenius_b = Arrhenius_b + + def mech_1S_CH4_MP1_combustion(self): + """Function computes the temperature profile for 1S_CH4_MP1 mechanism + references: + https://www.cerfacs.fr/cantera/mechanisms/meth.php + https://www.cerfacs.fr/cantera/docs/mechanisms/methane-air/GLOB/CANTERA/1S_CH4_MP1.cti + """ + gas = ct.Solution(yaml=self.get_1S_CH4_MP1_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + + dt = 1e-6 + t_end = 3 * 1e-3 + + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + for t in np.arange(0, t_end, dt): + # print("Time : {0:.5f} [s]".format(t)) + sim.advance(t) + states.append(reactor.thermo.state, t=t) + + return states + + def get_1S_CH4_MP1_gas_file(self): + """Function returns the gas file for 1S_CH4_MP1 mechanism""" + + gas_file = """ + + description: |- + Single step mechanism (1S CH4 MP1) + + units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + + phases: + - name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + + species: + - name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 + + - name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 + + - name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 + + - name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 + + - name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 + + reactions: + - equation: CH4 + 2 O2 => CO2 + 2 H2O + rate-constant: {A: %f, b: 0, Ea: %f} + orders: {CH4: %f, O2: %f} + """ % ( + self.Arrhenius_A, + self.Arrhenius_Ea, + self.Arrhenius_a, + self.Arrhenius_b, + ) + + return gas_file + + def compute_adiabatic_temperature(self): + """Function computes the adiabatic temperature""" + gas = ct.Solution(yaml=self.get_1S_CH4_MP1_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + return gas.T + + def compute_equilibrate_species_concentration(self, species_names): + """Function computest the equilibrate species concentration""" + gas = ct.Solution(yaml=self.get_1S_CH4_MP1_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + + gas.selected_species = species_names + species_final_mole_fraction = gas.X + + return species_final_mole_fraction + + +class mech_gri: + def __init__(self, initial_temperature, initial_pressure, equivalence_ratio): + self.initial_temperature = initial_temperature + self.initial_pressure = initial_pressure + self.equivalence_ratio = equivalence_ratio + + def gri_combustion(self): + """function computes the temperature profile""" + gas = ct.Solution("gri30.yaml") + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + dt = 5e-6 + t_end = 1 + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + while sim.time < t_end: + sim.step() + states.append(reactor.thermo.state, t=sim.time) + + # for t in np.arange(0, t_end, dt): + # # print("Time : {0:.5f} [s]".format(t)) + # sim.advance(t) + # states.append(reactor.thermo.state, t=t) + + ignition_time, ignition_temperature = compute_ignition_stats( + temperature=states.T, time=states.t + ) + + return states, ignition_time + + def compute_adiabatic_temperature(self): + """Function computes the adiabatic temperature""" + gas = ct.Solution("gri30.yaml") + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + return gas.T + + def compute_equilibrate_species_concentration(self, species_names): + """Function computest the equilibrate species concentration""" + gas = ct.Solution("gri30.yaml") + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + + gas.selected_species = species_names + species_final_mole_fraction = gas.X + + return species_final_mole_fraction + + def compute_ignition_time(self): + """Function computes the ignition temperature""" + gas = ct.Solution("gri30.yaml") + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + dt = 1e-6 + t_end = 1e5 + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + while sim.time < t_end: + try: + sim.step() + states.append(reactor.thermo.state, t=sim.time) + except: + sim.atol = sim.atol * 10 + + ignition_time, ignition_temperature = compute_ignition_stats( + temperature=states.T, time=states.t + ) + + return ignition_time + + +class mech_1S_CH4_Westbrook: + def __init__( + self, + initial_temperature, + initial_pressure, + equivalence_ratio, + Arrhenius_A=8.3e5, + Arrhenius_Ea=30000, + Arrhenius_a=-0.3, + Arrhenius_b=1.3, + ): + self.initial_temperature = initial_temperature + self.initial_pressure = initial_pressure + self.equivalence_ratio = equivalence_ratio + + # Arrhenius parameters (Reaction 1) + self.Arrhenius_A = Arrhenius_A + self.Arrhenius_Ea = Arrhenius_Ea + self.Arrhenius_a = Arrhenius_a + self.Arrhenius_b = Arrhenius_b + + def mech_1S_CH4_Westbrook_combustion(self): + """Function computes the temperature profile for 1S_CH4_MP1 mechanism + references: + """ + gas = ct.Solution(yaml=self.get_1S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + + dt = 1e-6 + t_end = 3 * 1e-3 + + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + for t in np.arange(0, t_end, dt): + # print("Time : {0:.5f} [s]".format(t)) + sim.advance(t) + states.append(reactor.thermo.state, t=t) + + return states + + def get_1S_CH4_Westbrook_gas_file(self): + """Function returns the gas file for 1S_CH4_MP1 mechanism""" + negative_orders_flag = False + if (self.Arrhenius_a or self.Arrhenius_b) < 0: + negative_orders_flag = True + + gas_file = """ + + description: |- + Single step mechanism (1S CH4 Westbrook) + + units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + + phases: + - name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + + species: + - name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 + + - name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 + + - name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 + + - name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 + + - name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 + + reactions: + - equation: CH4 + 2 O2 => CO2 + 2 H2O + rate-constant: {A: %f, b: 0, Ea: %f} + orders: {CH4: %f, O2: %f} + negative-orders: %s + """ % ( + self.Arrhenius_A, + self.Arrhenius_Ea, + self.Arrhenius_a, + self.Arrhenius_b, + negative_orders_flag, + ) + return gas_file + + def compute_adiabatic_temperature(self): + """Function computes the adiabatic temperature""" + gas = ct.Solution(yaml=self.get_1S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + return gas.T + + def compute_equilibrate_species_concentration(self, species_names): + """Function computest the equilibrate species concentration""" + gas = ct.Solution(yaml=self.get_1S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + + gas.selected_species = species_names + species_final_mole_fraction = gas.X + + return species_final_mole_fraction + + def compute_ignition_time(self): + """Function computes the ignition temperature""" + gas = ct.Solution(yaml=self.get_1S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + dt = 1e-6 + t_end = 2000 * 1e-3 + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + while sim.time < t_end: + sim.step() + states.append(reactor.thermo.state, t=sim.time) + + ignition_time, ignition_temperature = compute_ignition_stats( + temperature=states.T, time=states.t + ) + + return ignition_time + + +class mech_2S_CH4_Westbrook: + def __init__( + self, + initial_temperature, + initial_pressure, + equivalence_ratio, + Arrhenius_A=2.8e9, + Arrhenius_Ea=48400, + Arrhenius_a=-0.3, + Arrhenius_b=1.3, + ): + self.initial_temperature = initial_temperature + self.initial_pressure = initial_pressure + self.equivalence_ratio = equivalence_ratio + + # Arrhenius parameters (Reaction 1) + self.Arrhenius_A = Arrhenius_A + self.Arrhenius_Ea = Arrhenius_Ea + self.Arrhenius_a = Arrhenius_a + self.Arrhenius_b = Arrhenius_b + + def mech_2S_CH4_Westbrook_combustion(self): + """Function computes the temperature profile for 1S_CH4_MP1 mechanism + references: + """ + gas = ct.Solution(yaml=self.get_2S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + dt = 5e-6 + # t_end = 3e-3 + t_end = 1 + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.atol = 1e-15 + sim.rtol = 1e-6 + + while sim.time < t_end: + try: + sim.step() + states.append(reactor.thermo.state, t=sim.time) + simulation_success = True + except: + sim.atol = sim.atol * 10 + simulation_success = False + if sim.atol > 1e-1: + print( + "Simulation failed at time ; {} at atol : {} for rank : {}".format( + sim.t, sim.atol, rank + ) + ) + break + + # while sim.time < t_end: + # sim.step() + # states.append(reactor.thermo.state, t=sim.time) + + # for t in np.arange(0, t_end, dt): + # print("Time : {0:.5f} [s]".format(t)) + # sim.advance(t) + # states.append(reactor.thermo.state, t=t) + + ignition_time, ignition_temperature = compute_ignition_stats( + temperature=states.T, time=states.t + ) + return states, ignition_time + + def get_2S_CH4_Westbrook_gas_file(self): + """Function returns the gas file for 1S_CH4_MP1 mechanism""" + + gas_file = """ + + description: |- + Two-step mechanism (2S CH4 Westbrook) + + units: {length: cm, time: s, quantity: mol, activation-energy: cal/mol} + + phases: + - name: gas + thermo: ideal-gas + elements: [O, H, C, N, Ar] + species: [O2, H2O, CH4, CO2, N2, CO] + kinetics: gas + transport: mixture-averaged + state: {T: 300.0, P: 1 atm} + + species: + - name: O2 + composition: {O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.78245636, -2.99673416e-03, 9.84730201e-06, -9.68129509e-09, 3.24372837e-12, + -1063.94356, 3.65767573] + - [3.28253784, 1.48308754e-03, -7.57966669e-07, 2.09470555e-10, -2.16717794e-14, + -1088.45772, 5.45323129] + note: TPIS89 + transport: + model: gas + geometry: linear + well-depth: 107.4 + diameter: 3.458 + polarizability: 1.6 + rotational-relaxation: 3.8 + + - name: H2O + composition: {H: 2, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [4.19864056, -2.0364341e-03, 6.52040211e-06, -5.48797062e-09, 1.77197817e-12, + -3.02937267e+04, -0.849032208] + - [3.03399249, 2.17691804e-03, -1.64072518e-07, -9.7041987e-11, 1.68200992e-14, + -3.00042971e+04, 4.9667701] + note: L8/89 + transport: + model: gas + geometry: nonlinear + well-depth: 572.4 + diameter: 2.605 + dipole: 1.844 + rotational-relaxation: 4.0 + + - name: CH4 + composition: {C: 1, H: 4} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [5.14987613, -0.0136709788, 4.91800599e-05, -4.84743026e-08, 1.66693956e-11, + -1.02466476e+04, -4.64130376] + - [0.074851495, 0.0133909467, -5.73285809e-06, 1.22292535e-09, -1.0181523e-13, + -9468.34459, 18.437318] + note: L8/88 + transport: + model: gas + geometry: nonlinear + well-depth: 141.4 + diameter: 3.746 + polarizability: 2.6 + rotational-relaxation: 13.0 + + - name: CO2 + composition: {C: 1, O: 2} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [2.35677352, 8.98459677e-03, -7.12356269e-06, 2.45919022e-09, -1.43699548e-13, + -4.83719697e+04, 9.90105222] + - [3.85746029, 4.41437026e-03, -2.21481404e-06, 5.23490188e-10, -4.72084164e-14, + -4.8759166e+04, 2.27163806] + note: L7/88 + transport: + model: gas + geometry: linear + well-depth: 244.0 + diameter: 3.763 + polarizability: 2.65 + rotational-relaxation: 2.1 + + - name: N2 + composition: {N: 2} + thermo: + model: NASA7 + temperature-ranges: [300.0, 1000.0, 5000.0] + data: + - [3.298677, 1.4082404e-03, -3.963222e-06, 5.641515e-09, -2.444854e-12, + -1020.8999, 3.950372] + - [2.92664, 1.4879768e-03, -5.68476e-07, 1.0097038e-10, -6.753351e-15, + -922.7977, 5.980528] + note: '121286' + transport: + model: gas + geometry: linear + well-depth: 97.53 + diameter: 3.621 + polarizability: 1.76 + rotational-relaxation: 4.0 + + - name: CO + composition: {C: 1, O: 1} + thermo: + model: NASA7 + temperature-ranges: [200.0, 1000.0, 3500.0] + data: + - [3.57953347, -6.1035368e-04, 1.01681433e-06, 9.07005884e-10, -9.04424499e-13, + -1.4344086e+04, 3.50840928] + - [2.71518561, 2.06252743e-03, -9.98825771e-07, 2.30053008e-10, -2.03647716e-14, + -1.41518724e+04, 7.81868772] + note: TPIS79 + transport: + model: gas + geometry: linear + well-depth: 98.1 + diameter: 3.65 + polarizability: 1.95 + rotational-relaxation: 1.8 + + reactions: + - equation: CH4 + 1.5 O2 => CO + 2 H2O # Reaction 1 + rate-constant: {A: %f, b: 0 , Ea: %f} + orders: {CH4: %f, O2: %f} + negative-orders: true + - equation: CO + 0.5 O2 => CO2 # Reaction 2 + rate-constant: {A: 3.98e+14, b: 0 , Ea: 40000.0} + orders: {CO: 1, H2O: 0.5, O2: 0.25} + nonreactant-orders: true + - equation: CO2 => CO + 0.5 O2 # Reaction 3 + rate-constant: {A: 5.0e+8, b: 0, Ea: 40000.0} + orders: {CO2: 1} + """ % ( + self.Arrhenius_A, + self.Arrhenius_Ea, + self.Arrhenius_a, + self.Arrhenius_b, + ) + + return gas_file + + def compute_adiabatic_temperature(self): + """Function computes the adiabatic temperature""" + gas = ct.Solution(yaml=self.get_2S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + return gas.T + + def compute_equilibrate_species_concentration(self, species_names): + """Function computest the equilibrate species concentration""" + gas = ct.Solution(yaml=self.get_2S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + gas.equilibrate("HP") + + gas.selected_species = species_names + species_final_mole_fraction = gas.X + + return species_final_mole_fraction + + def compute_ignition_time(self, internal_state="sim"): + """Function computes the ignition temperature""" + gas = ct.Solution(yaml=self.get_2S_CH4_Westbrook_gas_file()) + gas.TP = self.initial_temperature, self.initial_pressure + gas.set_equivalence_ratio(self.equivalence_ratio, "CH4", "O2:1, N2:3.76") + dt = 1e-6 + if internal_state == "sim": + t_end = 5.0 + elif internal_state == "plot": + t_end = 1e5 + else: + raise ValueError("Invalid internal state") + + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + sim.rtol = 1e-8 + simulation_success = False + + while sim.time < t_end: + try: + sim.step() + states.append(reactor.thermo.state, t=sim.time) + simulation_success = True + except: + sim.atol = sim.atol * 10 + simulation_success = False + if sim.atol > 1e-1: + print( + "Simulation failed at time ; {} at atol : {} for rank : {}".format( + sim.t, sim.atol, rank + ) + ) + break + if simulation_success: + ignition_time, ignition_temperature = compute_ignition_stats( + temperature=states.T, time=states.t + ) + else: + ignition_time = 1e10 + + return ignition_time + + +def compute_ignition_stats(temperature, time): + dt_array = time[1:] - time[0:-1] + temperature_derivative = np.zeros_like(temperature) + temperature_derivative[0] = (temperature[1] - temperature[0]) / dt_array[0] + temperature_derivative[-1] = (temperature[-1] - temperature[-2]) / dt_array[-2] + temperature_derivative[1:-1] = (temperature[2:] - temperature[0:-2]) / ( + dt_array[1:] + dt_array[:-1] + ) + ignition_temperature = temperature[np.argmax(temperature_derivative)] + ignition_time = time[np.argmax(temperature_derivative)] + + return ignition_time, ignition_temperature + + +def main(): + # Begin user input + initial_temperature = 1100 # K + initial_pressure = 100000 # Pa + equivalence_ratio = 1.0 # For transient cases + eval_equivalence_ratio_adiabatic = np.linspace( + 0.5, 1.5, 100 + ) # For equilibrium studies + eval_auto_ignition_temp = np.linspace(1100, 2000, 4) + # eval_auto_ignition_temp = np.array([2500]) + # End user input + + ## GRIMech3.0 combustion model + # gri_eval_species_name = ['CO2', 'CO', 'H2O', 'H2', 'CH4', 'N2', 'O2'] + # tic = time.time() + gri_model = mech_gri(initial_temperature, initial_pressure, equivalence_ratio) + gri_states, gri_ignition_time = gri_model.gri_combustion() + plt.figure() + plt.plot(gri_states.t, gri_states.T) + plt.xscale("log") + plt.show() + # toc = time.time() + # print("Compute time GRIMech3.0 : {0:.5f} [s]".format(toc-tic)) + + # # Compute the adiabatic flame temperature + # gri_species_concentration = np.zeros((len(gri_eval_species_name), eval_equivalence_ratio_adiabatic.shape[0])) + # gri_adiabatic_temp = np.zeros_like(eval_equivalence_ratio_adiabatic) + # for ii, iratio in enumerate(eval_equivalence_ratio_adiabatic): + # gri_model = mech_gri(initial_temperature=initial_temperature, initial_pressure=initial_pressure, equivalence_ratio=iratio) + # gri_adiabatic_temp[ii] = gri_model.compute_adiabatic_temperature() + # gri_species_concentration[:, ii] = gri_model.compute_equilibrate_species_concentration(species_names=gri_eval_species_name) + # gri_species_dict = {} + # for ispecies in range(len(gri_eval_species_name)): + # gri_species_dict[gri_eval_species_name[ispecies]] = gri_species_concentration[ispecies, :] + + # # Computing the ignition temerature + # gri_ignition_time = np.zeros((4, eval_auto_ignition_temp.shape[0])) + # gri_ignition_time[0, :] = eval_auto_ignition_temp + # gri_ignition_time[1, :] = initial_pressure + # gri_ignition_time[2, :] = np.ones(eval_auto_ignition_temp.shape[0]) + # tic = time.time() + # for ii, itemp in enumerate(eval_auto_ignition_temp): + # gri_model = mech_gri(initial_temperature=itemp, initial_pressure=initial_pressure, equivalence_ratio=1.0) + # gri_ignition_time[3, ii] = gri_model.compute_ignition_time() + # np.savetxt("./data/ignition_time/ignition_time_gri_mech.dat", gri_ignition_time.T, delimiter=' ', header = "Vartiables: Inital_temperature, Initial_pressure, Equivalence_ratio, Ignition_time(s)") + # print("Ignition time (GRI) : {}".format(time.time() - tic)) + + # # 1S_CH4_MP1 + # mech_1S_CH4_MP1_eval_species_name = ['CO2', 'H2O','CH4', 'N2', 'O2'] + # tic = time.time() + # model_1S_CH4_MP1 = mech_1S_CH4_MP1(initial_temperature, initial_pressure, equivalence_ratio) + # mech_1S_CH4_MP1_states = model_1S_CH4_MP1.mech_1S_CH4_MP1_combustion() + # toc = time.time() + # print("Compute time 1S_CH4_MP1 : {0:.5f} [s]".format(toc-tic)) + # mech_1S_CH4_MP1_species_concentration = np.zeros((len(mech_1S_CH4_MP1_eval_species_name), eval_equivalence_ratio_adiabatic.shape[0])) + # mech_1S_CH4_MP1_adiabatic_temp= np.zeros_like(eval_equivalence_ratio_adiabatic) + # for ii, iratio in enumerate(eval_equivalence_ratio_adiabatic): + # model_1S_CH4_MP1 = mech_1S_CH4_MP1(initial_temperature=300, initial_pressure=100000, equivalence_ratio=iratio) + # mech_1S_CH4_MP1_adiabatic_temp[ii] = model_1S_CH4_MP1.compute_adiabatic_temperature() + # mech_1S_CH4_MP1_species_concentration[:, ii] = model_1S_CH4_MP1.compute_equilibrate_species_concentration(species_names=mech_1S_CH4_MP1_eval_species_name) + # mech_1S_CH4_MP1_species_dict = {} + # for ispecies in range(len(mech_1S_CH4_MP1_eval_species_name)): + # mech_1S_CH4_MP1_species_dict[mech_1S_CH4_MP1_eval_species_name[ispecies]] = mech_1S_CH4_MP1_species_concentration[ispecies, :] + + # # 1S_CH4_Westbrook + # mech_1S_CH4_Westbrook_eval_species_name = ['CO2', 'H2O','CH4', 'N2', 'O2'] + # tic = time.time() + # model_1S_CH4_Westbrook = mech_1S_CH4_Westbrook(initial_temperature, initial_pressure, equivalence_ratio) + # mech_1S_CH4_Westbrook_states = model_1S_CH4_Westbrook.mech_1S_CH4_Westbrook_combustion() + # toc = time.time() + # print("Compute time 1S_CH4_Westbrook : {0:.5f} [s]".format(toc-tic)) + # mech_1S_CH4_Westbrook_species_concentration = np.zeros((len(mech_1S_CH4_Westbrook_eval_species_name), eval_equivalence_ratio_adiabatic.shape[0])) + # mech_1S_CH4_Westbrook_adiabatic_temp= np.zeros_like(eval_equivalence_ratio_adiabatic) + # for ii, iratio in enumerate(eval_equivalence_ratio_adiabatic): + # model_1S_CH4_Westbrook = mech_1S_CH4_Westbrook(initial_temperature=initial_temperature, initial_pressure=initial_pressure, equivalence_ratio=iratio) + # mech_1S_CH4_Westbrook_adiabatic_temp[ii] = model_1S_CH4_Westbrook.compute_adiabatic_temperature() + # mech_1S_CH4_Westbrook_species_concentration[:, ii] = model_1S_CH4_Westbrook.compute_equilibrate_species_concentration(species_names=mech_1S_CH4_Westbrook_eval_species_name) + # mech_1S_CH4_Westbrook_species_dict = {} + # for ispecies in range(len(mech_1S_CH4_Westbrook_eval_species_name)): + # mech_1S_CH4_Westbrook_species_dict[mech_1S_CH4_Westbrook_eval_species_name[ispecies]] = mech_1S_CH4_Westbrook_species_concentration[ispecies, :] + + # # Computing the ignition temerature + # mech_1S_CH4_Westbrook_ignition_time = np.zeros((4, eval_auto_ignition_temp.shape[0])) + # mech_1S_CH4_Westbrook_ignition_time[0, :] = eval_auto_ignition_temp + # mech_1S_CH4_Westbrook_ignition_time[1, :] = initial_pressure + # mech_1S_CH4_Westbrook_ignition_time[2, :] = np.ones(eval_auto_ignition_temp.shape[0]) + # tic = time.time() + # for ii, itemp in enumerate(eval_auto_ignition_temp): + # model_1S_CH4_Westbrook = mech_1S_CH4_Westbrook(initial_temperature=itemp, initial_pressure=initial_pressure, equivalence_ratio=1.0) + # mech_1S_CH4_Westbrook_ignition_time[3, ii] = model_1S_CH4_Westbrook.compute_ignition_time() + # np.savetxt("./data/ignition_time/ignition_time_1S_CH4_Westbrook_mech.dat", mech_1S_CH4_Westbrook_ignition_time.T, delimiter=' ', header = "Vartiables: Inital_temperature, Initial_pressure, Equivalence_ratio, Ignition_temperature") + # print("Ignition time computation (1S Westbrook) : {}".format(time.time() - tic)) + + # # 2S_CH4_Westbrook + # mech_2S_CH4_Westbrook_eval_species_name = ['CO2', 'H2O','CH4', 'N2', 'O2', 'CO'] + # tic = time.time() + # model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook(initial_temperature, initial_pressure, equivalence_ratio) + # mech_2S_CH4_Westbrook_states, mech_2S_CH4_Westbrook_ignition_time = model_2S_CH4_Westbrook.mech_2S_CH4_Westbrook_combustion() + # toc = time.time() + # print("Compute time 2S_CH4_Westbrook : {0:.5f} [s]".format(toc-tic)) + # mech_2S_CH4_Westbrook_species_concentration = np.zeros((len(mech_2S_CH4_Westbrook_eval_species_name), eval_equivalence_ratio_adiabatic.shape[0])) + # mech_2S_CH4_Westbrook_adiabatic_temp= np.zeros_like(eval_equivalence_ratio_adiabatic) + # for ii, iratio in enumerate(eval_equivalence_ratio_adiabatic): + # model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook(initial_temperature=initial_temperature, initial_pressure=initial_pressure, equivalence_ratio=iratio) + # mech_2S_CH4_Westbrook_adiabatic_temp[ii] = model_2S_CH4_Westbrook.compute_adiabatic_temperature() + # mech_2S_CH4_Westbrook_species_concentration[:, ii] = model_2S_CH4_Westbrook.compute_equilibrate_species_concentration(species_names=mech_2S_CH4_Westbrook_eval_species_name) + + # mech_2S_CH4_Westbrook_species_dict = {} + # for ispecies in range(len(mech_2S_CH4_Westbrook_eval_species_name)): + # mech_2S_CH4_Westbrook_species_dict[mech_2S_CH4_Westbrook_eval_species_name[ispecies]] = mech_2S_CH4_Westbrook_species_concentration[ispecies, :] + + # mech_2S_CH4_Westbrook_ignition_time = np.zeros((4, eval_auto_ignition_temp.shape[0])) + # mech_2S_CH4_Westbrook_ignition_time[0, :] = eval_auto_ignition_temp + # mech_2S_CH4_Westbrook_ignition_time[1, :] = initial_pressure + # mech_2S_CH4_Westbrook_ignition_time[2, :] = np.ones(eval_auto_ignition_temp.shape[0]) + + # tic = time.time() + # for ii, itemp in enumerate(eval_auto_ignition_temp): + # model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook(initial_temperature=itemp, initial_pressure=initial_pressure, equivalence_ratio=1.0) + # mech_2S_CH4_Westbrook_ignition_time[3, ii] = model_2S_CH4_Westbrook.compute_ignition_time() + # print(mech_2S_CH4_Westbrook_ignition_time) + # np.savetxt("./data/ignition_time/ignition_time_2S_CH4_Westbrook_mech.dat", mech_2S_CH4_Westbrook_ignition_time.T, delimiter=' ', header = "Vartiables: Inital_temperature, Initial_pressure, Equivalence_ratio, Ignition_temperature") + # print("Ignition time computation (2S Westbrook) : {}".format(time.time() - tic)) + + if "--ignitionplot" in sys.argv: + fig, axs = plt.subplots(figsize=(10, 6)) + axs.scatter( + 1000 / eval_auto_ignition_temp, + mech_1S_CH4_Westbrook_ignition_time[1, :], + label=r"1-step mechanism [Westbrook \textit{et al.}]", + color="r", + marker="^", + s=60, + ) + axs.scatter( + 1000 / eval_auto_ignition_temp, + mech_2S_CH4_Westbrook_ignition_time[1, :], + label=r"2-step mechanism [Westbrook \textit{el al.}]", + color="b", + marker="D", + s=60, + ) + axs.scatter( + 1000 / eval_auto_ignition_temp, + gri_ignition_time[1, :], + label="Gri-Mech 3.0", + color="k", + marker="s", + s=60, + ) + axs.set_xlabel(r"1000/T [1/K]") + axs.set_ylabel(r"$t_{ign}$ [s]") + axs.legend(loc="upper left") + axs.set_yscale("log") + axs.grid(color="k", alpha=0.5) + plt.tight_layout() + plt.savefig( + "ignition_time_phi_{}_pressure_{}.png".format(1.0, initial_pressure) + ) + plt.close() + + if "--equilibrateplot" in sys.argv: + fig, axs = plt.subplots(2, 2, figsize=(18, 10)) + fig.tight_layout(h_pad=5, w_pad=5) + axs[0, 0].plot( + eval_equivalence_ratio_adiabatic, + mech_1S_CH4_Westbrook_adiabatic_temp, + color="r", + label=r"1-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[0, 0].plot( + eval_equivalence_ratio_adiabatic, + mech_2S_CH4_Westbrook_adiabatic_temp, + color="b", + label=r"2-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[0, 0].plot( + eval_equivalence_ratio_adiabatic, + gri_adiabatic_temp, + color="k", + label="Gri-Mech 3.0", + ) + axs[0, 0].set_xlabel(r"Equivalence ratio, $\phi$") + axs[0, 0].set_ylabel(r"Adiabatic temperature [K]") + axs[0, 0].set_ylim([1400, 2400]) + axs[0, 0].set_xlim([0.5, 1.5]) + axs[0, 0].tick_params(pad=10) + # axs[0, 0].legend(loc="lower right") + axs[0, 0].xaxis.set_major_locator(MultipleLocator(0.5)) + axs[0, 0].xaxis.set_minor_locator(MultipleLocator(0.25)) + axs[0, 0].yaxis.set_major_locator(MultipleLocator(200)) + axs[0, 0].grid(which="major", color="k", alpha=0.5) + axs[0, 0].grid(which="minor", color="grey", alpha=0.3) + + axs[0, 1].plot( + eval_equivalence_ratio_adiabatic, + mech_1S_CH4_Westbrook_species_dict["CH4"], + color="r", + label=r"1-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[0, 1].plot( + eval_equivalence_ratio_adiabatic, + mech_2S_CH4_Westbrook_species_dict["CH4"], + color="b", + label=r"2-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[0, 1].plot( + eval_equivalence_ratio_adiabatic, + gri_species_dict["CH4"], + color="k", + label="Gri-Mech 3.0", + ) + axs[0, 1].set_xlim([0.5, 1.5]) + axs[0, 1].set_yscale("log") + axs[0, 1].xaxis.set_major_locator(MultipleLocator(0.5)) + axs[0, 1].xaxis.set_minor_locator(MultipleLocator(0.25)) + axs[0, 1].grid(which="major", color="k", alpha=0.5) + axs[0, 1].grid(which="minor", color="grey", alpha=0.3) + axs[0, 1].set_xlabel(r"Equivalence ratio, $\phi$") + axs[0, 1].set_ylabel(r"$[\textrm{CH}_{4}]$") + # axs[0, 1].legend(loc="lower right") + + axs[1, 0].plot( + eval_equivalence_ratio_adiabatic, + mech_1S_CH4_Westbrook_species_dict["CO2"], + color="r", + label=r"1-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[1, 0].plot( + eval_equivalence_ratio_adiabatic, + mech_2S_CH4_Westbrook_species_dict["CO2"], + color="b", + label=r"2-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[1, 0].plot( + eval_equivalence_ratio_adiabatic, + gri_species_dict["CO2"], + color="k", + label="Gri-Mech 3.0", + ) + axs[1, 0].set_xlim([0.5, 1.5]) + axs[1, 0].set_ylim([0, 0.1]) + axs[1, 0].xaxis.set_major_locator(MultipleLocator(0.5)) + axs[1, 0].xaxis.set_minor_locator(MultipleLocator(0.25)) + axs[1, 0].yaxis.set_minor_locator(MultipleLocator(0.025)) + axs[1, 0].grid(which="major", color="k", alpha=0.5) + axs[1, 0].grid(which="minor", color="grey", alpha=0.3) + axs[1, 0].set_xlabel(r"Equivalence ratio, $\phi$") + axs[1, 0].set_ylabel(r"$[\textrm{CO}_{2}]$") + # axs[1, 0].legend(loc="lower right") + + # axs[1, 1].plot(eval_equivalence_ratio_adiabatic, mech_1S_CH4_Westbrook_species_dict['CO'], color="r", label=r"1-step mechanism [Westbrook $\textit{et al.}$]") + axs[1, 1].plot( + eval_equivalence_ratio_adiabatic, + mech_2S_CH4_Westbrook_species_dict["CO"], + color="b", + label=r"2-step mechanism [Westbrook $\textit{et al.}$]", + ) + axs[1, 1].plot( + eval_equivalence_ratio_adiabatic, + gri_species_dict["CO"], + color="k", + label="Gri-Mech 3.0", + ) + axs[1, 1].set_xlim([0.5, 1.5]) + axs[1, 1].set_ylim([0, 0.15]) + axs[1, 1].xaxis.set_major_locator(MultipleLocator(0.5)) + axs[1, 1].xaxis.set_minor_locator(MultipleLocator(0.25)) + axs[1, 1].yaxis.set_minor_locator(MultipleLocator(0.025)) + axs[1, 1].grid(which="major", color="k", alpha=0.5) + axs[1, 1].grid(which="minor", color="grey", alpha=0.3) + axs[1, 1].set_xlabel(r"Equivalence ratio, $\phi$") + axs[1, 1].set_ylabel(r"$[\textrm{CO}]$") + # axs[1, 1].legend(loc="lower right") + + handles, labels = axs[0, 0].get_legend_handles_labels() + fig.legend(handles, labels, bbox_to_anchor=(0.34, 0.5, 0.5, 0.5), ncol=3) + + plt.subplots_adjust(top=0.9, bottom=0.15, left=0.1) + plt.savefig( + "equilibrate_initial_temp_{}_pressure_{}.png".format( + initial_temperature, initial_pressure + ) + ) + plt.close() + + if "--transientplot" in sys.argv: + fig, axs = plt.subplots(2, 2, figsize=(18, 10)) + fig.tight_layout(h_pad=5, w_pad=5) + axs[0, 0].plot( + gri_states.t * 1000, gri_states.T, color="k", label="Gri-Mech3.0" + ) + # axs[0, 0].plot(mech_1S_CH4_MP1_states.t*1000, mech_1S_CH4_MP1_states.T, color="r", label="1S_CH4_MP1") + # axs[0, 0].plot(mech_1S_CH4_Westbrook_states.t*1000, mech_1S_CH4_Westbrook_states.T, color="g", label="1S_CH4_Westbrook") + axs[0, 0].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states.T, + color="b", + label="2S_CH4_Westbrook", + ) + axs[0, 0].set_ylim([1400, 3500]) + # axs[0, 0].set_xlim([0.0000007, 0.003]) + axs[0, 0].set_xlabel("time [ms]") + axs[0, 0].set_ylabel("Temperature [K]") + # axs[0, 0].set_xscale("log") + axs[0, 0].xaxis.set_major_locator(MultipleLocator(1)) + axs[0, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 0].grid() + + axs[0, 1].plot( + gri_states.t * 1000, gri_states("CH4").X, color="k", label="Gri-Mech3.0" + ) + # axs[0, 1].plot(mech_1S_CH4_MP1_states.t*1000, mech_1S_CH4_MP1_states('CH4').X, color="r", label="1S_CH4_MP1") + # axs[0, 1].plot(mech_1S_CH4_Westbrook_states.t*1000, mech_1S_CH4_Westbrook_states('CH4').X, color="g", label="1S_CH4_Westbrook") + axs[0, 1].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("CH4").X, + color="b", + label="2S_CH4_Westbrook", + ) + axs[0, 1].set_ylim([0, 0.1]) + # axs[0, 1].set_xlim([0.0000007, 0.003]) + axs[0, 1].set_xlabel("time [ms]") + axs[0, 1].set_ylabel("[$CH_{4}$]") + axs[0, 1].xaxis.set_major_locator(MultipleLocator(1)) + axs[0, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + # axs[0, 1].set_xscale("log") + axs[0, 1].grid() + + axs[1, 0].plot( + gri_states.t * 1000, gri_states("O2").X, color="k", label="Gri-Mech3.0" + ) + # axs[1, 0].plot(mech_1S_CH4_MP1_states.t*1000, mech_1S_CH4_MP1_states('O2').X, color="r", label="1S_CH4_MP1") + # axs[1, 0].plot(mech_1S_CH4_Westbrook_states.t*1000, mech_1S_CH4_Westbrook_states('O2').X, color="g", label="1S_CH4_Westbrook") + axs[1, 0].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("O2").X, + color="b", + label="2S_CH4_Westbrook", + ) + axs[1, 0].set_ylim([0, 0.2]) + # axs[1, 0].set_xlim([0.0000007, 0.003]) + axs[1, 0].set_xlabel("time [ms]") + axs[1, 0].set_ylabel("[$O_{2}$]") + # axs[1, 0].set_xscale("log") + axs[1, 0].xaxis.set_major_locator(MultipleLocator(1)) + axs[1, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 0].grid() + + axs[1, 1].plot( + gri_states.t * 1000, gri_states("CO2").X, color="k", label="Gri-Mech3.0" + ) + # axs[1, 1].plot(mech_1S_CH4_MP1_states.t*1000, mech_1S_CH4_MP1_states('CO2').X, color="r", label="1S_CH4_MP1") + # axs[1, 1].plot(mech_1S_CH4_Westbrook_states.t*1000, mech_1S_CH4_Westbrook_states('CO2').X, color="g", label="1S_CH4_Westbrook") + axs[1, 1].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("CO2").X, + color="b", + label="2S_CH4_Westbrook", + ) + axs[1, 1].set_ylim([0, 0.1]) + # axs[1, 1].set_xlim([0.0000007, 0.003]) + axs[1, 1].set_xlabel("time [ms]") + axs[1, 1].set_ylabel("[$CO_{2}$]") + # axs[1, 1].set_xscale("log") + axs[1, 1].xaxis.set_major_locator(MultipleLocator(1)) + axs[1, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 1].grid() + + handles, labels = axs[0, 0].get_legend_handles_labels() + fig.legend(handles, labels, bbox_to_anchor=(0.49, 0.5, 0.5, 0.5), ncol=4) + plt.subplots_adjust(top=0.9, bottom=0.15, left=0.15) + plt.savefig( + "methane_combustion_t_{}_p_{}.png".format( + initial_temperature, initial_pressure + ) + ) + plt.close() + + if "--temperatureplot" in sys.argv: + fig, axs = plt.subplots(figsize=(15, 8)) + # fig.tight_layout(h_pad=3, w_pad=3) + axs.axvline( + x=gri_ignition_time * 1000, + linestyle="--", + color="g", + linewidth=5, + label=r"$t_{ign},$ Gri-Mech 3.0", + ) + axs.axvline( + x=mech_2S_CH4_Westbrook_ignition_time * 1000, + linestyle="--", + color="b", + linewidth=5, + label=r"$t_{ign}$, 2-step mechanism [Westbrook \textit{et al.}]", + ) + axs.plot( + gri_states.t * 1000, gri_states.T, "-D", color="k", label="Gri-Mech 3.0" + ) + axs.plot( + gri_states.t * 1000, + mech_2S_CH4_Westbrook_states.T, + color="r", + label=r"2-step mechanism [Westbrook \textit{et al.}]", + ) + axs.yaxis.set_minor_locator(MultipleLocator(250)) + axs.yaxis.set_major_locator(MultipleLocator(500)) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.set_xscale("log") + axs.set_ylim([1499, 3500]) + axs.set_xlim([0, 3]) + axs.set_xlabel(r"time [ms]") + axs.set_ylabel(r"Temperature [K]") + handles, labels = axs.get_legend_handles_labels() + fig.legend( + handles, labels, bbox_to_anchor=(0.37, 0.48, 0.5, 0.5), ncol=2, fontsize=20 + ) + plt.subplots_adjust(top=0.83, bottom=0.15, left=0.1) + + plt.savefig("methane_temp_ignition.png") + plt.close() + + if "--combinedplot" in sys.argv: + fig, axs = plt.subplots(2, 3, figsize=(19, 8)) + fig.tight_layout(h_pad=3, w_pad=3) + # axs[0, 0].plot(gri_states.t*1000, gri_states.T, '-D', color="k", label="Gri-Mech 3.0") + axs[0, 1].plot( + gri_states.t * 1000, + gri_states("CO2").X, + "-D", + color="k", + label="Gri-Mech 3.0", + ) + axs[0, 1].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("CO2").X, + color="r", + label=r"2-step mechanism [Westbrook \textit{et al.}]", + ) + axs[0, 1].set_xlabel(r"time [ms]") + axs[0, 1].set_ylabel(r"[$CO_{2}$]") + axs[0, 1].set_ylim([0, 0.1]) + axs[0, 1].set_xscale("log") + axs[0, 1].set_xlim([0, 3]) + axs[0, 1].yaxis.set_minor_locator(MultipleLocator(0.025)) + axs[0, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 1].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 1].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[0, 0].plot( + gri_states.t * 1000, + gri_states("CH4").X, + "-D", + color="k", + label="Gri-Mech 3.0", + ) + axs[0, 0].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("CH4").X, + color="r", + label=r"2-step mechanism [Westbrook \textit{et al.}", + ) + axs[0, 0].set_xlabel(r"time [ms]") + axs[0, 0].set_ylabel(r"[$CH_{4}$]") + axs[0, 0].set_ylim([0, 0.1]) + axs[0, 0].set_xscale("log") + axs[0, 0].set_xlim([0, 3]) + axs[0, 0].yaxis.set_minor_locator(MultipleLocator(0.025)) + axs[0, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 0].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 0].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[0, 2].plot( + gri_states.t * 1000, gri_states("O2").X, "-D", color="k", label="O2" + ) + axs[0, 2].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("O2").X, + color="r", + label="O2", + ) + axs[0, 2].set_xlabel(r"time [ms]") + axs[0, 2].set_ylabel(r"[$O_2$]") + axs[0, 2].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 2].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs[0, 2].set_ylim([0, 0.2]) + axs[0, 2].set_xscale("log") + axs[0, 2].set_xlim([0, 3]) + axs[0, 2].yaxis.set_minor_locator(MultipleLocator(0.05)) + axs[0, 2].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 2].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 2].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[1, 0].plot( + gri_states.t * 1000, gri_states("CO").X, "-D", color="k", label="CO" + ) + axs[1, 0].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("CO").X, + color="r", + label="CO", + ) + axs[1, 0].set_xlabel(r"time [ms]") + axs[1, 0].set_ylabel(r"[$CO$]") + axs[1, 0].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 0].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs[1, 0].set_ylim([0, 0.08]) + axs[1, 0].set_xscale("log") + axs[1, 0].set_xlim([0, 3]) + axs[1, 0].yaxis.set_minor_locator(MultipleLocator(0.02)) + axs[1, 0].yaxis.set_major_locator(MultipleLocator(0.04)) + axs[1, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 0].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 0].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[1, 1].plot( + gri_states.t * 1000, gri_states("H2O").X, "-D", color="k", label="H2O" + ) + axs[1, 1].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("H2O").X, + color="r", + label="H2O", + ) + axs[1, 1].set_xlabel(r"time [ms]") + axs[1, 1].set_ylabel(r"[$H_{2}O$]") + axs[1, 1].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 1].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs[1, 1].set_ylim([0, 0.2]) + axs[1, 1].set_xscale("log") + axs[1, 1].set_xlim([0, 3]) + axs[1, 1].yaxis.set_minor_locator(MultipleLocator(0.05)) + axs[1, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + + axs[1, 2].plot( + gri_states.t * 1000, gri_states("N2").X, "-D", color="k", label="N2" + ) + axs[1, 2].plot( + mech_2S_CH4_Westbrook_states.t * 1000, + mech_2S_CH4_Westbrook_states("N2").X, + color="r", + label="N2", + ) + axs[1, 2].set_xlabel(r"time [ms]") + axs[1, 2].set_ylabel(r"[$N_{2}$]") + axs[1, 2].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 2].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs[1, 2].set_ylim([0.65, 0.75]) + axs[1, 2].set_xscale("log") + axs[1, 2].set_xlim([0, 3]) + axs[1, 2].yaxis.set_minor_locator(MultipleLocator(0.025)) + axs[1, 2].xaxis.set_minor_locator(MultipleLocator(0.5)) + handles, labels = axs[0, 0].get_legend_handles_labels() + fig.legend(handles, labels, bbox_to_anchor=(0.18, 0.5, 0.5, 0.5), ncol=2) + # axs[1].plot(gri_states.t*1000, gri_states('O2').X, 'D', color="C1", label="O2") + # axs[1].plot(gri_states.t*1000, gri_states('CO').X, 'D',color="C2", label="CO") + # axs[1].plot(gri_states.t*1000, gri_states('H2O').X, 'D',color="C3", label="H2O") + + # axs[1].plot(mech_2S_CH4_Westbrook_states.t*1000, mech_2S_CH4_Westbrook_states('O2').X, color="C1", label="O2") + # axs[1].plot(mech_2S_CH4_Westbrook_states.t*1000, mech_2S_CH4_Westbrook_states('CO').X, color="C2", label="CO") + # axs[1].plot(mech_2S_CH4_Westbrook_states.t*1000, mech_2S_CH4_Westbrook_states('H2O').X, color="C3", label="H2O") + + # axs[1].legend() + # plt.tight_layout() + plt.subplots_adjust(top=0.9, bottom=0.15, left=0.1) + plt.savefig("methane_combustion.png") + plt.close() + + +if __name__ == ("__main__"): + main() diff --git a/examples/ignition_model/forward_model/n_dodecane_ignition/generate_custom_gas_input.py b/examples/ignition_model/forward_model/n_dodecane_ignition/generate_custom_gas_input.py new file mode 100644 index 0000000..0b9cf92 --- /dev/null +++ b/examples/ignition_model/forward_model/n_dodecane_ignition/generate_custom_gas_input.py @@ -0,0 +1,270 @@ +import yaml +from mpi4py import MPI +import os + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +def gen_gas_file( + reaction_1_pre_exp_factor, reaction_1_activation_energy, campaign_path=None +): + data = { + "description": "Custom reaction for n-dodecane combustion", + "units": { + "length": "cm", + "time": "s", + "quantity": "mol", + "activation-energy": "cal/mol", + }, + "phases": [ + { + "name": "gas", + "thermo": "ideal-gas", + "elements": ["O", "H", "C", "N", "Ar"], + "species": ["C12H26", "O2", "CO", "H2O", "CO2"], + "kinetics": "gas", + "transport": "mixture-averaged", + "state": {"T": 300.0, "P": "1 atm"}, + } + ], + "species": [ + { + "name": "O2", + "composition": {"O": 2}, + "thermo": { + "model": "NASA7", + "temperature-ranges": [200.0, 1000.0, 3500.0], + "data": [ + [ + 3.78245636, + -2.99673416e-03, + 9.84730201e-06, + -9.68129509e-09, + 3.24372837e-12, + -1063.94356, + 3.65767573, + ], + [ + 3.28253784, + 1.48308754e-03, + -7.57966669e-07, + 2.09470555e-10, + -2.16717794e-14, + -1088.45772, + 5.45323129, + ], + ], + "note": "TPIS89", + }, + "transport": { + "model": "gas", + "geometry": "linear", + "well-depth": 107.4, + "diameter": 3.458, + "polarizability": 1.6, + "rotational-relaxation": 3.8, + }, + }, + { + "name": "H2O", + "composition": {"H": 2, "O": 1}, + "thermo": { + "model": "NASA7", + "temperature-ranges": [200.0, 1000.0, 3500.0], + "data": [ + [ + 4.19864056, + -2.0364341e-03, + 6.52040211e-06, + -5.48797062e-09, + 1.77197817e-12, + -3.02937267e04, + -0.849032208, + ], + [ + 3.03399249, + 2.17691804e-03, + -1.64072518e-07, + -9.7041987e-11, + 1.68200992e-14, + -3.00042971e04, + 4.9667701, + ], + ], + "note": "L8/89", + }, + "transport": { + "model": "gas", + "geometry": "nonlinear", + "well-depth": 572.4, + "diameter": 2.605, + "dipole": 1.844, + "rotational-relaxation": 4.0, + }, + }, + { + "name": "CO", + "composition": {"C": 1, "O": 1}, + "thermo": { + "model": "NASA7", + "temperature-ranges": [200.0, 1000.0, 3500.0], + "data": [ + [ + 3.57953347, + -6.1035368e-04, + 1.01681433e-06, + 9.07005884e-10, + -9.04424499e-13, + -1.4344086e04, + 3.50840928, + ], + [ + 2.71518561, + 2.06252743e-03, + -9.98825771e-07, + 2.30053008e-10, + -2.03647716e-14, + -1.41518724e04, + 7.81868772, + ], + ], + "note": "TPIS79", + }, + "transport": { + "model": "gas", + "geometry": "linear", + "well-depth": 98.1, + "diameter": 3.65, + "polarizability": 1.95, + "rotational-relaxation": 1.8, + }, + }, + { + "name": "CO2", + "composition": {"C": 1, "O": 2}, + "thermo": { + "model": "NASA7", + "temperature-ranges": [200.0, 1000.0, 3500.0], + "data": [ + [ + 2.35677352, + 8.98459677e-03, + -7.12356269e-06, + 2.45919022e-09, + -1.43699548e-13, + -4.83719697e04, + 9.90105222, + ], + [ + 3.85746029, + 4.41437026e-03, + -2.21481404e-06, + 5.23490188e-10, + -4.72084164e-14, + -4.8759166e04, + 2.27163806, + ], + ], + "note": "L7/88", + }, + "transport": { + "model": "gas", + "geometry": "linear", + "well-depth": 244.0, + "diameter": 3.763, + "polarizability": 2.65, + "rotational-relaxation": 2.1, + }, + }, + { + "name": "C12H26", + "composition": {"C": 12, "H": 26}, + "thermo": { + "model": "NASA7", + "temperature-ranges": [300.0, 1391.0, 5000.0], + "data": [ + [ + -2.62182000e00, + 1.47238000e-01, + -9.43970000e-05, + 3.07441000e-08, + -4.03602000e-12, + -4.00654000e04, + 5.00995000e01, + ], + [ + 3.85095000e01, + 5.63550000e-02, + -1.91493000e-05, + 2.96025000e-09, + -1.71244000e-13, + -5.48843000e04, + -1.72671000e02, + ], + ], + }, + "transport": { + "model": "gas", + "geometry": "nonlinear", + "well-depth": 789.980, + "diameter": 7.047, + "polarizability": 0.0, + "rotational-relaxation": 1.0, + }, + }, + ], + "reactions": [ + { + "equation": "C12H26 + 12.5 O2 => 12 CO + 13 H2O", + "rate-constant": { + "A": reaction_1_pre_exp_factor, + "b": 0.0, + "Ea": reaction_1_activation_energy, + }, + "orders": {"C12H26": 0.25, "O2": 1.25}, + }, + { + "equation": "CO + 0.5 O2 => CO2", + "rate-constant": { + "A": 3.98e14, + "b": 0.0, + "Ea": 40000.0, + }, + "orders": {"CO": 1.0, "H2O": 0.5, "O2": 0.25}, + "nonreactant-orders": True, + }, + { + "equation": "CO2 => CO + 0.5 O2", + "rate-constant": { + "A": 5.0e8, + "b": 0.0, + "Ea": 40000.0, + }, + "orders": { + "CO2": 1.0, + }, + }, + ], + } + if campaign_path is not None: + file_path = os.path.join( + campaign_path, "custom_gas_rank_" + str(rank) + ".yaml" + ) + else: + file_path = "./custom_gas_rank_" + str(rank) + ".yaml" + + with open(file_path, "w") as file: + yaml.dump(data, file, sort_keys=False, default_flow_style=False) + + +def main(): + gen_gas_file( + reaction_1_pre_exp_factor=24464314506.42409, + reaction_1_activation_energy=31944.0, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/ignition_model/forward_model/n_dodecane_ignition/n_dodecane_ignition.py b/examples/ignition_model/forward_model/n_dodecane_ignition/n_dodecane_ignition.py new file mode 100644 index 0000000..5c0ce15 --- /dev/null +++ b/examples/ignition_model/forward_model/n_dodecane_ignition/n_dodecane_ignition.py @@ -0,0 +1,195 @@ +import numpy as np +import cantera as ct +import matplotlib.pyplot as plt +from generate_custom_gas_input import gen_gas_file +from mpi4py import MPI +import os + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) + + +def n_dodecane_combustion( + equivalence_ratio, + initial_temp, + pre_exponential_parameter=27.38, + activation_energy=31944.0, + initial_fuel_moles=1, + campaign_path=None, + save_plot=False, +): + # Compute the pre-exponential factor + pre_exponential_factor = compute_pre_exponential_factor( + equivalence_ratio=equivalence_ratio, + initial_temp=initial_temp, + pre_exponential_parameter=pre_exponential_parameter, + ) + # Update the gas file + gen_gas_file( + reaction_1_pre_exp_factor=float(pre_exponential_factor), + reaction_1_activation_energy=float(activation_energy), + campaign_path=campaign_path, + ) + + # Initial_conc + moles_fuel = initial_fuel_moles + moles_oxygen = moles_fuel / (equivalence_ratio * (1 / 18.5)) + + # Initialize Model + if campaign_path is not None: + file_path = os.path.join( + campaign_path, "custom_gas_rank_" + str(rank) + ".yaml" + ) + else: + file_path = "./custom_gas_rank_" + str(rank) + ".yaml" + gas = ct.Solution(file_path) + # # Assign initial concentration + gas.X = {"C12H26": moles_fuel, "O2": moles_oxygen} + gas.TP = initial_temp, 101325 + # gas.TP = initial_temp, 6000000 + + # Reactor + t_end = 1.0 * 1e-3 + reactor = ct.IdealGasConstPressureReactor(gas) + sim = ct.ReactorNet([reactor]) + states = ct.SolutionArray(gas, extra=["t"]) + + sim.atol = 1e-15 + sim.rtol = 1e-6 + + # Adaptive stepping + # while sim.time < t_end: + # print("Time : {0:.5f} [ms]".format(sim.time*1000)) + # sim.step() + # states.append(reactor.thermo.state, t=sim.time) + + # Fixed step size + dt = 9e-7 + for t in np.arange(0, t_end, dt): + # print("Time : {0:.5f} [ms]".format(sim.time*1000)) + sim.advance(t) + states.append(reactor.thermo.state, t=t) + + # Ignition timing + auto_ignition_time, auto_ignition_temp = compute_auto_ignition_time( + temperature=states.T, time=states.t, dt=dt + ) + + # """ + if save_plot: + fig, axs = plt.subplots(3, 2, figsize=(15, 8)) + axs[0, 0].plot(states.t, states.T, color="red", lw=2) + axs[0, 0].scatter( + auto_ignition_time, + auto_ignition_temp, + marker="s", + c="k", + s=30, + label="Auto ignition", + ) + axs[0, 0].set_xlabel("time (s)") + axs[0, 0].set_ylabel(r"Temperature [K]", labelpad=20) + axs[0, 0].legend(loc="lower right") + axs[0, 0].set_xscale("log") + + axs[0, 1].plot(states.t, states("C12H26").X, color="r", lw=2) + axs[0, 1].set_xlabel("time (s)") + axs[0, 1].set_ylabel(r"$C_{12}H_{26}$", labelpad=20) + axs[0, 1].set_xscale("log") + + axs[1, 0].plot(states.t, states("O2").X, color="r", lw=2) + axs[1, 0].set_xlabel("time (s)") + axs[1, 0].set_ylabel(r"$O_{2}$", labelpad=20) + axs[1, 0].set_xscale("log") + + axs[1, 1].plot(states.t, states("H2O").X, color="r", lw=2) + axs[1, 1].set_xlabel("time (s)") + axs[1, 1].set_ylabel(r"$H_{2}O$", labelpad=20) + axs[1, 1].set_xscale("log") + + axs[2, 0].plot(states.t, states("CO").X, color="r", lw=2) + axs[2, 0].set_xlabel("time (s)") + axs[2, 0].set_ylabel(r"$CO$", labelpad=20) + axs[2, 0].set_xscale("log") + + axs[2, 1].plot(states.t, states("CO2").X, color="r", lw=2) + axs[2, 1].set_xlabel("time (s)") + axs[2, 1].set_ylabel(r"$CO_{2}$", labelpad=20) + axs[2, 1].set_xscale("log") + + plt.tight_layout() + figure_path = os.path.join( + campaign_path, + "true_n_dodecane_combustion_T_{}_phi_{}_rank_{}.png".format( + initial_temp, equivalence_ratio, rank + ), + ) + plt.savefig(figure_path) + plt.close() + # """ + + return auto_ignition_time + + +def compute_auto_ignition_time(temperature, time, dt): + """Function computes the ignition time via derivatives""" + derivative = np.zeros(temperature.shape[0]) + derivative[1:-1] = (temperature[2:] - temperature[0:-2]) / (2 * dt) + derivative[0] = (temperature[1] - temperature[0]) / dt + derivative[-1] = (temperature[-1] - temperature[-2]) / dt + arg_max = np.argmax(derivative) + return time[arg_max], temperature[arg_max] + + +# def compute_ignition_time(states, t_end): +# if states.t[states.T>1500].shape[0] == 0: +# ignition_time = t_end +# else: +# ignition_time = states.t[states.T>1500][0] +# return ignition_time + + +def compute_pre_exponential_factor( + pre_exponential_parameter, equivalence_ratio=0.5, initial_temp=300 +): + """Function computes the pre_exponential_factor""" + lambda_0 = pre_exponential_parameter + lambda_1 = -2.13 + lambda_2 = -2.05 + lambda_3 = 1.89 + lambda_4 = -1.0 * 10 ** (-2) + lambda_5 = 2.87 * 10 ** (-4) + lambda_6 = 8.43 + + log_pre_exponential_factor = ( + lambda_0 + + (lambda_1 * np.exp(lambda_2 * equivalence_ratio)) + + lambda_3 + * np.tanh( + ((lambda_4 + (lambda_5 * equivalence_ratio)) * initial_temp) + lambda_6 + ) + ) + + return np.exp(log_pre_exponential_factor) + + +def main(): + # Begin user input + equivalence_ratio = 0.5 + initial_temp = 1000 # K + # End user input + + n_dodecane_combustion( + equivalence_ratio=equivalence_ratio, + initial_temp=initial_temp, + pre_exponential_parameter=30.0, + activation_energy=35000.0, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/ignition_model/learning_model/config.yaml b/examples/ignition_model/learning_model/config.yaml new file mode 100644 index 0000000..1e7c940 --- /dev/null +++ b/examples/ignition_model/learning_model/config.yaml @@ -0,0 +1,27 @@ +# Campaign +campaign_id: 4 + +# Forward model parameters + +# Learning model parameters +n_parameter_model: 3 +data_file: ignition_time_gri_mech.dat # Used to generate the training data + +# data_file: ytrain.npy +model_noise_cov: 1.0e-1 +objective_scaling: 1.0e-12 + +# Leaning model +compute_map: False +compute_mle: False +compute_mcmc: False + +# Identifiability +compute_identifiability: False +compute_sobol_index: True +global_num_outer_samples: 10 +global_num_inner_samples: 5 +restart_identifiability: False + +# MCMC +num_mcmc_samples: 50000 diff --git a/examples/ignition_model/learning_model/create_new_campaign.sh b/examples/ignition_model/learning_model/create_new_campaign.sh new file mode 100755 index 0000000..7e34e3e --- /dev/null +++ b/examples/ignition_model/learning_model/create_new_campaign.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +campaign_results_path=$PWD/campaign_results +dname=$campaign_results_path/campaign_${1} + +if [[ -d $dname ]]; then + + read -p "Campaign already exists! Recreate it? [y/yes/Y] : " flag + + if [[ $flag == "y" || $flag == "yes" || $flag == "Y" ]]; then + + rm -rf $dname + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" (Re)created using config.yaml" + + # Generating the data from using config file + python generate_ignition_delay_training_data.py ${1} + + else + + echo "Campaign "${1}" was not removed" + + fi + +else + + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" created using config.yaml" + + # Generating the data from using config file + python generate_ignition_delay_training_data.py ${1} + + +fi diff --git a/examples/ignition_model/learning_model/generate_ignition_delay_training_data.py b/examples/ignition_model/learning_model/generate_ignition_delay_training_data.py new file mode 100644 index 0000000..0481ab8 --- /dev/null +++ b/examples/ignition_model/learning_model/generate_ignition_delay_training_data.py @@ -0,0 +1,96 @@ +import numpy as np +import yaml +import os +import matplotlib.pyplot as plt +from matplotlib.ticker import MultipleLocator, AutoMinorLocator +import sys + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) +plt.rc("legend", fontsize=18, framealpha=1.0) +plt.rc("xtick.major", size=6) +plt.rc("xtick.minor", size=4) +plt.rc("ytick.major", size=6) +plt.rc("ytick.minor", size=4) + +no_noise_data_path = "./ignition_delay_data" + +# Load the yaml data +with open("./config.yaml", "r") as config_file: + config_data = yaml.safe_load(config_file) + +campaign_id = config_data["campaign_id"] +assert campaign_id == int(sys.argv[1]), "Make sure the campaign id match" +model_noise_cov = config_data["model_noise_cov"] +data_file = config_data["data_file"] + +# Load the data +no_noise_data = np.loadtxt(os.path.join(no_noise_data_path, data_file)) +num_data_points = no_noise_data.shape[0] + +training_data = np.copy(no_noise_data) + +# Adding noise +noise = np.sqrt(model_noise_cov) * np.random.randn(num_data_points) +training_data[:, -1] = np.log(training_data[:, -1]) + noise +data_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/training_data.npy".format(campaign_id) +) +np.save(data_save_path, training_data) + +fig_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/Figures/training_data.png".format(campaign_id) +) +plt.figure(figsize=(8, 5)) +plt.scatter( + 1000 / training_data[:, 0], + np.log(no_noise_data[:, -1]), + label="True", + c="k", + marker="s", + s=50, +) +plt.scatter( + 1000 / training_data[:, 0], + training_data[:, -1], + label="Data", + c="r", + marker="D", + s=50, +) +plt.xlabel(r"1000/T [1/K]") +plt.ylabel(r"$\log(t_{ign})$") +plt.legend(loc="lower right") +plt.title("Training data") +plt.grid(color="k", alpha=0.5) +plt.tight_layout() +plt.savefig(fig_save_path) +plt.close() + + +# # Add noise +# spatial_resolution = no_noise_data.shape[1] +# noise = np.sqrt(model_noise_cov)*np.random.randn(num_data_points*spatial_resolution).reshape(num_data_points, -1) +# training_data = np.copy(no_noise_data) +# training_data[:, 1:] += noise[:, 1:] +# save_path = os.path.join("./campaign_results", "campaign_{0:d}/ytrain.npy".format(campaign_id)) +# np.save(save_path, training_data) + +# fig_save_path = os.path.join("./campaign_results", "campaign_{0:d}/Figures/training_data.png".format(campaign_id)) +# plt.figure(figsize=(10, 5)) +# fig, axs = plt.subplots(figsize=(10, 5)) +# colors=["C{}".format(ii) for ii in range(num_data_points)] +# for idata in range(num_data_points): +# axs.plot(time[idata, :]*1000, no_noise_data[idata, :], label=r"GRI-Mech3.0".format(equivalence_ratio[idata], initial_temperature[idata], initial_pressure[idata]), c="r") +# axs.scatter(time[idata, :]*1000, training_data[idata, :], label=r"Data".format(equivalence_ratio[idata], initial_temperature[idata], initial_pressure[idata]), s=10, c="k") +# axs.legend(loc="lower right", framealpha=1.0) +# axs.set_xlabel("time [ms]") +# axs.set_ylabel("Temperature [K]") +# axs.grid() +# axs.xaxis.set_major_locator(MultipleLocator(1)) +# axs.xaxis.set_minor_locator(MultipleLocator(0.5)) +# plt.tight_layout() +# plt.savefig(fig_save_path) +# plt.close() diff --git a/examples/ignition_model/learning_model/run.py b/examples/ignition_model/learning_model/run.py new file mode 100644 index 0000000..78c2374 --- /dev/null +++ b/examples/ignition_model/learning_model/run.py @@ -0,0 +1,1575 @@ +import shutil +import numpy as np +import time +import matplotlib.pyplot as plt +import yaml +from scipy.optimize import minimize +from matplotlib.ticker import MultipleLocator, AutoMinorLocator +import sys +import os +from mpi4py import MPI +from itertools import combinations + +sys.path.append("../forward_model/methane_combustion") +sys.path.append("../../../information_metrics") +sys.path.append("../../../mcmc") +from combustion import mech_1S_CH4_Westbrook, mech_2S_CH4_Westbrook, mech_gri +from compute_identifiability import conditional_mutual_information +from SobolIndex import SobolIndex +from mcmc import adaptive_metropolis_hastings +from mcmc_utils import sub_sample_data + +from color_schemes import dark_colors + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=20, titlepad=20) +plt.rc("legend", fontsize=18, framealpha=1.0) +plt.rc("xtick.major", size=6) +plt.rc("xtick.minor", size=4) +plt.rc("ytick.major", size=6) +plt.rc("ytick.minor", size=4) + + +class learn_ignition_model: + def __init__( + self, + config_data, + campaign_path, + prior_mean, + prior_cov, + use_normalization_coeff=False, + normalization_coeff=None, + ): + """Atribute initialization""" + # Prior + self.prior_mean = prior_mean + self.prior_cov = prior_cov + self.num_parameters = self.prior_mean.shape[0] + if use_normalization_coeff: + assert normalization_coeff is not None, "Normalization coeff not provided" + self.normalization_coeff = normalization_coeff + else: + self.normalization_coeff = np.ones(self.num_parameters) + + # Extract configurations + self.campaign_path = campaign_path + self.model_noise_cov = config_data["model_noise_cov"] + self.n_parameter_model = config_data["n_parameter_model"] + + # Traning data + training_data_set = np.load( + os.path.join(self.campaign_path, "training_data.npy") + ) + self.initial_temperature = training_data_set[:, 0] + self.initial_pressure = training_data_set[:, 1] + self.equivalence_ratio = training_data_set[:, 2] + self.ytrain = training_data_set[:, -1].reshape(-1, 1) + + self.num_data_points = self.ytrain.shape[0] + self.spatial_resolution = self.ytrain.shape[1] + self.objective_scaling = config_data["objective_scaling"] + + # Model identifiability + self.global_num_outer_samples = config_data["global_num_outer_samples"] + self.global_num_inner_samples = config_data["global_num_inner_samples"] + self.restart_identifiability = config_data["restart_identifiability"] + if rank == 0: + log_file_path = os.path.join(self.campaign_path, "log_file.dat") + self.log_file = open(log_file_path, "w") + else: + self.log_file = None + + def compute_kinetic_parameters(self, theta, initial_temperature, equivalence_ratio): + """Function computes the kinetic parameters from the input parameters""" + kinetic_parameters = {} + kinetic_parameters["A"] = self.compute_Arrehenius_A( + theta, initial_temperature, equivalence_ratio + ) + kinetic_parameters["Ea"] = self.compute_Arrehenius_Ea(theta) + return kinetic_parameters + + def compute_Arrehenius_A(self, theta, initial_temperature, equivalence_ratio): + """Function computes the pre exponential factor, A for the Arrhenius rate""" + + if self.n_parameter_model == 4 or self.n_parameter_model == 3: + l_0 = 18 + theta[0] + l_1 = theta[1] + l_2 = theta[2] + log_A = l_0 + np.tanh( + (l_1 + (l_2 * equivalence_ratio)) * (initial_temperature / 1000) + ) + + elif self.n_parameter_model == 8: + l_0 = 18 + theta[0] + l_1 = theta[1] + l_2 = theta[2] + l_3 = theta[3] + l_4 = theta[4] + l_5 = theta[5] + l_6 = theta[6] + + log_A = ( + l_0 + + (l_1 * np.exp(l_2 * equivalence_ratio)) + + ( + l_3 + * np.tanh( + ((l_4 + (l_5 * equivalence_ratio)) * initial_temperature) + l_6 + ) + ) + ) + + else: + raise ValueError("Number of parameters in the model not supported") + + return np.exp(log_A) + + def compute_Arrehenius_Ea(self, theta): + """Function computes the Arrhenius activation energy""" + if self.n_parameter_model == 3: + Arrhenius_Ea = 48400 + elif self.n_parameter_model == 4: + Arrhenius_Ea = 30000 + 10000 * theta[3] + elif self.n_parameter_model == 8: + Arrhenius_Ea = 30000 + 10000 * theta[7] + else: + raise ValueError("Number of parameters in the model not supported") + return Arrhenius_Ea + + def compute_model_prediction(self, theta, proc_log_file=None, true_theta=False): + """Function computes the model prediction, Temperature""" + if true_theta: + normalized_theta = theta + else: + normalized_theta = theta * self.normalization_coeff + + prediction = np.zeros((self.num_data_points, self.spatial_resolution)) + + for idata_sample in range(self.num_data_points): + + initial_pressure = self.initial_pressure[idata_sample] + initial_temperature = self.initial_temperature[idata_sample] + equivalence_ratio = self.equivalence_ratio[idata_sample] + + # Compute the kinetic parameters + kinetic_parameters = self.compute_kinetic_parameters( + normalized_theta, initial_temperature, equivalence_ratio + ) + + if proc_log_file is not None: + proc_log_file.write( + " Arrhenius_A: {0:.18f}\n".format(kinetic_parameters["A"]) + ) + proc_log_file.flush() + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + Arrhenius_A=kinetic_parameters["A"], + Arrhenius_Ea=kinetic_parameters["Ea"], + ) + + ignition_time = combustion_model.compute_ignition_time() + + prediction[idata_sample, :] = np.log(ignition_time) + + if proc_log_file is not None: + proc_log_file.write( + " Prediction: {}\n".format(prediction[idata_sample, :]) + ) + proc_log_file.flush() + + return prediction + + def compute_temperature_prediction(self, theta_samples, true_theta=False): + """Function computes the model prediction, Temperature""" + time_array = np.logspace(-6, 0, 3000) + + num_theta_samples = theta_samples.shape[1] + + if true_theta: + normalized_theta = theta_samples + else: + normalized_theta = theta_samples * self.normalization_coeff[:, None] + + prediction = np.zeros((3000, 4, num_theta_samples)) + for ii in range(num_theta_samples): + print( + "Computing prediction for theta sample: {0:d} / {1:d}".format( + ii, num_theta_samples + ), + flush=True, + ) + + for idata_sample in range(self.num_data_points): + initial_pressure = self.initial_pressure[idata_sample] + initial_temperature = self.initial_temperature[idata_sample] + equivalence_ratio = self.equivalence_ratio[idata_sample] + + kinetic_parameters = self.compute_kinetic_parameters( + normalized_theta[:, ii], initial_temperature, equivalence_ratio + ) + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + Arrhenius_A=kinetic_parameters["A"], + Arrhenius_Ea=kinetic_parameters["Ea"], + ) + + try: + states, _ = combustion_model.mech_2S_CH4_Westbrook_combustion() + interpolated_temp = self.interpolate_state( + states.t, states.T, interpolate_time=time_array + ) + prediction[:, idata_sample, ii] = interpolated_temp + + except Exception: + prediction[:, idata_sample, ii] = np.nan * np.ones(3000) + + return prediction + + def compute_temperature_prediction_2S_Westbrook(self): + """Function computes the model prediction, Temperature""" + + time_array = np.logspace(-6, 0, 3000) + prediction = np.zeros((3000, 4)) + + for idata_sample in range(self.num_data_points): + initial_pressure = self.initial_pressure[idata_sample] + initial_temperature = self.initial_temperature[idata_sample] + equivalence_ratio = self.equivalence_ratio[idata_sample] + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + ) + + states, _ = combustion_model.mech_2S_CH4_Westbrook_combustion() + interpolated_temp = self.interpolate_state( + states.t, states.T, interpolate_time=time_array + ) + prediction[:, idata_sample] = interpolated_temp + + return prediction + + def compute_species_evolution( + self, + theta_samples, + initial_pressure, + initial_temperature, + equivalence_ratio, + true_theta=False, + ): + """Function computes the model prediction, species concentration""" + + time_array = np.logspace(-6, 0, 3000) + prediction_mean = np.zeros((3000, 6)) + prediction_std = np.zeros((3000, 6)) + prediction = np.zeros((3000, 6, theta_samples.shape[1])) + + if true_theta: + normalized_theta = theta_samples + else: + normalized_theta = theta_samples * self.normalization_coeff[:, None] + + for ii in range(theta_samples.shape[1]): + + kinetic_parameters = self.compute_kinetic_parameters( + normalized_theta[:, ii], initial_temperature, equivalence_ratio + ) + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + Arrhenius_A=kinetic_parameters["A"], + Arrhenius_Ea=kinetic_parameters["Ea"], + ) + + states, _ = combustion_model.mech_2S_CH4_Westbrook_combustion() + + prediction[:, 0, ii] = self.interpolate_state( + states.t, states("CH4").X.ravel(), interpolate_time=time_array + ) + prediction[:, 1, ii] = self.interpolate_state( + states.t, states("CO2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 2, ii] = self.interpolate_state( + states.t, states("O2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 3, ii] = self.interpolate_state( + states.t, states("CO").X.ravel(), interpolate_time=time_array + ) + prediction[:, 4, ii] = self.interpolate_state( + states.t, states("H2O").X.ravel(), interpolate_time=time_array + ) + prediction[:, 5, ii] = self.interpolate_state( + states.t, states("N2").X.ravel(), interpolate_time=time_array + ) + + # breakpoint() + # prediction_mean = np.mean(prediction, axis=2) + # prediction_std = np.std(prediction, axis=2) + + # Compute the sample based mean + local_prediction_sum = np.sum(prediction, axis=2) + comm.Allreduce(local_prediction_sum, prediction_mean, op=MPI.SUM) + prediction_mean = prediction_mean / (theta_samples.shape[1] * size) + + # Compute the sample based std + local_prediction_error_sq = np.sum( + (prediction - prediction_mean[:, :, None]) ** 2, axis=2 + ) + comm.Allreduce(local_prediction_error_sq, prediction_std, op=MPI.SUM) + prediction_std = np.sqrt(prediction_std / (theta_samples.shape[1] * size)) + + return prediction_mean, prediction_std + + def compute_species_evolution_2S_Westbrook(self): + """Function computes the true species evolution""" + time_array = np.logspace(-6, 0, 3000) + prediction = np.zeros((3000, 6, self.num_data_points)) + + for idata in range(self.num_data_points): + initial_pressure = self.initial_pressure[idata] + initial_temperature = self.initial_temperature[idata] + equivalence_ratio = self.equivalence_ratio[idata] + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + ) + + states, _ = combustion_model.mech_2S_CH4_Westbrook_combustion() + + prediction[:, 0, idata] = self.interpolate_state( + states.t, states("CH4").X.ravel(), interpolate_time=time_array + ) + prediction[:, 1, idata] = self.interpolate_state( + states.t, states("CO2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 2, idata] = self.interpolate_state( + states.t, states("O2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 3, idata] = self.interpolate_state( + states.t, states("CO").X.ravel(), interpolate_time=time_array + ) + prediction[:, 4, idata] = self.interpolate_state( + states.t, states("H2O").X.ravel(), interpolate_time=time_array + ) + prediction[:, 5, idata] = self.interpolate_state( + states.t, states("N2").X.ravel(), interpolate_time=time_array + ) + + return prediction + + def compute_true_species_evolution(self): + """Function computes the true species evolution""" + time_array = np.logspace(-6, 0, 3000) + prediction = np.zeros((3000, 6, self.num_data_points)) + + for idata in range(self.num_data_points): + initial_pressure = self.initial_pressure[idata] + initial_temperature = self.initial_temperature[idata] + equivalence_ratio = self.equivalence_ratio[idata] + + combustion_model = mech_gri( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + ) + + states, _ = combustion_model.gri_combustion() + + prediction[:, 0, idata] = self.interpolate_state( + states.t, states("CH4").X.ravel(), interpolate_time=time_array + ) + prediction[:, 1, idata] = self.interpolate_state( + states.t, states("CO2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 2, idata] = self.interpolate_state( + states.t, states("O2").X.ravel(), interpolate_time=time_array + ) + prediction[:, 3, idata] = self.interpolate_state( + states.t, states("CO").X.ravel(), interpolate_time=time_array + ) + prediction[:, 4, idata] = self.interpolate_state( + states.t, states("H2O").X.ravel(), interpolate_time=time_array + ) + prediction[:, 5, idata] = self.interpolate_state( + states.t, states("N2").X.ravel(), interpolate_time=time_array + ) + + return prediction + + def compute_true_temperature_prediction(self): + """Function computes the true Temperature""" + + time_array = np.logspace(-6, 0, 3000) + prediction = np.zeros((3000, self.num_data_points)) + + for idata_sample in range(self.num_data_points): + print("True Temperature: ", idata_sample) + initial_pressure = self.initial_pressure[idata_sample] + initial_temperature = self.initial_temperature[idata_sample] + equivalence_ratio = self.equivalence_ratio[idata_sample] + + combustion_model = mech_gri( + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + ) + + states, _ = combustion_model.gri_combustion() + + interpolated_temp = self.interpolate_state( + states.t, states.T, interpolate_time=time_array + ) + prediction[:, idata_sample] = interpolated_temp + + return prediction + + def interpolate_state(self, time, state, interpolate_time=None): + """Function interpolates the temperature""" + interpolated_state = np.interp(interpolate_time, time, state) + return interpolated_state + + def compute_complete_state(self, theta): + """Function computes the entire state""" + state_list = [] + for idata_sample in range(self.num_data_points): + Arrhenius_A = self.compute_Arrehenius_A( + theta=theta, + equivalence_ratio=self.equivalence_ratio[idata_sample], + initial_temperature=self.initial_temperature[idata_sample], + ) + # print(Arrhenius_A, Arrhenius_Ea) + combustion_model = mech_1S_CH4_Westbrook( + initial_temperature=self.initial_temperature[idata_sample], + initial_pressure=self.initial_pressure[idata_sample], + equivalence_ratio=self.equivalence_ratio[idata_sample], + Arrhenius_A=Arrhenius_A, + # Arrhenius_Ea=Arrhenius_Ea, + ) + + states = combustion_model.mech_1S_CH4_Westbrook_combustion() + + state_list.append(states) + + return state_list + + def compute_log_likelihood(self, theta): + """Function comptues the log likelihood""" + prediction = self.compute_model_prediction(theta=theta) + error = self.ytrain - prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + log_likelihood = (-0.5 / self.model_noise_cov) * (np.sum(error_norm_sq)) + return log_likelihood + + def compute_log_prior(self, theta): + """Function computes the log prior""" + error = (theta - self.prior_mean).reshape(-1, 1) + error_norm_sq = (error.T @ np.linalg.solve(self.prior_cov, error)).item() + log_prior = -0.5 * error_norm_sq + return log_prior + + def compute_mle(self): + """Function comptues the MLE""" + theta_init = np.random.randn(self.num_parameters) + + def objective_function(theta): + objective_function = -self.objective_scaling * self.compute_log_likelihood( + theta + ) + print("MLE objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize(objective_function, theta_init, method="Nelder-Mead") + res = minimize(objective_function, res.x) + theta_mle = res.x + + prediction_mle = self.compute_model_prediction(theta=res.x).ravel() + prediction_save = np.zeros((self.num_data_points, 4)) + prediction_save[:, 0] = self.initial_temperature + prediction_save[:, 1] = self.initial_pressure + prediction_save[:, 2] = self.equivalence_ratio + prediction_save[:, 3] = prediction_mle + + # Saving MLE + save_prediction_path = os.path.join(self.campaign_path, "prediction_mle.dat") + save_mle_path = os.path.join(self.campaign_path, "theta_mle.npy") + + np.savetxt( + save_prediction_path, + prediction_save, + delimiter=" ", + header="Variables: Inital_temperature, Initial_pressure \ + ,Equivalence_ratio, Ignition_temperature", + ) + np.save(save_mle_path, theta_mle) + + return theta_mle + + def compute_unnormalized_posterior(self, theta): + log_likelihood = self.compute_log_likelihood(theta) + log_prior = self.compute_log_prior(theta) + unnormalized_log_post = log_likelihood + log_prior + + return unnormalized_log_post + + def compute_map(self): + """Function computes the map estimate""" + theta_init = np.random.randn(self.num_parameters) + + def objective_function(theta): + objective_function = ( + -self.objective_scaling * self.compute_unnormalized_posterior(theta) + ) + print("MAP objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize(objective_function, theta_init, method="Nelder-Mead") + res = minimize(objective_function, res.x) + + theta_map = res.x + theta_map_cov = res.hess_inv + + return theta_map.reshape(-1, 1), theta_map_cov + + def compute_mcmc(self, theta_map, theta_map_cov, num_mcmc_samples): + """Function computes the MCMC samples""" + + def compute_post(theta): + return self.compute_unnormalized_posterior(theta) + + mcmc_sampler = adaptive_metropolis_hastings( + initial_sample=theta_map.ravel(), + target_log_pdf_evaluator=compute_post, + num_samples=num_mcmc_samples, + adapt_sample_threshold=10, + initial_cov=1e-2 * theta_map_cov, + ) + + mcmc_sampler.compute_mcmc_samples(verbose=True) + + # Compute acceptance rate + ar = mcmc_sampler.compute_acceptance_ratio() + if rank == 0: + print("Acceptance rate: ", ar) + + # Compute the burn in samples + burned_samples = sub_sample_data( + mcmc_sampler.samples, frac_burn=0.5, frac_use=0.7 + ) + np.save( + os.path.join( + self.campaign_path, "burned_samples_rank_{}_.npy".format(rank) + ), + burned_samples, + ) + + def plot_mcmc_estimate(self, mcmc_samples=None): + + time_array = np.logspace(-6, 0, 3000) + + # Compute model prediction for ALL MCMC samples + test_samples = mcmc_samples[:10, :] + mcmc_prediction = self.compute_temperature_prediction( + theta_samples=test_samples.T + ) + + # Compute feasibile samples + feasible_idx = [] + for ii in range(self.num_data_points): + sub_data = mcmc_prediction[:, ii, :] + idx = np.arange(sub_data.shape[1])[ + np.logical_and( + np.max(sub_data, axis=0) < 1e4, + np.min(sub_data, axis=0) > self.initial_temperature[ii], + ) + ] + feasible_idx.append(idx) + + # Plot temperature prediction for feasible samples + fig, axs = plt.subplots(1, 1, figsize=(20, 8)) + fig_save_path = os.path.join(self.campaign_path, "Figures/mcmc_prediction.png") + + true_temperature_prediction = self.compute_true_temperature_prediction() + temperature_prediction_2S_Westbrook = ( + self.compute_temperature_prediction_2S_Westbrook() + ) + + for ii in range(self.num_data_points): + + global_selected_data_mean = np.zeros_like(time_array) + global_selected_data_std = np.zeros_like(time_array) + + sub_data = mcmc_prediction[:, ii, :] + selected_data = sub_data[:, feasible_idx[ii]] + local_selected_data_sum = np.sum(selected_data, axis=1) + comm.Allreduce( + local_selected_data_sum, global_selected_data_mean, op=MPI.SUM + ) + global_selected_data_mean /= test_samples.shape[0] * size + + local_selected_data_error_square_sum = np.sum( + (selected_data - global_selected_data_mean[:, None]) ** 2, axis=1 + ) + comm.Allreduce( + local_selected_data_error_square_sum, + global_selected_data_std, + op=MPI.SUM, + ) + global_selected_data_std /= test_samples.shape[0] * size + global_selected_data_std = np.sqrt(global_selected_data_std) + + if rank == 0: + + ub = global_selected_data_mean + global_selected_data_std + lb = global_selected_data_mean - global_selected_data_std + + axs.plot( + time_array, + true_temperature_prediction[:, ii], + "-", + color=dark_colors(ii), + label="Gri-Mech 3.0, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[ii] + ), + ) + axs.plot( + time_array, + temperature_prediction_2S_Westbrook[:, ii], + "-", + dashes=[3, 2, 3, 2], + lw=3, + color=dark_colors(ii), + label=r"2-step mechanism [Westbrook $\textit{{et al.}}$]," + "$T_{{o}}$={0:.1f} K".format(self.initial_temperature[ii]), + ) + axs.plot( + time_array, + global_selected_data_mean, + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(ii), + lw=3, + label=r"$\mu_{{prediction}}$, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[ii] + ), + ) + axs.fill_between( + np.logspace(-6, 0, 3000), + ub, + lb, + ls="-", + lw=2, + color=dark_colors(ii), + alpha=0.2, + label=r"$\pm\sigma_{{prediction}}$, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[ii] + ), + ) + + if rank == 0: + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.set_xscale("log") + axs.set_ylim([1000, 4000]) + axs.set_xlim([1e-6, 1]) + axs.set_xlabel("Time [s]") + axs.set_ylabel("Temperature [K]") + plt.subplots_adjust(left=0.1, right=0.65, top=0.7, bottom=0.15) + fig.legend(loc="upper center", bbox_to_anchor=(0.82, 0.8), ncol=1) + # plt.legend(ncol=2, bbox_to_anchor=(0.525, 1.125, 0.5, 0.5)) + plt.savefig(fig_save_path, bbox_inches="tight") + plt.close() + + # Plot species prediciton for feasible samples + species_true_prediction = self.compute_true_species_evolution() + species_2S_Westbrook_prediction = self.compute_species_evolution_2S_Westbrook() + species_prediction_mean = np.zeros((3000, 6, self.num_data_points)) + species_prediction_std = np.zeros((3000, 6, self.num_data_points)) + + for idata in range(self.num_data_points): + initial_temperature = self.initial_temperature[idata] + initial_pressure = self.initial_pressure[idata] + equivalence_ratio = self.equivalence_ratio[idata] + theta_samples = test_samples[feasible_idx[idata], :].T + + species_mean, species_std = self.compute_species_evolution( + theta_samples=theta_samples, + initial_temperature=initial_temperature, + initial_pressure=initial_pressure, + equivalence_ratio=equivalence_ratio, + ) + + species_prediction_mean[:, :, idata] = species_mean + species_prediction_std[:, :, idata] = species_std + + fig, axs = plt.subplots(2, 3, figsize=(20, 12)) + fig.tight_layout(h_pad=3.3, w_pad=3.3) + fig_save_path = os.path.join( + self.campaign_path, "Figures/mcmc_species_prediction.png" + ) + + for idata in range(self.num_data_points): + + axs[0, 0].plot( + time_array, + species_true_prediction[:, 0, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[idata] + ), + ) + axs[0, 0].plot( + time_array, + species_2S_Westbrook_prediction[:, 0, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"2-step mechanism [Westbrook $\textit{{et al.}}$]" + "$T_{{o}}$={0:.1f} K".format(self.initial_temperature[idata]), + ) + axs[0, 0].plot( + time_array, + species_prediction_mean[:, 0, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{prediction}}$, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[idata] + ), + ) + axs[0, 0].fill_between( + time_array, + species_prediction_mean[:, 0, idata] + + species_prediction_std[:, 0, idata], + species_prediction_mean[:, 0, idata] + - species_prediction_std[:, 0, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + label=r"$\pm\sigma_{{prediction}}$, $T_{{o}}$={0:.1f} K".format( + self.initial_temperature[idata] + ), + ) + axs[0, 0].set_xlabel(r"Time [s]") + axs[0, 0].set_ylabel(r"[$CH_{4}$]") + axs[0, 0].set_ylim([0, 0.1]) + axs[0, 0].set_xlim([1e-6, 1]) + axs[0, 0].set_xscale("log") + axs[0, 0].yaxis.set_minor_locator(MultipleLocator(0.0125)) + # axs[0, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + # axs[0, 0].xaxis.set_minor_locator(AutoMinorLocator()) + axs[0, 0].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 0].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[0, 1].plot( + time_array, + species_true_prediction[:, 1, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0", + ) + axs[0, 1].plot( + time_array, + species_2S_Westbrook_prediction[:, 1, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[0, 1].plot( + time_array, + species_prediction_mean[:, 1, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[0, 1].fill_between( + time_array, + species_prediction_mean[:, 1, idata] + + species_prediction_std[:, 1, idata], + species_prediction_mean[:, 1, idata] + - species_prediction_std[:, 1, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + ) + axs[0, 1].set_xlabel(r"Time [s]") + axs[0, 1].set_ylabel(r"[$CO_{2}$]") + axs[0, 1].set_ylim([0, 0.1]) + axs[0, 1].set_xlim([1e-6, 1]) + axs[0, 1].set_xscale("log") + axs[0, 1].yaxis.set_minor_locator(MultipleLocator(0.0125)) + # axs[0, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 1].xaxis.set_minor_locator(AutoMinorLocator()) + axs[0, 1].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 1].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[0, 2].plot( + time_array, + species_true_prediction[:, 2, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0", + ) + axs[0, 2].plot( + time_array, + species_2S_Westbrook_prediction[:, 2, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[0, 2].plot( + time_array, + species_prediction_mean[:, 2, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[0, 2].fill_between( + time_array, + species_prediction_mean[:, 2, idata] + + species_prediction_std[:, 2, idata], + species_prediction_mean[:, 2, idata] + - species_prediction_std[:, 2, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + ) + axs[0, 2].set_xlabel(r"Time [s]") + axs[0, 2].set_ylabel(r"[$O_{2}$]") + axs[0, 2].set_ylim([0, 0.2]) + axs[0, 2].set_xlim([1e-6, 1]) + axs[0, 2].set_xscale("log") + axs[0, 2].yaxis.set_minor_locator(MultipleLocator(0.025)) + # axs[0, 2].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[0, 2].xaxis.set_minor_locator(AutoMinorLocator()) + axs[0, 2].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[0, 2].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[1, 0].plot( + time_array, + species_true_prediction[:, 3, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0", + ) + axs[1, 0].plot( + time_array, + species_2S_Westbrook_prediction[:, 3, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 0].plot( + time_array, + species_prediction_mean[:, 3, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 0].fill_between( + time_array, + species_prediction_mean[:, 3, idata] + + species_prediction_std[:, 3, idata], + species_prediction_mean[:, 3, idata] + - species_prediction_std[:, 3, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + ) + axs[1, 0].set_xlabel(r"Time [s]") + axs[1, 0].set_ylabel(r"[$CO$]") + axs[1, 0].set_ylim([0, 0.2]) + axs[1, 0].set_xlim([1e-6, 1]) + axs[1, 0].set_xscale("log") + axs[1, 0].yaxis.set_minor_locator(MultipleLocator(0.02)) + axs[1, 0].yaxis.set_major_locator(MultipleLocator(0.04)) + axs[1, 0].xaxis.set_minor_locator(AutoMinorLocator()) + # axs[1, 0].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 0].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 0].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[1, 1].plot( + time_array, + species_true_prediction[:, 4, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0", + ) + axs[1, 1].plot( + time_array, + species_2S_Westbrook_prediction[:, 4, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 1].plot( + time_array, + species_prediction_mean[:, 4, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 1].fill_between( + time_array, + species_prediction_mean[:, 4, idata] + + species_prediction_std[:, 4, idata], + species_prediction_mean[:, 4, idata] + - species_prediction_std[:, 4, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + ) + axs[1, 1].set_xlabel(r"Time [s]") + axs[1, 1].set_ylabel(r"[$H_{2}O$]") + axs[1, 1].set_ylim([0, 0.2]) + axs[1, 1].set_xlim([1e-6, 1]) + axs[1, 1].set_xscale("log") + axs[1, 1].yaxis.set_minor_locator(MultipleLocator(0.025)) + # axs[1, 1].yaxis.set_major_locator(MultipleLocator(0.04)) + axs[1, 1].xaxis.set_minor_locator(AutoMinorLocator()) + # axs[1, 1].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 1].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 1].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + axs[1, 2].plot( + time_array, + species_true_prediction[:, 5, idata], + color=dark_colors(idata), + label="Gri-Mech 3.0", + ) + axs[1, 2].plot( + time_array, + species_2S_Westbrook_prediction[:, 5, idata], + "-", + dashes=[2, 1, 2, 1], + lw=3, + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 2].plot( + time_array, + species_prediction_mean[:, 5, idata], + "-", + dashes=[6, 2, 2, 2], + color=dark_colors(idata), + label=r"$\mu_{{post}}$", + ) + axs[1, 2].fill_between( + time_array, + species_prediction_mean[:, 5, idata] + + species_prediction_std[:, 5, idata], + species_prediction_mean[:, 5, idata] + - species_prediction_std[:, 5, idata], + ls="-", + lw=2, + color=dark_colors(idata), + alpha=0.2, + ) + axs[1, 2].set_xlabel(r"Time [s]") + axs[1, 2].set_ylabel(r"[$N_{2}$]") + axs[1, 2].set_ylim([0.65, 0.75]) + axs[1, 2].set_xlim([1e-6, 1]) + axs[1, 2].set_xscale("log") + axs[1, 2].yaxis.set_minor_locator(MultipleLocator(0.025)) + # axs[12 1].yaxis.set_major_locator(MultipleLocator(0.04)) + axs[1, 2].xaxis.set_minor_locator(AutoMinorLocator()) + # axs[1, 2].xaxis.set_minor_locator(MultipleLocator(0.5)) + axs[1, 2].grid(True, axis="both", which="major", color="k", alpha=0.5) + axs[1, 2].grid(True, axis="both", which="minor", color="grey", alpha=0.3) + + handles, labels = axs[0, 0].get_legend_handles_labels() + fig.legend( + handles, + labels, + loc="upper center", + bbox_to_anchor=(1.2, 0.75), + ncol=1, + fontsize=25, + ) + plt.subplots_adjust(top=0.73, bottom=0.12, left=0.1, right=0.95) + plt.savefig(fig_save_path, bbox_inches="tight", dpi=300) + plt.close() + + # Plot the ignition delay + + num_samples = 8 + temperature_range = np.linspace(833, 2500, num_samples) + save_fig_path = os.path.join( + self.campaign_path, "Figures/ignition_delay_mcmc.png" + ) + + local_ignition_prediction = np.zeros( + (temperature_range.shape[0], test_samples.shape[0]) + ) + + for ii in range(test_samples.shape[0]): + for jj, itemp in enumerate(temperature_range): + normalized_theta = test_samples[ii, :] * self.normalization_coeff + + # Compute the kinetic parameters + kinetic_parameters = self.compute_kinetic_parameters( + normalized_theta, itemp, 1.0 + ) + + combustion_model = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + Arrhenius_A=kinetic_parameters["A"], + Arrhenius_Ea=kinetic_parameters["Ea"], + ) + + local_ignition_prediction[jj, ii] = np.log( + combustion_model.compute_ignition_time() + ) + + ignition_prediction_mean = np.zeros(num_samples) + ignition_prediction_std = np.zeros(num_samples) + + local_ignition_prediction_sum = np.sum(local_ignition_prediction, axis=1) + comm.Allreduce( + local_ignition_prediction_sum, ignition_prediction_mean, op=MPI.SUM + ) + ignition_prediction_mean /= size * test_samples.shape[0] + + local_ignition_prediction_error_square_sum = np.sum( + (local_ignition_prediction - ignition_prediction_mean[:, None]) ** 2, axis=1 + ) + comm.Allreduce( + local_ignition_prediction_error_square_sum, + ignition_prediction_std, + op=MPI.SUM, + ) + ignition_prediction_std /= size * test_samples.shape[0] + ignition_prediction_std = np.sqrt(ignition_prediction_std) + + if rank == 0: + np.save( + os.path.join(self.campaign_path, "ignition_prediction_mean_mcmc.npy"), + ignition_prediction_mean, + ) + np.save( + os.path.join(self.campaign_path, "ignition_prediction_std_mcmc.npy"), + ignition_prediction_std, + ) + + un_calibrated_model_prediction = np.zeros_like(temperature_range) + gri_prediction = np.zeros_like(temperature_range) + for ii, itemp in enumerate(temperature_range): + # Compute gri prediciton + gri_model = mech_gri( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + gri_prediction[ii] = np.log(gri_model.compute_ignition_time()) + + # Compute 2-step Westbrook prediction (uncalibrated) + model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + + un_calibrated_model_prediction[ii] = np.log( + model_2S_CH4_Westbrook.compute_ignition_time(internal_state="plot") + ) + + fig, axs = plt.subplots(figsize=(11, 7)) + axs.scatter( + 1000 / self.initial_temperature, + self.ytrain.ravel(), + label="Data", + color="k", + marker="s", + s=100, + ) + axs.plot( + 1000 / temperature_range, + gri_prediction, + "--", + label="Gri-Mech 3.0", + color="k", + ) + axs.plot( + 1000 / temperature_range, + un_calibrated_model_prediction, + label=r"2-step mechanism [Westbrook \textit{et al.}]", + color="r", + ) + axs.plot( + 1000 / temperature_range, + ignition_prediction_mean, + label=r"$\mu_{prediction}$", + color="b", + ) + axs.fill_between( + 1000 / temperature_range, + ignition_prediction_mean + 3 * ignition_prediction_std, + ignition_prediction_mean - 3 * ignition_prediction_std, + ls="--", + edgecolor="C0", + lw=2, + alpha=0.3, + label=r"$\pm 3\sigma_{prediction}$", + ) + axs.set_xlabel(r"1000/T [1/K]") + axs.set_ylabel(r"$\log(t_{ign})$") + axs.legend(loc="upper left") + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.xaxis.set_minor_locator(MultipleLocator(0.05)) + axs.xaxis.set_major_locator(MultipleLocator(0.2)) + axs.yaxis.set_minor_locator(AutoMinorLocator()) + axs.set_xlim([0.4, 1.2]) + axs.set_ylim([-15, 10]) + plt.tight_layout() + plt.savefig(save_fig_path, bbox_inches="tight", dpi=300) + plt.close() + + def plot_map_estimate(self, theta_map, theta_map_cov): + """Function plots the map estimate""" + num_samples = 50 + temperature_range = np.linspace(833, 2500, num_samples) + calibrated_model_prediction = np.zeros( + (num_samples, temperature_range.shape[0]) + ) + un_calibrated_model_prediction = np.zeros_like(temperature_range) + gri_prediction = np.zeros_like(temperature_range) + + d = theta_map.shape[0] + std_normal_samples = np.random.randn(d, num_samples) + cov_cholesky = np.linalg.cholesky(theta_map_cov) + theta_samples = theta_map + cov_cholesky @ std_normal_samples + + for ii in range(num_samples): + print("sample : {}".format(ii)) + eval_theta = theta_samples[:, ii] + for jj, itemp in enumerate(temperature_range): + + Arrhenius_A = self.compute_Arrehenius_A( + theta=eval_theta, equivalence_ratio=1.0, initial_temperature=itemp + ) + + model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + Arrhenius_A=Arrhenius_A, + ) + + calibrated_model_prediction[ii, jj] = np.log( + model_2S_CH4_Westbrook.compute_ignition_time(internal_state="plot") + ) + + for ii, itemp in enumerate(temperature_range): + # Compute gri prediciton + gri_model = mech_gri( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + gri_prediction[ii] = np.log(gri_model.compute_ignition_time()) + + # Compute 2-step Westbrook prediction (uncalibrated) + model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + + un_calibrated_model_prediction[ii] = np.log( + model_2S_CH4_Westbrook.compute_ignition_time(internal_state="plot") + ) + + mean_val = np.mean(calibrated_model_prediction, axis=0) + std_val = np.std(calibrated_model_prediction, axis=0) + + save_fig_path = os.path.join( + self.campaign_path, + "Figures/prediction_map_phi_{}_pressure_{}_noise_{}.png".format( + 1.0, 100000, self.model_noise_cov + ), + ) + fig, axs = plt.subplots(figsize=(11, 7)) + axs.scatter( + 1000 / self.initial_temperature, + self.ytrain.ravel(), + label="Data", + color="k", + marker="s", + s=100, + ) + axs.plot( + 1000 / temperature_range, + gri_prediction, + "--", + label="Gri-Mech 3.0", + color="k", + ) + axs.plot( + 1000 / temperature_range, + un_calibrated_model_prediction, + label=r"2-step mechanism [Westbrook \textit{et al.}]", + color="r", + ) + axs.plot(1000 / temperature_range, mean_val, label=r"$\mu_{MAP}$", color="b") + axs.fill_between( + 1000 / temperature_range, + mean_val + std_val, + mean_val - std_val, + ls="--", + edgecolor="C0", + lw=2, + alpha=0.3, + label=r"$\pm\sigma_{MAP}$", + ) + axs.set_xlabel(r"1000/T [1/K]") + axs.set_ylabel(r"$\log(t_{ign})$") + axs.legend(loc="upper left") + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.xaxis.set_minor_locator(MultipleLocator(0.05)) + axs.xaxis.set_major_locator(MultipleLocator(0.2)) + axs.yaxis.set_minor_locator(AutoMinorLocator()) + axs.set_xlim([0.4, 1.2]) + axs.set_ylim([-15, 10]) + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def plot_mle_estimate(self, theta_mle): + """Function plots the mle estimate""" + num_samples = 50 + temperature_range = np.linspace(833, 2500, num_samples) + calibrated_model_prediction = np.zeros_like(temperature_range) + un_calibrated_model_prediction = np.zeros_like(temperature_range) + gri_prediction = np.zeros_like(temperature_range) + + for ii, itemp in enumerate(temperature_range): + # Compute gri prediciton + gri_model = mech_gri( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + gri_prediction[ii] = np.log(gri_model.compute_ignition_time()) + + # Compute 2-step Westbrook prediction (Calibrated) + Arrhenius_A = self.compute_Arrehenius_A( + theta=theta_mle, equivalence_ratio=1.0, initial_temperature=itemp + ) + + model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + Arrhenius_A=Arrhenius_A, + ) + + calibrated_model_prediction[ii] = np.log( + model_2S_CH4_Westbrook.compute_ignition_time(internal_state="plot") + ) + + # Compute 2-step Westbrook prediction (uncalibrated) + model_2S_CH4_Westbrook = mech_2S_CH4_Westbrook( + initial_temperature=itemp, + initial_pressure=100000, + equivalence_ratio=1.0, + ) + + un_calibrated_model_prediction[ii] = np.log( + model_2S_CH4_Westbrook.compute_ignition_time(internal_state="plot") + ) + + save_fig_path = os.path.join( + self.campaign_path, + "Figures/prediction_mle_phi_{}_pressure_{}_noise_{}.png".format( + 1.0, 100000, self.model_noise_cov + ), + ) + fig, axs = plt.subplots(figsize=(12, 7)) + axs.scatter( + 1000 / self.initial_temperature, + self.ytrain.ravel(), + label="Data", + color="k", + marker="s", + s=100, + ) + axs.plot( + 1000 / temperature_range, + gri_prediction, + "--", + label="Gri-Mech 3.0", + color="k", + ) + axs.plot( + 1000 / temperature_range, + calibrated_model_prediction, + label="M.L.E", + color="b", + ) + axs.plot( + 1000 / temperature_range, + un_calibrated_model_prediction, + label=r"2-step mechanism [Westbrook \textit{et al.}]", + color="r", + ) + axs.set_xlabel(r"1000/T [1/K]") + axs.set_ylabel(r"$\log(t_{ign})$") + axs.legend(loc="upper left") + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.set_xlim([0.4, 1.2]) + axs.set_ylim([-15, 10]) + axs.xaxis.set_minor_locator(MultipleLocator(0.05)) + axs.xaxis.set_major_locator(MultipleLocator(0.2)) + axs.yaxis.set_minor_locator(AutoMinorLocator()) + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def compute_model_identifiability(self, prior_mean, prior_cov): + """Function computes the model identifiability""" + + def forward_model(theta, proc_log_file=None): + prediction = self.compute_model_prediction( + theta, proc_log_file=proc_log_file + ).T + return prediction + + estimator = conditional_mutual_information( + forward_model=forward_model, + prior_mean=prior_mean, + prior_cov=prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain.T, + log_file=self.log_file, + ) + + # estimator.compute_individual_parameter_data_mutual_information_via_mc( + # use_quadrature=True, + # single_integral_gaussian_quad_pts=5 + # ) + + estimator.compute_posterior_pair_parameter_mutual_information( + use_quadrature=True, + single_integral_gaussian_quad_pts=5, + double_integral_gaussian_quad_pts=10, + ) + + def compute_sobol_index(self, prior_mean, prior_cov): + """Function computes the sobol index""" + + def forward_model(theta): + return self.compute_model_prediction(theta).T + + sobol_index = SobolIndex( + forward_model=forward_model, + prior_mean=prior_mean, + prior_cov=prior_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + model_noise_cov_scalar=self.model_noise_cov, + data_shape=(1, self.num_data_points), + write_log_file=True, + save_path=os.path.join(self.campaign_path, "SobolIndex"), + ) + + sobol_index.comp_first_order_sobol_indices() + sobol_index.comp_total_effect_sobol_indices() + + def compute_sobol_input_predictions(self): + """Function to compute the model predictions for the input Sobol parameters + Note: The parameters are generated using the Saltelli sampling scheme + """ + input_file = os.path.join( + "sobol_samples", "sobol_input_samples_rank_{}.npy".format(rank) + ) + assert os.path.exists(input_file), "Input file does not exist" + input_samples = np.load(input_file) + num_inputs = input_samples.shape[0] + print("Number of inputs: {} at RANK : {}".format(num_inputs, rank), flush=True) + output_samples = np.zeros( + (self.num_data_points, self.spatial_resolution, num_inputs) + ) + + tic = time.time() + for isample in range(num_inputs): + if rank == 0: + print( + "Computing the model prediction for input sample: {} / {}".format( + isample, num_inputs - 1 + ) + ) + output_samples[:, :, isample] = self.compute_model_prediction( + input_samples[isample, :] + ) + print( + "Time taken to compute the model predictions: {} (RANK: {})".format( + time.time() - tic, rank + ) + ) + + sobol_output_dir = os.path.join(self.campaign_path, "SALib_Sobol") + if rank == 0: + if not os.path.exists(sobol_output_dir): + os.makedirs(sobol_output_dir) + else: + shutil.rmtree(sobol_output_dir) + os.makedirs(sobol_output_dir) + comm.Barrier() + save_output_path = os.path.join( + sobol_output_dir, "sobol_output_samples_rank_{}.npy".format(rank) + ) + np.save(save_output_path, output_samples) + + +def load_configuration_file(config_file_path="./config.yaml"): + """Function loads the configuration file""" + with open(config_file_path, "r") as config_file: + config_data = yaml.safe_load(config_file) + if rank == 0: + print("Loaded %s configuration file" % (config_file_path), flush=True) + return config_data + + +def compute_normalization_coefficient(individual_mi, pair_mi): + num_parameters = len(individual_mi) + alpha = individual_mi / np.sum(individual_mi) + + parameter_comb = np.array(list(combinations(np.arange(num_parameters), 2))) + mat = np.zeros((num_parameters, num_parameters)) + + for ii in range(parameter_comb.shape[0]): + + mat[parameter_comb[ii, 0], parameter_comb[ii, 1]] = pair_mi[ii] + mat[parameter_comb[ii, 1], parameter_comb[ii, 0]] = pair_mi[ii] + + beta = np.sum(mat, axis=1) / np.sum(mat) + gamma_unnormalized = alpha / (alpha + beta) + gamma = gamma_unnormalized / np.sum(gamma_unnormalized) + + return gamma + + +def main(): + # Load the configurations + config_data = load_configuration_file() + + # Model prior + num_model_parameters = config_data["n_parameter_model"] + prior_mean = np.zeros(num_model_parameters) + prior_cov = np.eye(num_model_parameters) + # Campaign path + campaign_path = os.path.join( + os.getcwd(), "campaign_results/campaign_%d" % (config_data["campaign_id"]) + ) + + gamma_coefficient = compute_normalization_coefficient( + individual_mi=[2.73662068, 1.26551743, 1.26228971], + pair_mi=[1.13192122, 1.13670414, 0.73127758], + ) + + # Learning model + learning_model = learn_ignition_model( + config_data=config_data, + campaign_path=campaign_path, + prior_mean=prior_mean, + prior_cov=prior_cov, + use_normalization_coeff=False, + normalization_coeff=gamma_coefficient, + ) + + if config_data["compute_mle"]: + theta_mle = learning_model.compute_mle() + np.save(os.path.join(campaign_path, "theta_mle.npy"), theta_mle) + elif config_data["compute_map"]: + theta_map, theta_map_cov = learning_model.compute_map() + np.save(os.path.join(campaign_path, "theta_map.npy"), theta_map) + np.save(os.path.join(campaign_path, "theta_map_cov.npy"), theta_map_cov) + elif config_data["compute_mcmc"]: + num_mcmc_samples = config_data["num_mcmc_samples"] + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + learning_model.compute_mcmc( + theta_map, theta_map_cov, num_mcmc_samples=num_mcmc_samples + ) + + if "--plotmap" in sys.argv: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + learning_model.plot_map_estimate( + theta_map=theta_map, theta_map_cov=theta_map_cov + ) + + elif "--plotmle" in sys.argv: + theta_mle = np.load(os.path.join(campaign_path, "theta_mle.npy")) + learning_model.plot_mle_estimate( + theta_mle=theta_mle, + ) + + elif "--plotmcmc" in sys.argv: + mcmc_samples = np.load( + os.path.join(campaign_path, "burned_samples_rank_{}_.npy".format(rank)) + ) + learning_model.plot_mcmc_estimate( + mcmc_samples=mcmc_samples, + ) + + if config_data["compute_identifiability"]: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + learning_model.compute_model_identifiability( + prior_mean=theta_map, prior_cov=theta_map_cov + ) + + if config_data["compute_sobol_index"]: + learning_model.compute_sobol_input_predictions() + # theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + # theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + # learning_model.compute_sobol_index( + # prior_mean=theta_map, prior_cov=theta_map_cov + # ) + + if rank == 0: + learning_model.log_file.close() + + +if __name__ == "__main__": + main() diff --git a/examples/linear_gaussian/forward_model/linear_gaussian.py b/examples/linear_gaussian/forward_model/linear_gaussian.py new file mode 100644 index 0000000..61a1aa8 --- /dev/null +++ b/examples/linear_gaussian/forward_model/linear_gaussian.py @@ -0,0 +1,49 @@ +import numpy as np +import os +import shutil +import matplotlib.pyplot as plt + + +class linear_gaussian: + def __init__(self, true_theta=np.arange(1, 4), spatial_res=100): + self.spatial_res = spatial_res + self.xtrain = np.linspace(-1, 1, self.spatial_res) + self.true_theta = true_theta + self.num_parameters = self.true_theta.shape[0] + self.vm = self.compute_vm() + + def compute_vm(self): + vm = np.tile(self.xtrain[:, None], (1, self.num_parameters)) + vm = np.cumprod(vm, axis=1) + return vm + + def compute_prediction(self, theta): + return self.vm @ theta.reshape(-1, 1) + + +def main(): + # User input + true_theta = np.arange(3) + spatial_res = 100 + # End User input + model = linear_gaussian(true_theta=true_theta, spatial_res=spatial_res) + prediction = model.compute_prediction(theta=true_theta) + + data = np.zeros((spatial_res, 2)) + data[:, 0] = model.xtrain + data[:, 1] = prediction.ravel() + + # Create the folders + save_path = "./Output" + + if os.path.exists(save_path) is True: + shutil.rmtree(save_path) + + os.mkdir(save_path) + + data_save_path = os.path.join(save_path, "true_output.npy") + np.save(data_save_path, data) + + +if __name__ == "__main__": + main() diff --git a/examples/linear_gaussian/learning_model/config.yaml b/examples/linear_gaussian/learning_model/config.yaml new file mode 100644 index 0000000..3a8e4f8 --- /dev/null +++ b/examples/linear_gaussian/learning_model/config.yaml @@ -0,0 +1,18 @@ +campaign_id: 2 + +total_samples: 100 +num_samples: 100 + +model_noise_cov: 1.0e-1 +objective_scaling: 1.0e-10 + +# +compute_map: False +compute_mle: False +compute_post: True + +# +compute_identifiability: True +global_num_outer_samples: 10000 +global_num_inner_samples: 5000 +restart_identifiability: False diff --git a/examples/linear_gaussian/learning_model/create_new_campaign.sh b/examples/linear_gaussian/learning_model/create_new_campaign.sh new file mode 100755 index 0000000..d993a0a --- /dev/null +++ b/examples/linear_gaussian/learning_model/create_new_campaign.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +campaign_results_path=$PWD/campaign_results +dname=$campaign_results_path/campaign_${1} + +if [[ -d $dname ]]; then + + read -p "Campaign already exists! Recreate it? [y/yes/Y] : " flag + + if [[ $flag == "y" || $flag == "yes" || $flag == "Y" ]]; then + + rm -rf $dname + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" (Re)created using config.yaml" + + # Generating the data from using config file + python generate_training_data.py ${1} + + else + + echo "Campaign "${1}" was not removed" + + fi + +else + + mkdir -p $dname + fname=$dname/readme.txt + fig_file_name=$dname/Figures + mkdir -p $fig_file_name + echo "Campaign description" >> $fname + cp config.yaml $dname + echo "Campaign "${1}" created using config.yaml" + + # Generating the data from using config file + python generate_training_data.py ${1} + + +fi diff --git a/examples/linear_gaussian/learning_model/generate_training_data.py b/examples/linear_gaussian/learning_model/generate_training_data.py new file mode 100644 index 0000000..b7829a1 --- /dev/null +++ b/examples/linear_gaussian/learning_model/generate_training_data.py @@ -0,0 +1,63 @@ +import numpy as np +import matplotlib.pyplot as plt +import os +import sys +import yaml + +sys.path.append("../forward_model/") +from linear_gaussian import linear_gaussian + + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) + +with open("./config.yaml", "r") as config_file: + config_data = yaml.safe_load(config_file) + +campaign_id = config_data["campaign_id"] + +assert campaign_id == int(sys.argv[1]), "Make sure the campaign id match" + +total_samples = config_data["total_samples"] +true_model = linear_gaussian(spatial_res=total_samples) +true_prediction = true_model.compute_prediction(theta=true_model.true_theta) + +# Subsampling +num_sub_samples = config_data["num_samples"] +assert num_sub_samples <= total_samples +sample_idx = np.arange(total_samples) +np.random.shuffle(sample_idx) +sample_idx = sample_idx[:num_sub_samples] + + +# Adding noise +model_noise_cov = config_data["model_noise_cov"] +noise = np.sqrt(model_noise_cov) * (np.random.randn(num_sub_samples)) +training_data = np.zeros((num_sub_samples, 2)) +training_data[:, 0] = true_model.xtrain[sample_idx] +training_data[:, 1] = true_prediction[sample_idx, 0] + noise + + +# Saving data +data_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/training_data.npy".format(campaign_id) +) +sample_idx_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/sample_idx.npy".format(campaign_id) +) +np.save(data_save_path, training_data) +np.save(sample_idx_save_path, sample_idx) + + +fig_save_path = os.path.join( + "./campaign_results", "campaign_{0:d}/Figures/training_data.png".format(campaign_id) +) +plt.figure(figsize=(10, 5)) +plt.plot(true_model.xtrain, true_prediction, label="True", c="r") +plt.scatter(training_data[:, 0], training_data[:, 1], label="Data", c="k") +plt.legend(loc="upper left") +plt.tight_layout() +plt.savefig(fig_save_path) +plt.close() diff --git a/examples/linear_gaussian/learning_model/run.py b/examples/linear_gaussian/learning_model/run.py new file mode 100644 index 0000000..73eba63 --- /dev/null +++ b/examples/linear_gaussian/learning_model/run.py @@ -0,0 +1,963 @@ +import numpy as np +from mpi4py import MPI +import yaml +import sys +import os +import matplotlib.pyplot as plt +from matplotlib.ticker import MultipleLocator +from scipy.optimize import minimize +from itertools import combinations + +sys.path.append("../../../information_metrics/") +sys.path.append("../forward_model/") +sys.path.append("../../../mcmc/") +from linear_gaussian import linear_gaussian +from compute_identifiability import conditional_mutual_information +from SobolIndex import SobolIndex +from mcmc import adaptive_metropolis_hastings +from mcmc_utils import sub_sample_data + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=25) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=18, titlepad=20) +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class learn_linear_gaussian: + def __init__( + self, + config_data, + campaign_path, + prior_mean, + prior_cov, + use_normalization_coeff=False, + normalization_coeff=None, + ): + self.prior_mean = prior_mean + self.prior_cov = prior_cov + + self.prior_cov = prior_cov + self.num_parameters = self.prior_mean.shape[0] + if use_normalization_coeff is True: + assert normalization_coeff is not None, "Normalization coeff is None" + self.normalization_coeff = normalization_coeff + else: + self.normalization_coeff = np.ones(self.num_parameters) + + # Extract configurations + self.campaign_path = campaign_path + self.model_noise_cov = config_data["model_noise_cov"] + + training_data_set = np.load( + os.path.join(self.campaign_path, "training_data.npy") + ) + self.xtrain = training_data_set[:, 0] + self.ytrain = training_data_set[:, 1].reshape(1, -1) + self.sample_idx = np.load(os.path.join(self.campaign_path, "sample_idx.npy")) + + self.num_data_points = self.ytrain.shape[0] + self.spatial_resolution = self.ytrain.shape[1] + self.objective_scaling = config_data["objective_scaling"] + + # Model identifiability + self.global_num_outer_samples = config_data["global_num_outer_samples"] + self.global_num_inner_samples = config_data["global_num_inner_samples"] + self.restart_identifiability = config_data["restart_identifiability"] + if rank == 0: + log_file_path = os.path.join(self.campaign_path, "log_file.dat") + self.log_file = open(log_file_path, "w") + else: + self.log_file = None + + # Forward model + self.linear_gaussian_model = linear_gaussian( + spatial_res=self.spatial_resolution + ) + self.vm = self.linear_gaussian_model.compute_vm() + self.sub_sampled_vm = self.vm[self.sample_idx, :] + + self.sample_idx_mat = np.zeros( + (self.spatial_resolution, config_data["total_samples"]) + ) + self.sample_idx_mat[np.arange(self.spatial_resolution), self.sample_idx] = 1 + self.forward_model = self.linear_gaussian_model.compute_prediction + + def compute_model_prediction(self, theta, proc_log_file=None, true_theta=False): + if true_theta is True: + normalized_theta = theta + else: + normalized_theta = theta * self.normalization_coeff + prediction = self.forward_model(theta=normalized_theta)[self.sample_idx, :].T + return prediction + + def sort_prediction(self, prediction): + sorted_idx_id = np.argsort(self.sample_idx) + sorted_prediction = prediction[:, sorted_idx_id] + return sorted_prediction + + def sort_input(self): + sorted_idx_id = np.argsort(self.sample_idx) + return self.xtrain[sorted_idx_id] + + def compute_log_likelihood(self, theta): + """Function computes the log likelihood""" + prediction = self.compute_model_prediction(theta) + error = self.ytrain - prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + log_likelihood = -(0.5 / self.model_noise_cov) * np.sum(error_norm_sq, axis=0) + return log_likelihood + + def compute_mle(self): + """Function computes the mle estimate""" + + def negative_log_likelihood(theta): + objective_function = -self.objective_scaling * self.compute_log_likelihood( + theta + ) + print("MLE objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize( + negative_log_likelihood, + np.random.randn(self.num_parameters), + method="Nelder-Mead", + ) + res = minimize(negative_log_likelihood, res.x) + if res.success is False: + print("Numerical minima not found") + + theta_mle = res.x + + prediction = self.compute_model_prediction(theta=res.x) + prediction_data = np.zeros((self.spatial_resolution, 2)) + prediction_data[:, 0] = self.xtrain + prediction_data[:, 1] = prediction.ravel() + + save_prediction_path = os.path.join(self.campaign_path, "prediction_mle.npy") + save_mle_path = os.path.join(self.campaign_path, "theta_mle.npy") + + np.save(save_prediction_path, prediction_data) + np.save(save_mle_path, theta_mle) + + return theta_mle + + def compute_map(self): + """Function computes the map estimate""" + theta_init = np.random.randn(self.num_parameters) + + def objective_function(theta): + objective_function = ( + -self.objective_scaling * self.compute_unnormalized_posterior(theta) + ) + print("MAP objective : {0:.18e}".format(objective_function)) + return objective_function + + res = minimize(objective_function, theta_init, method="Nelder-Mead") + res = minimize(objective_function, res.x) + + theta_map = res.x + theta_map_cov = res.hess_inv + + save_map_path = os.path.join(self.campaign_path, "theta_map.npy") + save_map_cov_path = os.path.join(self.campaign_path, "theta_map_cov.npy") + + np.save(save_map_path, theta_map.reshape(-1, 1)) + np.save(save_map_cov_path, theta_map_cov) + + return theta_map.reshape(-1, 1), theta_map_cov + + def compute_unnormalized_posterior(self, theta): + """Function computes the unnormalized log posterior""" + unnormalized_log_likelihood = self.compute_log_likelihood(theta) + unnormalized_log_prior = self.compute_log_prior(theta) + unnormalized_log_posterior = ( + unnormalized_log_likelihood + unnormalized_log_prior + ) + return unnormalized_log_posterior + + def compute_log_prior(self, theta): + """Function computes the log prior (unnormalized)""" + error = (theta - self.prior_mean.ravel()).reshape(-1, 1) + exp_term_solve = error.T @ np.linalg.solve(self.prior_cov, error) + exp_term = -0.5 * exp_term_solve + return exp_term.item() + + def plot_mle_estimate(self, theta_mle): + prediction = self.compute_model_prediction(theta_mle) + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta, true_theta=True + ) + + sorted_prediction = self.sort_prediction(prediction) + sorted_prediction_true = self.sort_prediction(prediction_true) + + save_fig_path = os.path.join(self.campaign_path, "Figures/prediction_mle.png") + + sorted_xtrain = self.sort_input() + fig, axs = plt.subplots(figsize=(10, 6)) + axs.scatter(self.xtrain, self.ytrain.ravel(), label="Data", c="k", alpha=0.8) + axs.plot( + sorted_xtrain, sorted_prediction.ravel(), label="Prediction", color="r" + ) + axs.plot( + sorted_xtrain, sorted_prediction_true.ravel(), "--", label="True", color="k" + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.legend(framealpha=1.0) + axs.set_title("M.L.E.") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def compute_evidence_stats(self, itheta=None): + """Fuction computes the evidence stats""" + if itheta is None: + evidence_mean = self.compute_model_prediction(theta=self.prior_mean).T + + evidence_cov = ( + self.sub_sampled_vm @ self.prior_cov @ self.sub_sampled_vm.T + + self.model_noise_cov * (self.sample_idx_mat @ self.sample_idx_mat.T) + ) + + else: + evidence_mean = self.compute_model_prediction(theta=self.prior_mean.ravel()) + + vm_selected = self.vm[:, itheta].reshape(-1, 1) + select_prior_cov = (np.diag(self.prior_cov)[itheta]).reshape(1, 1) + evidence_cov = vm_selected @ select_prior_cov @ vm_selected.T + + return evidence_mean, evidence_cov + + def compute_gaussian_entropy(self, cov): + """Function computes the entropy of a gaussian distribution""" + d = cov.shape[0] + return 0.5 * d * np.log(2 * np.pi * np.exp(1)) + 0.5 * np.log( + np.linalg.det(cov) + ) + + def compute_true_mutual_information(self): + """Function comptues the true mutual information""" + evidence_mean, evidence_cov = self.compute_evidence_stats() + evidence_entropy = self.compute_gaussian_entropy(cov=evidence_cov) + print("evidence entropy : {}".format(evidence_entropy)) + + prior_entropy = self.compute_gaussian_entropy(cov=self.prior_cov) + print("prior entropy : {}".format(prior_entropy)) + + correlation = self.prior_cov @ (self.sub_sampled_vm).T + d = self.num_parameters + self.spatial_resolution + joint_mean = np.zeros((d, 1)) + joint_cov = np.zeros((d, d)) + + joint_mean[: self.num_parameters, :] = self.prior_mean + joint_mean[self.num_parameters :, :] = evidence_mean + joint_cov[: self.num_parameters, : self.num_parameters] = self.prior_cov + joint_cov[: self.num_parameters, self.num_parameters :] = correlation + joint_cov[self.num_parameters :, : self.num_parameters] = correlation.T + joint_cov[self.num_parameters :, self.num_parameters :] = evidence_cov + joint_entropy = self.compute_gaussian_entropy(cov=joint_cov) + print("joint entropy : {}".format(joint_entropy)) + mutual_information = prior_entropy - joint_entropy + evidence_entropy + print("Theoretical mutual informaiton : {}".format(mutual_information)) + + def compute_true_individual_parameter_data_mutual_information(self): + """Function computes the true conditional mutual information for + each parameter + """ + # Definitions + individual_mutual_information = np.zeros(self.num_parameters) + evidence_mean, evidence_cov = self.compute_evidence_stats() + + total_corr = self.compute_correlation( + parameter_pair=np.arange(self.num_parameters) + ) + + total_joint_mean, total_joint_cov = self.build_joint( + parameter_mean=self.prior_mean, + parameter_cov=self.prior_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=total_corr, + ) + + total_joint_entropy = self.compute_gaussian_entropy(cov=total_joint_cov) + + for iparameter in range(self.num_parameters): + parameter_pair = np.array([iparameter]) + fixed_parameter_id = np.arange(self.num_parameters) != iparameter + + # selected_parameter_mean = self.prior_mean[parameter_pair].reshape( + # parameter_pair.shape[0], 1 + # ) + selected_parameter_cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + + fixed_parameter_mean = self.prior_mean[fixed_parameter_id].reshape( + sum(fixed_parameter_id), 1 + ) + fixed_parameter_cov = np.diag(np.diag(self.prior_cov)[fixed_parameter_id]) + + prior_entropy = self.compute_gaussian_entropy(cov=selected_parameter_cov) + + correlation = self.compute_correlation(parameter_pair=fixed_parameter_id) + + individual_joint_mean, individual_joint_cov = self.build_joint( + parameter_mean=fixed_parameter_mean, + parameter_cov=fixed_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation, + ) + individual_joint_entropy = self.compute_gaussian_entropy( + cov=individual_joint_cov + ) + individual_mutual_information[iparameter] = ( + prior_entropy - total_joint_entropy + individual_joint_entropy + ) + + print( + "True individual mutual information I(theta_i;Y|theta_not_i) : {}".format( + individual_mutual_information + ) + ) + + def compute_true_pair_parameter_data_mutual_information(self): + """Function computes the true entropy between the parameters given the data""" + evidence_mean, evidence_cov = self.compute_evidence_stats() + parameter_combinations = np.array( + list(combinations(np.arange(self.num_parameters), 2)) + ) + + def get_fixed_parameter_id(x): + parameter_id = np.arange(self.num_parameters) + return ~np.logical_or(parameter_id == x[0], parameter_id == x[1]) + + def get_selected_parameter_joint_id(x, select_id): + parameter_id = np.arange(self.num_parameters) + condition_1 = ~np.logical_or(parameter_id == x[0], parameter_id == x[1]) + condition_2 = parameter_id == select_id + return np.logical_or(condition_1, condition_2) + + for iparameter in parameter_combinations: + # h(y, \theta_k) + fixed_parameter_mean = self.prior_mean[ + get_fixed_parameter_id(iparameter), : + ] + fixed_parameter_cov = np.diag( + np.diag(self.prior_cov)[get_fixed_parameter_id(iparameter)] + ) + correlation_fixed_param_data = self.compute_correlation( + parameter_pair=get_fixed_parameter_id(iparameter) + ) + joint_fixed_param_mean, joint_fixed_param_cov = self.build_joint( + parameter_mean=fixed_parameter_mean, + parameter_cov=fixed_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_fixed_param_data, + ) + joint_fixed_param_entropy = self.compute_gaussian_entropy( + cov=joint_fixed_param_cov + ) + print("h(Y, theta_k) : {}".format(joint_fixed_param_entropy)) + + # h(y, theta_i, theta_k) + selected_parameter_id = get_selected_parameter_joint_id( + iparameter, iparameter[0] + ) + selected_parameter_mean = self.prior_mean[selected_parameter_id, :] + selected_parameter_cov = np.diag( + np.diag(self.prior_cov)[selected_parameter_id] + ) + correlation_pair_param_data = self.compute_correlation( + parameter_pair=selected_parameter_id + ) + + joint_pair_param_mean, joint_pair_param_cov = self.build_joint( + parameter_mean=selected_parameter_mean, + parameter_cov=selected_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_pair_param_data, + ) + joint_pair_param_one_entropy = self.compute_gaussian_entropy( + cov=joint_pair_param_cov + ) + print("h(Y, theta_i, theta_k) : {}".format(joint_pair_param_one_entropy)) + + # h(y, theta_j, theta_k) + selected_parameter_id = get_selected_parameter_joint_id( + iparameter, iparameter[1] + ) + selected_parameter_mean = self.prior_mean[selected_parameter_id, :] + selected_parameter_cov = np.diag( + np.diag(self.prior_cov)[selected_parameter_id] + ) + correlation_pair_param_data = self.compute_correlation( + parameter_pair=selected_parameter_id + ) + + joint_pair_param_mean, joint_pair_param_cov = self.build_joint( + parameter_mean=selected_parameter_mean, + parameter_cov=selected_parameter_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=correlation_pair_param_data, + ) + joint_pair_param_two_entropy = self.compute_gaussian_entropy( + cov=joint_pair_param_cov + ) + print("h(Y, theta_j, theta_k) : {}".format(joint_pair_param_two_entropy)) + + # h(y, theta_i, theta_j, theta_k) + total_correlation = self.compute_correlation( + parameter_pair=np.arange(self.num_parameters) + ) + + joint_mean, joint_cov = self.build_joint( + parameter_mean=self.prior_mean, + parameter_cov=self.prior_cov, + evidence_mean=evidence_mean, + evidence_cov=evidence_cov, + correlation=total_correlation, + ) + joint_entropy = self.compute_gaussian_entropy(cov=joint_cov) + print("h(Y, theta_i, theta_j, theta_k) : {}".format(joint_entropy)) + conditional_mutual_information = ( + joint_pair_param_one_entropy + + joint_pair_param_two_entropy + - joint_fixed_param_entropy + - joint_entropy + ) + + print( + "I(theta_{};theta_{} | y, theta_[not selected]) : {}".format( + iparameter[0], iparameter[1], conditional_mutual_information + ) + ) + print("------------") + + def compute_correlation(self, parameter_pair): + """Function computes the correlation""" + cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + vm_selected = self.sample_idx_mat @ self.vm[:, parameter_pair] + return cov @ vm_selected.T + + def build_joint( + self, parameter_mean, parameter_cov, evidence_mean, evidence_cov, correlation + ): + """Function builds the joint""" + num_parameters = parameter_mean.shape[0] + dim = num_parameters + evidence_mean.shape[0] + joint_mean = np.zeros((dim, 1)) + joint_cov = np.zeros((dim, dim)) + joint_mean[:num_parameters, :] = parameter_mean + joint_mean[num_parameters:, :] = evidence_mean + joint_cov[:num_parameters, :num_parameters] = parameter_cov + joint_cov[:num_parameters, num_parameters:] = correlation + joint_cov[num_parameters:, :num_parameters] = correlation.T + joint_cov[num_parameters:, num_parameters:] = evidence_cov + + return joint_mean, joint_cov + + def update_prior(self, theta_mean, theta_cov): + self.prior_mean = theta_mean + self.prior_cov = theta_cov + + def compute_esimated_mi(self): + mi_estimator = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + mi_estimator.compute_individual_parameter_data_mutual_information_via_mc( + use_quadrature=True, single_integral_gaussian_quad_pts=50 + ) + + mi_estimator.compute_posterior_pair_parameter_mutual_information( + use_quadrature=True, + single_integral_gaussian_quad_pts=50, + double_integral_gaussian_quad_pts=50, + ) + + def compute_sobol_indices(self): + """Function computes the sobol indices""" + + def forward_model(theta): + return self.compute_model_prediction(theta) + + sobol_index = SobolIndex( + forward_model=forward_model, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + global_num_outer_samples=self.global_num_outer_samples, + global_num_inner_samples=self.global_num_inner_samples, + model_noise_cov_scalar=self.model_noise_cov, + # model_noise_cov_scalar=0.0, + data_shape=(self.num_data_points, self.spatial_resolution), + write_log_file=True, + save_path=os.path.join(self.campaign_path, "SobolIndex"), + ) + + sobol_index.comp_first_order_sobol_indices() + sobol_index.comp_total_effect_sobol_indices() + + def plot_map_estimate(self, theta_map, theta_map_cov): + num_samples = 1000 + theta = theta_map + np.linalg.cholesky(theta_map_cov) @ np.random.randn( + self.num_parameters, num_samples + ) + + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta, + true_theta=True, + ) + + prediction = np.zeros(self.ytrain.shape + (num_samples,)) + for isample in range(num_samples): + prediction[:, :, isample] = self.compute_model_prediction( + theta=theta[:, isample] + ) + + prediction_mean = np.mean(prediction, axis=-1) + prediction_std = np.std(prediction, axis=-1) + sorted_prediction_mean = self.sort_prediction(prediction=prediction_mean) + sorted_prediction_std = self.sort_prediction(prediction=prediction_std) + sorted_prediction_true = self.sort_prediction(prediction_true) + upper_lim = sorted_prediction_mean + sorted_prediction_std + lower_lim = sorted_prediction_mean - sorted_prediction_std + + sorted_input = self.sort_input() + + save_fig_path = os.path.join(self.campaign_path, "Figures/prediction_map.png") + fig, axs = plt.subplots(figsize=(12, 6)) + axs.scatter( + self.xtrain, self.ytrain.ravel(), c="k", s=30, zorder=-1, label="Data" + ) + axs.plot( + sorted_input, + sorted_prediction_mean.ravel(), + color="r", + label=r"$\mu_{prediction}$", + ) + axs.fill_between( + sorted_input, + upper_lim.ravel(), + lower_lim.ravel(), + ls="--", + lw=2, + alpha=0.3, + color="r", + label=r"$\pm\sigma$", + ) + + axs.plot( + sorted_input, sorted_prediction_true.ravel(), "--", label="True", color="k" + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.legend(framealpha=1.0) + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.set_ylim([-8, 8]) + axs.yaxis.set_minor_locator(MultipleLocator(1)) + axs.xaxis.set_minor_locator(MultipleLocator(0.25)) + # axs.set_title("M.A.P. Prediction") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + def compute_mcmc_samples(self, theta_map, theta_map_cov): + """Function computes the mcmc samples""" + + def compute_post(theta): + return self.compute_unnormalized_posterior(theta) + + mcmc_sampler = adaptive_metropolis_hastings( + initial_sample=theta_map.ravel(), + target_log_pdf_evaluator=compute_post, + # num_samples=200000, + # adapt_sample_threshold=10000, + num_samples=20, + adapt_sample_threshold=10, + initial_cov=1e-2 * theta_map_cov, + ) + + mcmc_sampler.compute_mcmc_samples(verbose=True) + + # Compute acceptance rate + ar = mcmc_sampler.compute_acceptance_ratio() + if rank == 0: + print("Acceptance rate: ", ar) + + # Compute the burn in samples + burned_samples = sub_sample_data( + mcmc_sampler.samples, frac_burn=0.5, frac_use=0.7 + ) + + np.save( + os.path.join( + self.campaign_path, "burned_samples_rank_{}_.npy".format(rank) + ), + burned_samples, + ) + + return burned_samples + + def compute_variance_convergence(self): + """Function computes the variance convergence""" + + num_experiments = 10 + # eval_global_num_samples = np.logspace(1, 6, 6).astype(int) + eval_global_num_samples = np.logspace(1, 2, 2).astype(int) + + individual_mi = np.zeros( + (self.num_parameters, len(eval_global_num_samples), num_experiments) + ) + + num_combinations = len(list(combinations(np.arange(self.num_parameters), 2))) + pair_mi = np.zeros( + (num_combinations, eval_global_num_samples.shape[0], num_experiments) + ) + + for iexp in range(num_experiments): + if rank == 0: + print("Experiment: ", iexp) + + for ii, global_num_samples in enumerate(eval_global_num_samples): + + if rank == 0: + print("Global num samples: ", global_num_samples) + + est_cmi = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=global_num_samples, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + individual_estimator = ( + est_cmi.compute_individual_parameter_data_mutual_information_via_mc + ) + + pair_mi_estimator = ( + est_cmi.compute_posterior_pair_parameter_mutual_information + ) + + individual_mi[:, ii, iexp] = individual_estimator( + use_quadrature=True, single_integral_gaussian_quad_pts=50 + ) + + pair_mi[:, ii, iexp] = pair_mi_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=50, + double_integral_gaussian_quad_pts=50, + ) + + if rank == 0: + print("Individual MI: ", individual_mi[:, ii, iexp]) + print("Pair MI: ", pair_mi[:, ii, iexp]) + + if rank == 0: + np.save( + os.path.join( + self.campaign_path, "individual_mi_variance_convergence.npy" + ), + individual_mi, + ) + + np.save( + os.path.join( + self.campaign_path, "pair_mi_variance_convergence.npy" + ), + pair_mi, + ) + + def compute_bias_convergence(self): + """Function computes the bias convergence""" + + num_experiments = 10 + + eval_quad_points = np.array([5, 10]) + + individual_mi = np.zeros( + (self.num_parameters, len(eval_quad_points), num_experiments) + ) + num_combinations = len(list(combinations(np.arange(self.num_parameters), 2))) + pair_mi = np.zeros((num_combinations, len(eval_quad_points), num_experiments)) + + for iexp in range(num_experiments): + if rank == 0: + print("Experiment: ", iexp) + + for ii, quad_points in enumerate(eval_quad_points): + + if rank == 0: + print("Single quad pts: ", quad_points) + print( + "Total Double quad pts" "(after tensorize): ", quad_points**2 + ) + + est_cmi = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov, + global_num_outer_samples=10000, + global_num_inner_samples=self.global_num_inner_samples, + save_path=self.campaign_path, + restart=self.restart_identifiability, + ytrain=self.ytrain, + log_file=self.log_file, + ) + + individual_estimator = ( + est_cmi.compute_individual_parameter_data_mutual_information_via_mc + ) + + pair_mi_estimator = ( + est_cmi.compute_posterior_pair_parameter_mutual_information + ) + + individual_mi[:, ii, iexp] = individual_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=quad_points, + ) + + pair_mi[:, ii, iexp] = pair_mi_estimator( + use_quadrature=True, + single_integral_gaussian_quad_pts=quad_points, + double_integral_gaussian_quad_pts=quad_points, + ) + + if rank == 0: + print("Individual MI: ", individual_mi[:, ii, iexp]) + print("Pair MI: ", pair_mi[:, ii, iexp]) + + if rank == 0: + np.save( + os.path.join( + self.campaign_path, "individual_mi_bias_convergence.npy" + ), + individual_mi, + ) + + np.save( + os.path.join(self.campaign_path, "pair_mi_bias_convergence.npy"), + pair_mi, + ) + + def plot_aggregate_post(self, samples): + """Function plots the aggregate posterior""" + agg_prediction = np.zeros((self.ytrain.shape + (samples.shape[0],))) + + prediction_true = self.compute_model_prediction( + self.linear_gaussian_model.true_theta + ) + + for i, sample in enumerate(samples): + agg_prediction[:, :, i] = self.compute_model_prediction(sample) + + agg_prediction_list = comm.gather(agg_prediction, root=0) + + if rank == 0: + agg_prediction_global = np.concatenate(agg_prediction_list, axis=2) + + prediction_mean = np.mean(agg_prediction_global, axis=-1) + prediction_std = np.std(agg_prediction_global, axis=-1) + sorted_prediction_mean = self.sort_prediction(prediction=prediction_mean) + sorted_prediction_std = self.sort_prediction(prediction=prediction_std) + sorted_prediction_true = self.sort_prediction(prediction_true) + upper_lim = sorted_prediction_mean + sorted_prediction_std + lower_lim = sorted_prediction_mean - sorted_prediction_std + + np.save( + os.path.join(self.campaign_path, "agg_prediction_mean.npy"), + sorted_prediction_mean, + ) + np.save( + os.path.join(self.campaign_path, "agg_prediction_std.npy"), + sorted_prediction_std, + ) + + sorted_input = self.sort_input() + + save_fig_path = os.path.join( + self.campaign_path, "Figures/prediction_agg_post.png" + ) + fig, axs = plt.subplots(figsize=(12, 6)) + axs.scatter( + self.xtrain, self.ytrain.ravel(), c="k", s=30, zorder=-1, label="Data" + ) + axs.plot( + sorted_input, + sorted_prediction_mean.ravel(), + color="r", + label=r"$\mu_{prediction}$", + ) + axs.fill_between( + sorted_input, + upper_lim.ravel(), + lower_lim.ravel(), + ls="--", + lw=2, + alpha=0.3, + color="r", + label=r"$\pm\sigma$", + ) + + axs.plot( + sorted_input, + sorted_prediction_true.ravel(), + "--", + label="True", + color="k", + ) + axs.grid(True, axis="both", which="major", color="k", alpha=0.5) + axs.grid(True, axis="both", which="minor", color="grey", alpha=0.3) + axs.legend(framealpha=1.0, loc="lower right") + axs.set_xlabel("d") + axs.set_ylabel("y") + axs.set_ylim([-8, 8]) + axs.yaxis.set_minor_locator(MultipleLocator(1)) + axs.yaxis.set_major_locator(MultipleLocator(5)) + axs.xaxis.set_minor_locator(MultipleLocator(0.25)) + # axs.set_title("M.A.P. Prediction") + plt.tight_layout() + plt.savefig(save_fig_path) + plt.close() + + +def load_configuration_file(config_file_path="./config.yaml"): + """Function loads the configuration file""" + with open(config_file_path, "r") as config_file: + config_data = yaml.safe_load(config_file) + if rank == 0: + print("Loaded %s configuration file" % (config_file_path), flush=True) + return config_data + + +def compute_normalization_coefficient(individual_mi, pair_mi): + num_parameters = len(individual_mi) + alpha = individual_mi / np.sum(individual_mi) + + parameter_comb = np.array(list(combinations(np.arange(num_parameters), 2))) + mat = np.zeros((num_parameters, num_parameters)) + + for ii in range(parameter_comb.shape[0]): + + mat[parameter_comb[ii, 0], parameter_comb[ii, 1]] = pair_mi[ii] + mat[parameter_comb[ii, 1], parameter_comb[ii, 0]] = pair_mi[ii] + + beta = np.sum(mat, axis=1) / np.sum(mat) + gamma_unnormalized = alpha / (alpha + beta) + gamma = gamma_unnormalized / np.sum(gamma_unnormalized) + + return gamma + + +def main(): + prior_mean = np.zeros((3, 1)) + prior_cov = np.eye(3) + + gamma_coeff = compute_normalization_coefficient( + individual_mi=[3.43029824, 2.85712464, 2.59584844], + pair_mi=[-4.20996571e-18, 1.45196126e00, -9.15001408e-18], + ) + + # Load the config data + config_data = load_configuration_file() + + # Campaign path + campaign_path = os.path.join( + os.getcwd(), "campaign_results/campaign_%d" % (config_data["campaign_id"]) + ) + + # Leaning model + + learning_model = learn_linear_gaussian( + config_data=config_data, + campaign_path=campaign_path, + prior_mean=prior_mean, + prior_cov=prior_cov, + use_normalization_coeff=True, + normalization_coeff=gamma_coeff, + ) + + if config_data["compute_mle"]: + theta_mle = learning_model.compute_mle() + elif config_data["compute_map"]: + theta_map, theta_map_cov = learning_model.compute_map() + elif config_data["compute_post"]: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + samples = learning_model.compute_mcmc_samples( + theta_map=theta_map, theta_map_cov=theta_map_cov + ) + + learning_model.plot_aggregate_post(samples) + + if "--plotmle" in sys.argv: + theta_mle = np.load(os.path.join(campaign_path, "theta_mle.npy")) + learning_model.plot_mle_estimate( + theta_mle=theta_mle, + ) + + if "--plotmap" in sys.argv: + theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + + learning_model.plot_map_estimate( + theta_map=theta_map, theta_map_cov=theta_map_cov + ) + + # Identifiability + if config_data["compute_identifiability"]: + + # Update the prior distribution + # theta_map = np.load(os.path.join(campaign_path, "theta_map.npy")) + # theta_map_cov = np.load(os.path.join(campaign_path, "theta_map_cov.npy")) + # learning_model.update_prior(theta_mean=theta_map, theta_cov=theta_map_cov) + + # True MI + # learning_model.compute_true_mutual_information() + # learning_model.compute_true_individual_parameter_data_mutual_information() + # learning_model.compute_true_pair_parameter_data_mutual_information() + + # Estimated MI + # learning_model.compute_esimated_mi() + + # Sobol indices + learning_model.compute_sobol_indices() + + # Variance convergence + # learning_model.compute_variance_convergence() + + # Bias convergence + # learning_model.compute_bias_convergence() + + if rank == 0: + learning_model.log_file.close() + + +if __name__ == "__main__": + main() diff --git a/examples/non_equil_gas/forward_model/config.yml b/examples/non_equil_gas/forward_model/config.yml new file mode 100644 index 0000000..0c48ebf --- /dev/null +++ b/examples/non_equil_gas/forward_model/config.yml @@ -0,0 +1,17 @@ +AMBIENT_TEMP: 10000. # (K) +AMBIENT_PRESSURE: 10000. # (Pa) +INITIAL_TEMP: 1000. # (K) +SYSTEM: 'H2_H' # Options: H2_H, N2_N +NUM_SPECIES: 2 + +# Reduced order modeling +NUM_BINS: 8 + +# Training +NUM_EPOCHS: 500 +REGULARIZATION_PARAM_1: 0 +REGULARIZATION_PARAM_2: 0 +LEARNING_RATE: 5.0e-1 + +#Path +BASE_PATH: '/home/sbhola/Documents/CASLAB/GIM/examples/non_equil_gas/forward_model' diff --git a/examples/non_equil_gas/forward_model/run.py b/examples/non_equil_gas/forward_model/run.py new file mode 100644 index 0000000..9912674 --- /dev/null +++ b/examples/non_equil_gas/forward_model/run.py @@ -0,0 +1,35 @@ +# import numpy as np +import yaml +import torch +from solver import SOLVER + + +def configure_model(config_data, device): + """Configure the model based on the config file""" + + +def load_config(config_file_path="./config.yml"): + """Load the config file + Input: + config_file_path: path to the config file + Output: + config_data: dictionary containing the config data + """ + with open(config_file_path, "r") as config_file: + config_data = yaml.load(config_file, Loader=yaml.FullLoader) + return config_data + + +def main(): + # Load the config file + config_data = load_config() + + # Device definition + device = torch.device("cuda:3" if torch.cuda.is_available() else "cpu") + + # Model definition + SOLVER(config_data, device) + + +if __name__ == "__main__": + main() diff --git a/examples/non_equil_gas/forward_model/solver.py b/examples/non_equil_gas/forward_model/solver.py new file mode 100644 index 0000000..5d359ac --- /dev/null +++ b/examples/non_equil_gas/forward_model/solver.py @@ -0,0 +1,193 @@ +import torch +import torch.nn as nn +import numpy as np +import os.path as osp +import yaml +import warnings + + +class SOLVER(nn.Module): + boltzmann_constant = 1.38064852e-23 # J/K #IUKB + boltzmann_constant_ev = 8.617333262145e-5 # eV/K #K + planck_constant = 6.62607015e-34 # J s #IUH + avagaadro_constant = 6.02214076e23 # mol^-1 #IUNA + pi = np.pi # IUPI + universal_gas_constant = 8.3144598 # J/(mol K) #URG or IUR + electron_charge = 1.602191e-19 # C #IUE + + def __init__(self, config_data, device): + self.system = config_data["SYSTEM"] + self.ambient_temp = config_data["AMBIENT_TEMP"] + self.ambient_pressure = config_data["AMBIENT_PRESSURE"] + self.initial_temp = config_data["INITIAL_TEMP"] + self.num_species = config_data["NUM_SPECIES"] + + self.num_bins = config_data["NUM_BINS"] + + self.system_path = osp.join( + config_data["BASE_PATH"], "system_definition/" + self.system + "_system" + ) + system_parameter_file_path = osp.join(self.system_path, "system_parameters.yml") + + # Get system-specific parameters + print("Evaluating {} system".format(self.system)) + if osp.exists(system_parameter_file_path): + with open(system_parameter_file_path, "r") as config_file: + self.system_config_data = yaml.load(config_file, Loader=yaml.FullLoader) + else: + raise ValueError("System parameter file does not exist") + + # GAT + self.ground_state_degeneracy = self.system_config_data[ + "GROUND_STATE_DEGENERACY" + ] + # EAT + self.activation_energy = self.system_config_data["ACTIVATION_ENERGY"] + + self.num_states = self.system_config_data["NUM_STATES"] + + if self.system == "H2_H": + self.species_list = ["H2", "H"] + self.species_molar_mass = [ + self.system_config_data["MOLAR_MASS_H2"], + self.system_config_data["MOLAR_MASS_H"], + ] + self.molecular_mass = self.system_config_data["MOLECULAR_MASS_H"] + elif self.system == "N2_N": + raise NotImplementedError + self.species_list = ["N2", "N"] + self.species_molar_mass = [ + self.system_config_data["MOLAR_MASS_N2"], + self.system_config_data["MOLAR_MASS_N"], + ] + self.molecular_mass = self.system_config_data["MOLECULAR_MASS_N"] + else: + raise Exception("System {} not implemented".format(self.system)) + + self.specific_gas_constant = [ + self.universal_gas_constant / self.species_molar_mass[i] + for i in range(self.num_species) + ] + + # State to state solution + self.state_to_state_sol = self._load_state_to_state_sol() + + # Energy states + self.energy_states, self.degeneracy = self._load_energy_states() + + # Dissociation coefficients + dissociation_arr_params = self._load_dissociation_arr_params() + self.dissociation_rate = self._comp_arrhenius_rate( + arrhenius_params=dissociation_arr_params, temperature=self.ambient_temp + ) + + self.dissociation_rate = self.dissociation_rate * 1e-6 # Convert cm3 to m3 + + # Compute the recombination rate from the dissociation rate + self.recombination_rate = self._comp_recombination_rate() + + # Excitation-Deexcitation rates coefficients + ( + self.excitation_deexitation_index, + excitation_arr_params, + ) = self._load_excitation_deexcitation_arr_params() + + self.excitation_rate = self._comp_arrhenius_rate( + arrhenius_params=excitation_arr_params, temperature=self.ambient_temp + ) + + self.excitation_rate = self.excitation_rate * 1e-6 # Convert cm3 to m3 + + self.deexcitation_rate = self._comp_deexcitation_rate() + + def _comp_recombination_rate(self): + """Compute the recombination rate from the dissociation rate""" + + assert ( + self.num_species == 2 + ), "Recombination rate only implemented for 2 species" + + fac1 = ( + 2 + * self.pi + * self.universal_gas_constant + / (self.planck_constant * self.avagaadro_constant) ** 2 + ) + warnings.warn("QMOL and QAT are the same thing!!! (Check this)") + QMOL = (fac1 * self.species_molar_mass[0] * self.ambient_temp) ** (1.5) + QAT = (fac1 * self.species_molar_mass[0] * self.ambient_temp) ** (1.5) + + term1 = (self.ground_state_degeneracy * QAT) ** 2 + term2 = torch.exp( + -(2 * self.activation_energy / self.avagaadro_constant - self.energy_states) + / (self.boltzmann_constant * self.ambient_temp) + ) + term3 = self.degeneracy * QMOL + Keq = term1 * term2 / term3 + recom_rate = self.dissociation_rate / Keq + + assert ( + recom_rate.shape[0] == self.num_states + ), "Recombination rate shape does not match energy states" + assert recom_rate.shape[1] == 1, "Recombination rate must be a column vector" + + return recom_rate + + def _load_state_to_state_sol(self): + """Function loads the state to state solution""" + path = osp.join(self.system_path, "state_to_state_solution.dat") + data = torch.from_numpy(np.loadtxt(path, skiprows=10))[ + :, 1 : self.num_species + 1 + ] + return data + + def _load_energy_states(self): + """Function loads the energy states""" + path = osp.join(self.system_path, "energy_states.dat") + data = np.loadtxt(path, skiprows=2) + energy_states = torch.from_numpy(data[:, 1] * self.electron_charge).view(-1, 1) + degeneracy = torch.from_numpy(data[:, 2]).view(-1, 1) + + assert ( + energy_states.shape[0] == self.num_states + ), "Number of energy states does not match" + assert ( + degeneracy.shape[0] == self.num_states + ), "Number of degeneracy states does not match" + + assert energy_states.shape[1] == 1, "Energy states should be a column vector" + assert degeneracy.shape[1] == 1, "degeneracy should be a column vector" + + return energy_states, degeneracy + + def _load_dissociation_arr_params(self): + """Function loads the dissociation arrehenius parameters""" + path = osp.join(self.system_path, "Kinetics_Data/dissociation_rate_arr_fit.dat") + data = np.loadtxt(path, skiprows=3)[:, 1:] + + assert data.shape[0] == self.num_states, "Number of states does not match" + assert data.shape[1] == 3, "Provide 3 parameters for the arrhenius fit" + + return torch.from_numpy(data) + + def _load_excitation_deexcitation_arr_params(self): + """Function loads the excitation arrehenius parameters""" + path = osp.join(self.system_path, "Kinetics_Data/excitation_rate_arr_fit.dat") + data = np.loadtxt(path, skiprows=3) + excitation_deexitation_index = data[:, :2].astype(int) + excitation_arr_params = data[:, 2:] + + return excitation_deexitation_index, torch.from_numpy(excitation_arr_params) + + def _comp_arrhenius_rate(self, arrhenius_params, temperature): + """Function computes the Arrhenius rate from the Arrhenius parameters""" + rate = arrhenius_params[:, 0] * torch.exp( + arrhenius_params[:, 1] * np.log(temperature) + - arrhenius_params[:, 2] / temperature + ) + rate = rate.view(-1, 1) + + # assert(rate.shape[0] == self.num_states), 'Number of states does not match' + assert rate.shape[1] == 1, "Rate should be a column vector" + + return rate diff --git a/examples/non_equil_gas/forward_model/system_definition/H2_H_system/system_parameters.yml b/examples/non_equil_gas/forward_model/system_definition/H2_H_system/system_parameters.yml new file mode 100644 index 0000000..30ec948 --- /dev/null +++ b/examples/non_equil_gas/forward_model/system_definition/H2_H_system/system_parameters.yml @@ -0,0 +1,6 @@ +NUM_STATES: 385 +GROUND_STATE_DEGENERACY: 2.0 +ACTIVATION_ENERGY: 0.216035e+6 +MOLAR_MASS_H: 0.10079e-2 # kg/mol +MOLAR_MASS_H2: 0.20158e-2 # kg/mol +MOLECULAR_MASS_H: 1.6737e-27 #kg diff --git a/examples/non_equil_gas/forward_model/system_definition/create_system.sh b/examples/non_equil_gas/forward_model/system_definition/create_system.sh new file mode 100755 index 0000000..0d4aabe --- /dev/null +++ b/examples/non_equil_gas/forward_model/system_definition/create_system.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Begin user input +system_name='H2_H' +# End user input + +if [ -d $system_name'_system' ]; then + echo "System $system_name already exists" + + read -p "Do you want to overwrite it? [y/n] " -n 1 -r + + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "" + # echo -e "" + echo "Overwriting system $system_name" + rm -rf $system_name'_system' + else + echo "Exiting" + exit 1 + fi + +fi + +echo "Creating system $system_name" +mkdir -p $system_name'_system' +cd $system_name'_system' +mkdir -p 'Kinetics_Data' +mkdir -p 'Cluster_Assignment/Energy_Based_Clustering' +for i in 1 2 4 8 +do + mkdir -p 'Cluster_Assignment/Energy_Based_Clustering/num_bins_'$i +done diff --git a/examples/radiative_heat_transfer/forward_model/rht.py b/examples/radiative_heat_transfer/forward_model/rht.py new file mode 100644 index 0000000..d8bc335 --- /dev/null +++ b/examples/radiative_heat_transfer/forward_model/rht.py @@ -0,0 +1,163 @@ +"""Original code by Vishal Srivastava""" +import numpy as np + + +def setJacobian(jacT, jacbeta, T, T_inf, beta, dy2, relay=False): + jacT[0, 0] = -1.0 + jacT[-1, -1] = -1.0 + + ii = np.linspace(1, jacT.shape[0] - 2, jacT.shape[0] - 2).astype(int) + jacT[ii, ii - 1] = 1.0 / dy2 + jacT[ii, ii] = -2.0 / dy2 - 2e-3 * beta[ii] * T[ii] ** 3 + jacT[ii, ii + 1] = 1.0 / dy2 + + jacbeta[ii, ii] = 5e-4 * (T_inf**4 - T[ii] ** 4) + + if relay: + return jacT, jacbeta + + +class rht: + """radiative heat transfer class""" + + def __init__( + self, T_inf=5.0, n_spatial=129, dt=1e-2, n_iter=1000, tol=1e-8, net=None + ): + self.n_spatial = n_spatial + # Temperature of the one-dimensional body + self.T_inf = T_inf + self.y = np.linspace(0.0, 1.0, self.n_spatial) # Coordinates + # dy^2 to be used in the second derivative + self.dy2 = (self.y[1] - self.y[0]) ** 2 + # Initial temperature of the body at the coordinates specified above + self.T = np.zeros_like(self.y) + + # Augmentation profiles of the model + self.beta = np.ones_like(self.y) + self.dt = dt # Time step to be used in the simulation + # Maximum number of iterations to be run during direct solve + self.n_iter = n_iter + self.print_iter = int(self.n_iter / 50) + # Maximum value of residual at which direct solve can be terminated + self.tol = tol + self.res = np.zeros_like(self.y) # Residuals + # \partial R / \partial T + self.jacT = np.zeros((self.n_spatial, self.n_spatial)) + # \partial R / \partial beta + self.jacbeta = np.zeros((self.n_spatial, self.n_spatial)) + self.net = net # Network parameterization of beta + + def getFeatures(self): + """Function computes the required featured for the NN + outputs: + features : (2, n_spatial) : features for the NN + """ + features = np.zeros((2, self.n_spatial)) + features[0, :] = self.T_inf / self.T_inf + features[1, :] = self.T / self.T_inf + + return features + + def evalResidual(self): + """Funcition computes the residual for the ODE solve""" + self.res[1:-1] = ( + self.T[0:-2] - 2 * self.T[1:-1] + self.T[2:] + ) / self.dy2 + 5e-4 * self.beta[1:-1] * (self.T_inf**4 - self.T[1:-1] ** 4) + self.res[0] = -self.T[0] + self.res[-1] = -self.T[-1] + return self.res + + def compute_beta(self, features): + """Function computes the beta field predicion""" + # Assign the network inputs + self.net.assign_network_IO(network_input=features) + # Deploy Network + self.net.deploy_network() + # Collect prediction + self.beta = self.net.prediction.ravel() + + def implicitEulerUpdate(self): + """Funciton updates the states using implicit Euler time stepping""" + features = self.getFeatures() + # deploying the network for computing beta + if self.net != None: + self.compute_beta(features) + setJacobian(self.jacT, self.jacbeta, self.T, self.T_inf, self.beta, self.dy2) + + self.evalResidual() + self.T = self.T + np.linalg.solve( + np.eye(self.y.shape[0]) / self.dt - self.jacT, self.res + ) + return np.linalg.norm(self.res) + + def direct_solve(self): + """Funciton solves the forward model""" + for iteration in range(self.n_iter): + res_norm = self.implicitEulerUpdate() + # Check if the residual is within tolerance, if yes, save the data and exit the simulation, if no, continue + if res_norm < self.tol: + # print("Forward model converged at residual : {0:.2e} (T_inf : {1:.1f})".format(res_norm, self.T_inf)) + break + elif iteration == self.n_iter - 1: + print( + "Maximum iterations reached! residual norm : {0:.5f}".format( + res_norm + ) + ) + return self.res + + def sub_sample(self, sample_idx): + """Function sub-samples the prediction""" + return self.T[sample_idx] + + def adjoint_solve(self, data, model_noise_cov, scaling, sample_idx): + """Funciton solves the adjoint equations""" + # Compute the permutation matrix + P = np.zeros((sample_idx.shape[0], self.n_spatial)) + P[np.arange(sample_idx.shape[0]), sample_idx] = 1 + + # computing jac_cost_state + prediction = self.sub_sample(sample_idx) + if model_noise_cov == 0: + jac_cost_state = (scaling * (data - prediction)).reshape(1, -1) + else: + jac_cost_state = (scaling * (data - prediction) / model_noise_cov).reshape( + 1, -1 + ) + jac_cost_state = jac_cost_state @ P + + # computing jac_cost_beta + jac_cost_beta = np.zeros((1, self.n_spatial)) + # computing jac_res_state + jac_res_state = self.jacT + # computing jac_res_beta + jac_res_beta = self.jacbeta + + # solving the adjoint equation + psi = np.linalg.solve(jac_res_state.T, jac_cost_state.T) + nabla_cost_beta = jac_cost_beta - psi.T @ jac_res_beta + + return nabla_cost_beta + + def get_jacobians(self): + """Function returns the jacobians""" + + # computing jac_cost_beta + jac_cost_beta = np.zeros((1, self.n_spatial)) + # computing jac_res_state + jac_res_state = self.jacT + # computing jac_res_beta + jac_res_beta = self.jacbeta + + jac = (jac_cost_beta, jac_res_state, jac_res_beta) + + return jac + + def infer_beta(self): + """Function return the beta value""" + # Compute the features + features = self.getFeatures() + # Compute beta + self.compute_beta(features=features) + + return self.beta diff --git a/examples/radiative_heat_transfer/forward_model/rht_true.py b/examples/radiative_heat_transfer/forward_model/rht_true.py new file mode 100644 index 0000000..56a74de --- /dev/null +++ b/examples/radiative_heat_transfer/forward_model/rht_true.py @@ -0,0 +1,107 @@ +# Function originally written by : Dr. Vishal Shrivastava +# Modified by: Sahil Bhola +# Function takes in the parameters for the emmisivity computation and returns.. +# the temperature prediction + +import numpy as np + + +class RHT_True: + # Class to generate reference data (considered truth) for Radiative heat transfer + def __init__( + self, + T_inf=5.0, + npoints=129, + dt=1e-2, + n_iter=1000, + tol=1e-8, + plot=True, + alpha=3 * np.pi / 200, + gamma=1, + delta=5, + ): + """Init Funciton""" + # Temperature of the one-dimensional body + self.T_inf = T_inf + self.y = np.linspace(0.0, 1.0, npoints) # Coordinates + # dy^2 to be used in the second derivative + self.dy2 = (self.y[1] - self.y[0]) ** 2 + # Initial temperature of the body at the coordinates specified above + self.T = np.zeros_like(self.y) + self.dt = dt # Time step to be used in the simulation + # Maximum number of iterations to be run during direct solve + self.n_iter = n_iter + # Maximum value of residual at which direct solve can be terminated + self.tol = tol + # Boolean flag whether to plot the solution at the end of simulation + self.plot = plot + # Emissivity parameters + self.alpha = alpha + self.gamma = gamma + self.delta = delta + + def getEmiss(self, T): + """Funciton returns the emmisivity""" + # Function to ascertain the local radiative emissivity, given the temperature + # return 1e-4 * (1. + 5.*np.sin(3*np.pi*T/200.) + np.exp(0.02*T)) + return 1e-4 * ( + self.gamma + self.delta * np.sin(self.alpha * T) + np.exp(0.02 * T) + ) + + def GaussSeidelUpdate(self, T): + """Gauss Seidel Update""" + # Evaluate the residual + res = np.zeros_like(T, dtype=T.dtype) + T_copy = T.copy() + emiss = self.getEmiss(T) + T[1:-1:2] = 0.5 * (T[0:-2:2] + T[2::2]) + 0.5 * self.dy2 * ( + emiss[1:-1:2] * (self.T_inf**4 - T[1:-1:2] ** 4) + + 0.5 * (self.T_inf - T[1:-1:2]) + ) + + T[2:-1:2] = 0.5 * (T[1:-2:2] + T[3::2]) + 0.5 * self.dy2 * ( + emiss[2:-1:2] * (self.T_inf**4 - T[2:-1:2] ** 4) + + 0.5 * (self.T_inf - T[2:-1:2]) + ) + + return np.linalg.norm(T - T_copy) + + def solve(self): + """Implicit solve""" + for iteration in range(self.n_iter): + + # Update the states for this iteration + res_norm = self.GaussSeidelUpdate(self.T) + # print("%9d\t%E" % (iteration, res_norm)) + + if res_norm < self.tol: + # call("mkdir -p true_solutions", shell=True) + # np.save("true_solutions/solution_%d" % self.T_inf, self.T) + break + + return self.T + + +def compute_prediction(T_inf, alpha=3 * np.pi / 200, gamma=1, delta=5): + """Function computes the model prediciton at a given temperature value""" + rht = RHT_True( + T_inf=T_inf, n_iter=100000, tol=1e-13, alpha=alpha, gamma=gamma, delta=delta + ) + T_prediciton = rht.solve() + return T_prediciton + + +def main(): + alpha = 3 * np.pi / 200 + gamma = 0.02 + delta = 5 + T_inf = 50 + + rht = RHT_True( + T_inf=T_inf, n_iter=100000, tol=1e-13, alpha=alpha, gamma=gamma, delta=delta + ) + rht.solve() + + +if __name__ == "__main__": + main() diff --git a/examples/radiative_heat_transfer/learning_model/run.py b/examples/radiative_heat_transfer/learning_model/run.py new file mode 100644 index 0000000..8b9dc4f --- /dev/null +++ b/examples/radiative_heat_transfer/learning_model/run.py @@ -0,0 +1,283 @@ +# Author: Sahil Bhola +# Date: 6/6/2022 +# Description: Identifiability ananlysis of the radiative heat transfer model +# True dynamics are considered and the parameters of the emmissivity are identified +from scipy.optimize import minimize +import matplotlib.pyplot as plt +import matplotlib +import numpy as np +import sys +import os +from mpi4py import MPI +from matplotlib.ticker import MultipleLocator, AutoMinorLocator + +sys.path.append("../forward_model") +sys.path.append("../../../information_metrics") + +from rht_true import compute_prediction +from compute_identifiability_radiative_heat_transfer import ( + conditional_mutual_information, +) + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=20) +matplotlib.rcParams["font.family"] = "serif" + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class inference: + def __init__( + self, + xtrain, + model_noise_cov_scalar, + true_theta, + objective_scaling, + prior_mean, + prior_cov, + num_outer_samples, + num_inner_samples, + loaded_ytrain=None, + restart=False, + ): + self.xtrain = xtrain + self.model_noise_cov_scalar = model_noise_cov_scalar + self.num_data_samples = self.xtrain.shape[0] + self.true_theta = true_theta + self.prior_mean = prior_mean + self.prior_cov = prior_cov + self.num_parameters = self.true_theta.shape[0] + self.objective_scaling = objective_scaling + self.num_outer_samples = num_outer_samples + self.num_inner_samples = num_inner_samples + self.restart = restart + + if loaded_ytrain is None: + self.ytrain = self.compute_model_prediction(theta=self.true_theta) + self.spatial_res = self.ytrain.shape[1] + + noise = np.sqrt(self.model_noise_cov_scalar) * np.random.randn( + self.num_data_samples * (self.spatial_res - 2) + ).reshape(self.num_data_samples, -1) + self.ytrain[:, 1:-1] += noise + if rank == 0: + np.save("ytrain.npy", self.ytrain) + + else: + self.ytrain = loaded_ytrain + self.spatial_res = self.ytrain.shape[1] + + self.model_noise_cov_mat = self.model_noise_cov_scalar * np.eye( + self.spatial_res + ) + self.spatial_field = np.linspace(0, 1, self.spatial_res) + + # Initialize the log file + if rank == 0: + self.log_file = open("log_file.dat", "w") + else: + self.log_file = None + + def compute_model_prediction(self, theta): + """Function computes the model prediciton""" + + alpha, gamma, delta = self.extract_prameters(theta) + + T_prediction = [] + for isample in range(self.num_data_samples): + T_inf = self.xtrain[isample] + T_prediction.append( + compute_prediction(alpha=alpha, gamma=gamma, delta=delta, T_inf=T_inf) + ) + + T_prediction = np.array(T_prediction) + + return T_prediction + + def compute_log_likelihood(self, theta): + """Function comptues the log likelihood (unnormalized)""" + prediction = self.compute_model_prediction(theta) + error = self.ytrain - prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + if self.model_noise_cov_scalar != 0: + log_likelihood = -0.5 * np.sum( + error_norm_sq / self.model_noise_cov_scalar, axis=0 + ) + else: + log_likelihood = -0.5 * np.sum(error_norm_sq, axis=0) + scaled_log_likelihood = self.objective_scaling * log_likelihood + + self.write_log_file( + "Scaled Log likelihood : {0:.5e} at theta : {1}".format( + scaled_log_likelihood, theta + ) + ) + + return scaled_log_likelihood + + def compute_mle(self): + """Function computes the mle""" + + def objective_func(theta): + return -self.compute_log_likelihood(theta) + + res = minimize( + objective_func, + np.random.randn(self.num_parameters), + method="Nelder-Mead", + options={"maxiter": 500}, + ) + res = minimize(objective_func, res.x) + self.write_log_file("MLE computation finished!!!") + self.write_log_file("Theta MLE : {} | Success : {}".format(res.x, res.success)) + comm.Barrier() + self.write_log_file("Converged at all procs.") + + return res.x + + def compute_emmisivity_distribution(self, theta): + """Function computes the emmissivity""" + alpha, gamma, delta = self.extract_prameters(theta) + + T = np.linspace(20, 100, 200) + + return 1e-4 * (gamma + delta * np.sin(alpha * T) + np.exp(0.02 * T)) + + def extract_prameters(self, theta): + """Function extracts the parameters""" + alpha = theta[0] + gamma = theta[1] + delta = theta[2] + + # alpha = 3*np.pi/200 + # gamma = 1 + # delta = 5 + + return alpha, gamma, delta + + def write_log_file(self, message): + """Function writes the log file""" + if rank == 0: + self.log_file.write(message + "\n") + self.log_file.flush() + else: + pass + + def estimate_parameter_conditional_mutual_information(self): + """Function computes the individual mutual information, I(theta_i;Y|theta_[rest of parameters])""" + estimator = conditional_mutual_information( + forward_model=self.compute_model_prediction, + prior_mean=self.prior_mean, + prior_cov=self.prior_cov, + model_noise_cov_scalar=self.model_noise_cov_scalar, + global_num_outer_samples=self.num_outer_samples, + global_num_inner_samples=self.num_inner_samples, + ytrain=self.ytrain, + save_path=os.getcwd(), + log_file=self.log_file, + restart=self.restart, + ) + + estimator.compute_individual_parameter_data_mutual_information_via_mc( + use_quadrature=True, single_integral_gaussian_quad_pts=60 + ) + estimator.compute_posterior_pair_parameter_mutual_information( + use_quadrature=True, + single_integral_gaussian_quad_pts=60, + double_integral_gaussian_quad_pts=60, + ) + + def update_prior(self, theta_mle): + """Function updates the prior""" + if rank == 0: + update_prior = [theta_mle.reshape(-1, 1)] * size + else: + update_prior = None + + comm.Barrier() + self.prior_mean = comm.scatter(update_prior, root=0) + + +def main(): + alpha_true = 3 * np.pi / 200 + gamma_true = 1 + delta_true = 5 + # loaded_ytrain = None + # theta_mle = None + loaded_ytrain = np.load("ytrain.npy") + theta_mle = np.load("theta_mle.npy") + restart = False + + true_theta = np.array([alpha_true, gamma_true, delta_true]) + # true_theta = np.array([alpha_true, gamma_true]) + + # Initialize the prior + prior_mean = true_theta.copy().reshape(-1, 1) + prior_cov = np.eye(prior_mean.shape[0]) + + xtrain = np.array([50]) + model_noise_cov_scalar = 1e-1 + objective_scaling = 1e-10 + num_outer_samples = 500 + num_inner_samples = 20 + + model = inference( + xtrain=xtrain, + model_noise_cov_scalar=model_noise_cov_scalar, + true_theta=true_theta, + objective_scaling=objective_scaling, + prior_mean=prior_mean, + prior_cov=prior_cov, + num_outer_samples=num_outer_samples, + num_inner_samples=num_inner_samples, + loaded_ytrain=loaded_ytrain, + restart=restart, + ) + + # MLE + if theta_mle is None: + theta_mle = model.compute_mle() + + if rank == 0: + np.save("theta_mle.npy", theta_mle) + # Update the prior + model.update_prior(theta_mle) + + # Model identifiability + # model.estimate_parameter_conditional_mutual_information() + + # if rank == 0: + # # Emmissivity + # true_emmisivity = model.compute_emmisivity_distribution(theta=true_theta) + # prediction_emmisivity = model.compute_emmisivity_distribution(theta=theta_mle) + # # Temperature distribution + # true_temperature = model.compute_model_prediction(theta=true_theta) + # prediction_temperature = model.compute_model_prediction(theta=theta_mle) + # fig, axs = plt.subplots(1, 2, figsize=(10, 5)) + # axs[0].scatter(model.spatial_field, model.ytrain.ravel(), c='k', s=30, label="Data") + # axs[0].plot(model.spatial_field, prediction_temperature.ravel(), lw=3, color='red', label="Prediction") + # axs[0].legend() + # axs[0].grid(axis="both") + # axs[0].set_xlabel("z") + # axs[0].set_ylabel("T(z)") + # axs[1].plot(np.linspace(20, 100, 200), np.abs(true_emmisivity - prediction_emmisivity), color="k", lw=3) + # axs[1].set_xlim(left=20, right=100) + # axs[1].grid(axis="both") + # axs[1].set_yscale("log") + # axs[1].set_xlabel("T") + # axs[1].set_ylabel(r"$|\epsilon_{true}-\epsilon_{prediction}|$") + # axs[1].xaxis.set_minor_locator(MultipleLocator(5)) + # fig.suptitle(r"$T_{{\infty}} = {}$".format(50)) + # plt.tight_layout() + # plt.savefig("prediction.png") + # plt.show() + + # Close the log file + if rank == 0: + model.log_file.close() + + +if __name__ == "__main__": + main() diff --git a/information_metrics/SobolIndex.py b/information_metrics/SobolIndex.py new file mode 100644 index 0000000..aa98466 --- /dev/null +++ b/information_metrics/SobolIndex.py @@ -0,0 +1,518 @@ +import numpy as np +from mpi4py import MPI + +import os +import shutil + +COMM = MPI.COMM_WORLD +RANK = COMM.Get_rank() +SIZE = COMM.Get_size() + + +class SobolIndex: + def __init__( + self, + forward_model, + prior_mean, + prior_cov, + global_num_outer_samples, + global_num_inner_samples, + model_noise_cov_scalar, + data_shape, + save_path, + write_log_file=False, + ): + + self._forward_model = forward_model + self._prior_mean = prior_mean + self._prior_cov = prior_cov + self._global_num_outer_samples = global_num_outer_samples + self._global_num_inner_samples = global_num_inner_samples + self._model_noise_cov_scalar = model_noise_cov_scalar + self._data_shape = data_shape + self._save_path = save_path + + self._num_parameters = self._prior_mean.shape[0] + + # Compute worker specific properties + self._worker_num_outer_samples = self._comp_local_num_outer_samples() + self._worker_num_inner_samples = self._global_num_inner_samples + + assert ( + self._forward_model(self._prior_mean).shape == self._data_shape + ), "Data shape does not match" + + # File I/O + self._create_save_path() + self._log_file = self._create_log_file(write_log_file) + + self._write_message_to_log_file( + "Num Outer Samples(Global): {}".format(self._global_num_outer_samples) + ) + + self._write_message_to_log_file( + "Num Inner Samples(Global): {}".format(self._global_num_inner_samples) + ) + + self._write_message_to_log_file("Num Procs: {}".format(SIZE)) + + self._write_message_to_log_file( + "Num Outer Samples(Worker): {}".format(self._worker_num_outer_samples) + ) + self._write_message_to_log_file( + "Num Inner Samples(Worker): {}".format(self._worker_num_inner_samples) + ) + + self._write_message_to_log_file( + "Number of parameters: {}".format(self._num_parameters) + ) + + self._write_message_to_log_file( + "Model Noise Cov (scalar): {}".format(self._model_noise_cov_scalar) + ) + + # Compute the Sobol Denominator + self._write_message_to_log_file("Computing Sobol Denominator") + self._sobol_denominator = self._comp_sobol_denominator_via_samples() + + # Save the sobol denominator + self._save_data(self._sobol_denominator, "sobol_denominator.npy") + + def _create_save_path(self): + """Creates the save path""" + if RANK == 0: + if not os.path.exists(self._save_path): + os.makedirs(self._save_path) + else: + shutil.rmtree(self._save_path) + os.makedirs(self._save_path) + else: + pass + + def _create_log_file(self, write_log_file): + """Creates the log file + Returns: + Log file + """ + if write_log_file: + if RANK == 0: + log_file = open(self._save_path + "/log_SobolIndex.txt", "w") + log_file.write("Log file (Sobol Index)\n") + log_file.flush() + else: + log_file = None + else: + log_file = None + + return log_file + + def _comp_local_num_outer_samples(self): + """Function computes the number of local number of samples + Returns: + (int): Number of worker specific outer samples + """ + assert ( + self._global_num_outer_samples % SIZE == 0 + ), "Equally divide the outer expectation samples" + return int(self._global_num_outer_samples / SIZE) + + def _comp_model_prediction(self, theta): + """Compute the model prediciton + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + theta (float[num_parameters, num_samples]): Parameter sample + + Returns: + (float): Model prediciton + + """ + assert ( + theta.shape[0] == self._num_parameters + ), "Number of parameters does not match" + num_samples = theta.shape[1] + model_prediction = np.zeros(self._data_shape + (num_samples,)) + + for isample in range(num_samples): + model_prediction[:, :, isample] = self._forward_model(theta[:, isample]) + + return model_prediction + + def _sample_model_likelihood(self, theta): + """Function computes the model likelihood + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + theta (float[num_parameters, num_samples]): Parameter sample + + Returns: + (float[num_samples, 1]): Model likelihood samples + + """ + assert theta.shape[0] == self._num_parameters, "Incorrect number of parameters" + + num_samples = theta.shape[1] + noise_sample = np.sqrt(self._model_noise_cov_scalar) * np.random.randn( + self._data_shape[0], self._data_shape[1], num_samples + ) + model_prediction = self._comp_model_prediction(theta) + likelihood_sample = model_prediction + noise_sample + + return likelihood_sample + + def _sample_gaussian(self, mean, cov, num_samples): + """Sample from a Gaussian distribution + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + mean (float[num_parameters, 1]): Mean of the Gaussian distribution + cov (float[num_parameters, num_parameters]): Covariance of the Gaussian + distribution + num_samples (int): Number of samples + + Returns: + (float[num_parameters, num_samples]): Samples from the Gaussian distribution + + """ + assert mean.shape[0] == cov.shape[0], "Number of parameters does not match" + assert mean.shape[1] == 1, "Input mean should be a column vector" + assert num_samples > 0, "Number of samples must be greater than zero" + + return np.random.multivariate_normal(mean.ravel(), cov, num_samples).T + + def _comp_selected_parameter_samples(self, parameter_id, num_samples): + """Computes the selected parameter samples + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + parameter_id (int): Parameter id for which samples are computed + num_samples (int): Number of samples + + Returns: + (float): Samples of the selected parameter + """ + mean = self._prior_mean[parameter_id, 0].reshape(-1, 1) + cov = self._prior_cov[parameter_id, :][:, parameter_id] + return self._sample_gaussian(mean, cov, num_samples) + + def _comp_sobol_denominator_via_samples(self): + """Computes the Sobol denominator via Samples + Note: + Do not include the `self` parameter in the ``Args`` section. + + Returns: + (float): Sobol denominator + """ + # Compute outer samples + outer_parameter_samples = self._comp_selected_parameter_samples( + parameter_id=np.arange(self._num_parameters), + num_samples=self._worker_num_outer_samples, + ) + + # Compute model prediction + model_likelihood = self._sample_model_likelihood(outer_parameter_samples) + + # Compute Sobol denominator + unnormalized_mean_model_prediction = self._worker_num_outer_samples * np.mean( + model_likelihood, axis=2 + ) + + unnormalized_mean_model_prediction_list = COMM.gather( + unnormalized_mean_model_prediction, root=0 + ) + + if RANK == 0: + global_mean_model_prediction = ( + 1 / self._global_num_outer_samples + ) * np.sum(unnormalized_mean_model_prediction_list, axis=0) + global_mean_model_prediction_list = [global_mean_model_prediction] * SIZE + else: + global_mean_model_prediction_list = None + + COMM.Barrier() + local_mean_model_prediction = COMM.scatter( + global_mean_model_prediction_list, root=0 + ) + + error = model_likelihood - local_mean_model_prediction[:, :, np.newaxis] + unnormalized_mean_squared_error = self._worker_num_outer_samples * np.mean( + error**2, axis=2 + ) + unnormalized_mean_squation_error_list = COMM.gather( + unnormalized_mean_squared_error, root=0 + ) + + if RANK == 0: + global_mean_squared_error = (1 / self._global_num_outer_samples) * np.sum( + unnormalized_mean_squation_error_list, axis=0 + ) + global_mean_squared_error_list = [global_mean_squared_error] * SIZE + else: + global_mean_squared_error_list = None + + sobol_denominator = COMM.scatter(global_mean_squared_error_list, root=0) + + assert ( + sobol_denominator.shape == self._data_shape + ), "Sobol denominator shape does not match" + assert (sobol_denominator > 0).all(), "Sobol denominator should be positive" + assert (sobol_denominator < np.inf).all(), "Sobol denominator should be finite" + + return sobol_denominator + + def _comp_inner_expectation( + self, selected_parameter_id, outer_selected_parameter_samples + ): + """Computes the inner expectation + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + selected_parameter_id (int): Parameter id for outer samples + outer_selected_parameter_samples (float): Samples of the selected parameter + + Returns: + (float): Inner expectation + """ + inner_expectation = np.zeros( + self._data_shape + (self._worker_num_outer_samples,) + ) + unselected_parameter_id = np.delete( + np.arange(self._num_parameters), selected_parameter_id + ) + + # Compute inner samples + inner_unselected_parameter_samples = self._comp_selected_parameter_samples( + parameter_id=unselected_parameter_id, + num_samples=self._worker_num_inner_samples, + ) + + for iouter in range(self._worker_num_outer_samples): + + if iouter % 100 == 0 or iouter == self._worker_num_outer_samples - 1: + self._write_message_to_log_file( + " > Computing inner expectation for outer sample {}/{}".format( + iouter + 1, self._worker_num_outer_samples + ) + ) + + # Compute model prediction + theta = np.zeros((self._num_parameters, self._worker_num_inner_samples)) + theta[selected_parameter_id, :] = outer_selected_parameter_samples[ + :, iouter + ].reshape(-1, 1) + theta[unselected_parameter_id, :] = inner_unselected_parameter_samples + model_likelihood = self._sample_model_likelihood(theta) + + # Compute inner expectation + inner_expectation[:, :, iouter] = np.mean(model_likelihood, axis=2) + + return inner_expectation + + def _write_message_to_log_file(self, message, write_rank=0): + """Writes a message to the log file + Args: + message (str): Message to be written + """ + if RANK == write_rank: + if self._log_file is None: + print(message + "\n", flush=True) + else: + self._log_file.write(message + "\n") + self._log_file.flush() + + def _save_data(self, data, filename, save_all_rank=False): + """Saves data to a file + Args: + data (float): Data to be saved + filename (str): Filename + """ + # File path + file_path = os.path.join(self._save_path, filename) + + if save_all_rank is False: + if RANK == 0: + np.save(file_path, data) + else: + np.save(file_path, data) + + def _comp_variance_of_inner_expectation(self, inner_expectation): + """Function to compute the variance of the inner Expectation + Args: + inner_expectation (float): Inner Expectation samples + + Returns: + (float): Variance of the inner Expectation + """ + unnormalized_mean_inner_expectation = self._worker_num_outer_samples * np.mean( + inner_expectation, axis=2 + ) + + unnormalized_mean_inner_expectation_list = COMM.gather( + unnormalized_mean_inner_expectation, root=0 + ) + + if RANK == 0: + global_mean_inner_expectation = ( + 1 / self._global_num_outer_samples + ) * np.sum(unnormalized_mean_inner_expectation_list, axis=0) + global_mean_inner_expectation_list = [global_mean_inner_expectation] * SIZE + else: + global_mean_inner_expectation_list = None + + COMM.Barrier() + local_mean_inner_expectation = COMM.scatter( + global_mean_inner_expectation_list, root=0 + ) + + error = inner_expectation - local_mean_inner_expectation[:, :, np.newaxis] + unnormalized_mean_squared_error = self._worker_num_outer_samples * np.mean( + error**2, axis=2 + ) + unnormalized_mean_squation_error_list = COMM.gather( + unnormalized_mean_squared_error, root=0 + ) + + if RANK == 0: + global_mean_squared_error = (1 / self._global_num_outer_samples) * np.sum( + unnormalized_mean_squation_error_list, axis=0 + ) + global_mean_squared_error_list = [global_mean_squared_error] * SIZE + else: + global_mean_squared_error_list = None + + COMM.Barrier() + + variance_of_inner_expectation = COMM.scatter( + global_mean_squared_error_list, root=0 + ) + + return variance_of_inner_expectation + + def comp_first_order_sobol_indices(self): + """Compute the First order Sobol index + + Notes: Higher the value of the Sobol index, higher the sensitivity of the output + with respect to the parameter. + + Note: + Do not include the `self` parameter in the ``Args`` section. + """ + sobol_numerator = np.zeros(self._data_shape + (self._num_parameters,)) + + # Compute the Sobol Numerator + for iparam in range(self._num_parameters): + + self._write_message_to_log_file( + message="Computing First order Sobol num. for parameter {}/{}".format( + iparam + 1, self._num_parameters + ) + ) + + selected_parameter_id = np.array([iparam]) + + # Compute outer samples + outer_selected_parameter_samples = self._comp_selected_parameter_samples( + parameter_id=selected_parameter_id, + num_samples=self._worker_num_outer_samples, + ) + # Compute inner expectation for each outer selected parameter sample + inner_expectation = self._comp_inner_expectation( + selected_parameter_id=selected_parameter_id, + outer_selected_parameter_samples=outer_selected_parameter_samples, + ) + + # Save the inner expectation + self._save_data( + inner_expectation, + "first_order_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, RANK + ), + save_all_rank=True, + ) + + # Compute Variance of Inner Expectation + sobol_numerator[:, :, iparam] = self._comp_variance_of_inner_expectation( + inner_expectation + ) + + # Compute the Sobol Index + ratio = sobol_numerator / self._sobol_denominator[:, :, np.newaxis] + sobol_index = np.mean(ratio, axis=(0, 1)) + + self._write_message_to_log_file(message="Sobol index computation completed!") + self._write_message_to_log_file(message="Sobol index: {}".format(sobol_index)) + + # Save the data + self._save_data(sobol_index, filename="sobol_index.npy") + + def comp_total_effect_sobol_indices(self): + """Compute the Total effect Sobol index + + Notes: Higher the value of the total effect Sobol index, that means that fixing + that parameter leads to `less` variability in the output. This means + that that parameter is `more` important. + + Note: + Do not include the `self` parameter in the ``Args`` section. + """ + sobol_numerator = np.zeros(self._data_shape + (self._num_parameters,)) + + # Compute the Sobol Numerator + for iparam in range(self._num_parameters): + + self._write_message_to_log_file( + message="Computing Total Sobol numerator for parameter {}/{}".format( + iparam + 1, self._num_parameters + ) + ) + + selected_parameter_id = np.delete(np.arange(self._num_parameters), iparam) + + # Compute outer samples + outer_selected_parameter_samples = self._comp_selected_parameter_samples( + parameter_id=selected_parameter_id, + num_samples=self._worker_num_outer_samples, + ) + + # Compute inner expectation for each outer selected parameter sample + inner_expectation = self._comp_inner_expectation( + selected_parameter_id=selected_parameter_id, + outer_selected_parameter_samples=outer_selected_parameter_samples, + ) + + # Save the inner expectation + self._save_data( + inner_expectation, + "total_effect_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, RANK + ), + save_all_rank=True, + ) + + # Compute Variance of Inner Expectation + sobol_numerator[:, :, iparam] = self._comp_variance_of_inner_expectation( + inner_expectation + ) + + # Compute the Total Sobol Index + ratio = sobol_numerator / self._sobol_denominator[:, :, np.newaxis] + sobol_index = 1 - np.mean(ratio, axis=(0, 1)) + + self._write_message_to_log_file( + message="Total Sobol index computation completed!" + ) + self._write_message_to_log_file( + message="Total Sobol index: {}".format(sobol_index) + ) + + # Save the data + self._save_data(sobol_index, filename="total_sobol_index.npy") diff --git a/information_metrics/compute_identifiability.py b/information_metrics/compute_identifiability.py new file mode 100644 index 0000000..15f262d --- /dev/null +++ b/information_metrics/compute_identifiability.py @@ -0,0 +1,1541 @@ +# Author: Sahil Bhola (University of Michigan) +# Date: 14 June 2022 +# Description : Computes the model identifiability using Mutual information + +import numpy as np +from mpi4py import MPI +import sys +from itertools import combinations +import os +import shutil + +sys.path.append("/home/sbhola/Documents/CASLAB/GIM/quadrature") +from quadrature import unscented_quadrature, gauss_hermite_quadrature + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class mutual_information: + def __init__( + self, + forward_model, + prior_mean, + prior_cov, + model_noise_cov_scalar, + global_num_outer_samples, + global_num_inner_samples, + save_path, + restart, + ytrain=None, + log_file=None, + mutual_information_evidence_save_restart_freq=50, + conditional_mutual_information_save_restart_freq=50, + write_proc_log_file=True, + ): + self.forward_model = forward_model + self.prior_mean = prior_mean + self.prior_cov = prior_cov + self.model_noise_cov_scalar = model_noise_cov_scalar + self.global_num_outer_samples = global_num_outer_samples + self.global_num_inner_samples = global_num_inner_samples + self.log_file = log_file + self.ytrain = ytrain + self.save_path = save_path + self.mutual_information_evidence_save_restart_freq = ( + mutual_information_evidence_save_restart_freq + ) + self.conditional_mutual_information_save_restart_freq = ( + conditional_mutual_information_save_restart_freq + ) + self.restart = restart + + self.num_parameters = self.prior_mean.shape[0] + + self.local_num_inner_samples = self.global_num_inner_samples + self.local_num_outer_samples = self.compute_local_num_outer_samples() + + self.local_outer_prior_samples = np.zeros( + (self.num_parameters, self.local_num_outer_samples) + ) + + self.local_outer_model_prediction = np.zeros( + self.ytrain.shape + (self.local_num_outer_samples,) + ) + + self.local_outer_data_samples = np.zeros( + self.local_outer_model_prediction.shape + ) + + self.local_outer_likelihood_prob = np.zeros(self.local_num_outer_samples) + + self.outer_data_computation_req_flag = True + + self.num_data_samples = self.ytrain.shape[0] + self.spatial_res = self.ytrain.shape[1] + + self.restart_file_path = os.path.join(self.save_path, "restart_files") + if self.restart is False: + if rank == 0: + self.create_restart_folders() + self.remove_file("estimated_mutual_information.npy") + self.remove_file("estimated_individual_mutual_information.npy") + self.remove_file("estimated_pair_mutual_information.npy") + comm.Barrier() + + if write_proc_log_file: + proc_log_file_path = os.path.join( + self.save_path, "proc_{}_log_file.dat".format(rank) + ) + self.proc_log_file = open(proc_log_file_path, "w") + + def compute_local_num_outer_samples(self): + """Function computes the number of local number of samples""" + assert ( + self.global_num_outer_samples % size == 0 + ), "Equally divide the outer expectation samples" + return int(self.global_num_outer_samples / size) + + def sample_gaussian(self, mean, cov, num_samples): + """Function samples the gaussian""" + # Definitions + d, N = mean.shape + product = d * N * num_samples + + if N > 1: + assert np.prod(cov.shape) == 1, "Must provide scalar cov" + L = np.linalg.cholesky(cov + np.eye(1) * 1e-8) + noise = L @ np.random.randn(product).reshape(d, N, num_samples) + return np.expand_dims(mean, axis=-1) + noise + else: + L = np.linalg.cholesky(cov + np.eye(d) * 1e-8) + noise = L @ np.random.randn(product).reshape(d, num_samples) + return mean + noise + + def sample_parameter_distribution(self, num_samples, parameter_pair=None): + """Function samples the parameter distribution""" + if parameter_pair is None: + # parameter_samples = self.sample_gaussian( + # mean=self.prior_mean, + # cov=self.prior_cov, + # num_samples=num_samples + # ) + raise ValueError("Must provide parameter pair") + else: + ( + selected_parameter_mean, + selected_parameter_cov, + ) = self.get_selected_parameter_stats(parameter_pair=parameter_pair) + parameter_samples = self.sample_gaussian( + mean=selected_parameter_mean, + cov=selected_parameter_cov, + num_samples=num_samples, + ) + + return parameter_samples + + def sample_prior_distribution(self, num_samples): + """Function samples the prior distribution""" + prior_samples = self.sample_gaussian( + mean=self.prior_mean, cov=self.prior_cov, num_samples=num_samples + ) + return prior_samples + + def get_selected_parameter_stats(self, parameter_pair): + """Function selectes the parameter pair + Assumptions: parameters are assumed to be uncorrelated + (prior to observing the data)""" + mean = self.prior_mean[parameter_pair, :].reshape(parameter_pair.shape[0], 1) + cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + return mean, cov + + def compute_model_prediction(self, theta): + """Function computes the model prediction""" + num_samples = theta.shape[1] + prediction = np.zeros(self.ytrain.shape + (num_samples,)) + for isample in range(num_samples): + self.write_proc_log_file( + " (Model, {}/{}) theta: [{}, {}, {}]".format( + isample, + theta.shape[1] - 1, + theta[0, isample], + theta[1, isample], + theta[2, isample], + ) + ) + prediction[:, :, isample] = self.forward_model( + theta[:, isample], proc_log_file=self.proc_log_file + ) + return prediction + + def sample_likelihood(self, theta): + """Function samples the likelihood""" + model_prediction = self.compute_model_prediction(theta=theta) + product = np.prod(model_prediction.shape) + noise = np.sqrt(self.model_noise_cov_scalar) * np.random.randn(product).reshape( + model_prediction.shape + ) + likelihood_sample = model_prediction + noise + return likelihood_sample, model_prediction + + def evaluate_likelihood_probaility(self, data, model_prediction): + """Function evaluates the likelihood probability""" + error = data - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + likelihood = ((pre_exp) ** (self.num_data_samples)) * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + + return likelihood + + def estimate_mutual_information_via_mc( + self, use_quadrature=False, quadrature_rule="gaussian", num_gaussian_quad_pts=50 + ): + """Function computes the mutual information via mc""" + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the mutual information, I(theta;Y) <<<" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + # Compute evidence + local_evidence_prob = self.estimate_evidence_probability( + use_quadrature=use_quadrature, + quadrature_rule=quadrature_rule, + num_gaussian_quad_pts=num_gaussian_quad_pts, + ) + local_log_evidence = np.log(local_evidence_prob) + + comm.Barrier() + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_evidence = comm.gather(local_log_evidence, root=0) + + if rank == 0: + summation = sum( + [ + np.sum(global_log_likelihood[ii] - global_log_evidence[ii]) + for ii in range(size) + ] + ) + mutual_information = (1 / self.global_num_outer_samples) * summation + + self.save_quantity("estimated_mutual_information.npy", mutual_information) + self.write_log_file( + ">>> End computing the mutual information, I(theta;Y) <<<" + ) + + def estimate_evidence_probability( + self, num_gaussian_quad_pts, use_quadrature=False, quadrature_rule="gaussian" + ): + """Function estimates the evidence probability""" + if use_quadrature is True: + evidence_prob = self.integrate_likelihood_via_quadrature( + quadrature_rule=quadrature_rule, + num_gaussian_quad_pts=num_gaussian_quad_pts, + ) + else: + evidence_prob = self.integrate_likelihood_via_mc() + + return evidence_prob + + def integrate_likelihood_via_mc(self): + """Function integrates the likelihood to estimate the evidence""" + # Definitions + evidence_prob, is_evidence_prob_avail = self.load_restart_data( + data_type="inner", label="evidence_prob", sub_type="MI" + ) + + ( + local_mutual_information_inner_counter, + is_mi_inner_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="mutual_infomation_inner_counter", sub_type="MI" + ) + + if is_evidence_prob_avail is False: + evidence_prob = np.nan * np.ones(self.local_num_outer_samples) + if is_mi_inner_counter_avail is False: + local_mutual_information_inner_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_mutual_information_inner_counter.item() + + # Pre computations + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + # Compute inner prior samples + inner_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_inner_samples + ) + + # Compute model prediction + inner_model_prediction = self.compute_model_prediction( + theta=inner_prior_samples + ) + + # Comptute the evidence + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + error = outer_sample - inner_model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + evidence_prob[isample] = (1 / self.local_num_inner_samples) * np.sum( + sample_evidence_estimates + ) + + local_mutual_information_inner_counter += 1 + + save_condition = ( + isample % self.mutual_information_evidence_save_restart_freq + ) + final_save_condition = ( + self.local_num_outer_samples + % self.mutual_information_evidence_save_restart_freq + ) + + if save_condition or final_save_condition == 0: + self.save_restart_data( + data=local_mutual_information_inner_counter, + data_type="inner", + label="mutual_infomation_inner_counter", + sub_type="MI", + ) + + self.save_restart_data( + data=evidence_prob, + data_type="inner", + label="evidence_prob", + sub_type="MI", + ) + + return evidence_prob + + def integrate_likelihood_via_quadrature( + self, quadrature_rule, num_gaussian_quad_pts + ): + """Function integrates the likelihood to estimate the evidence + using quadratures""" + # Definitions + evidence_prob, is_evidence_prob_avail = self.load_restart_data( + data_type="inner", label="evidence_prob", sub_type="MI" + ) + + ( + local_mutual_information_inner_counter, + is_mi_inner_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="mutual_infomation_inner_counter", sub_type="MI" + ) + + if is_evidence_prob_avail is False: + evidence_prob = np.nan * np.ones(self.local_num_outer_samples) + if is_mi_inner_counter_avail is False: + local_mutual_information_inner_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_mutual_information_inner_counter.item() + + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + # Extract outer sample + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + + # Integrand + def integrand(eval_theta): + integrand_val = self.likelihood_integrand( + theta=eval_theta, outer_sample=outer_sample + ) + return integrand_val + + if quadrature_rule == "unscented": + unscented_quad = unscented_quadrature( + mean=self.prior_mean, cov=self.prior_cov, integrand=integrand + ) + + evidence_mean, evidence_cov = unscented_quad.compute_integeral() + + evidence_prob[isample] = evidence_mean + np.sqrt( + evidence_cov + ) * np.random.randn(1) + + elif quadrature_rule == "gaussian": + gh = gauss_hermite_quadrature( + mean=self.prior_mean, + cov=self.prior_cov, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + evidence_prob[isample] = gh.compute_integeral() + + else: + raise ValueError("Invalid quadrature rule") + + local_mutual_information_inner_counter += 1 + + save_condition = ( + isample % self.mutual_information_evidence_save_restart_freq + ) + final_save_condition = ( + self.local_num_outer_samples + % self.mutual_information_evidence_save_restart_freq + ) + + if save_condition or final_save_condition == 0: + self.save_restart_data( + data=local_mutual_information_inner_counter, + data_type="inner", + label="mutual_infomation_inner_counter", + sub_type="MI", + ) + + self.save_restart_data( + data=evidence_prob, + data_type="inner", + label="evidence_prob", + sub_type="MI", + ) + return evidence_prob + + def likelihood_integrand(self, theta, outer_sample): + """Function returns the integrand evaluated at the quadrature points + NOTE: shape of the model prediction should be : + (num_data_samples, spatial_res, num_samples)""" + model_prediction = self.compute_model_prediction(theta=theta) + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + error = outer_sample - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + return sample_evidence_estimates.reshape(1, -1) + + def display_messsage(self, message, print_rank="root"): + """Print function""" + if print_rank == "root": + if rank == 0: + print(message) + elif print_rank == "all": + print(message + " at rank : {}".format(rank)) + elif rank == print_rank: + print(message + " at rank : {}".format(rank)) + + def save_quantity(self, file_name, data): + """Function saves the data""" + if rank == 0: + np.save(os.path.join(self.save_path, file_name), data) + else: + pass + + def write_log_file(self, message): + """Function writes the message on the log file""" + if rank == 0: + assert self.log_file is not None, "log file must be provided" + self.log_file.write(message + "\n") + self.log_file.flush() + + def write_proc_log_file(self, message): + """Function writes the message on the log file""" + self.proc_log_file.write(message + "\n") + self.proc_log_file.flush() + + def save_restart_data(self, data, data_type, label, sub_type=None): + """Function saves the data""" + if data_type == "outer": + folder = "outer_data" + file_path = os.path.join( + self.restart_file_path, folder, label + "_rank_{}.npy".format(rank) + ) + else: + folder = "inner_data" + + if sub_type == "MI": + sub_folder = "mutual_information" + elif sub_type == "ICMI": + sub_folder = "individual_conditional_mutual_information" + elif sub_type == "PCMI": + sub_folder = "pair_conditional_mutual_information" + else: + raise ValueError("Invalid subfolder type") + + file_path = os.path.join( + self.restart_file_path, + folder, + sub_folder, + label + "_rank_{}.npy".format(rank), + ) + + np.save(file_path, data) + + def load_restart_data(self, data_type, label, sub_type=None): + """Function loads the data""" + if data_type == "outer": + folder = "outer_data" + file_path = os.path.join( + self.restart_file_path, folder, label + "_rank_{}.npy".format(rank) + ) + else: + folder = "inner_data" + + if sub_type == "MI": + sub_folder = "mutual_information" + elif sub_type == "ICMI": + sub_folder = "individual_conditional_mutual_information" + elif sub_type == "PCMI": + sub_folder = "pair_conditional_mutual_information" + + file_path = os.path.join( + self.restart_file_path, + folder, + sub_folder, + label + "_rank_{}.npy".format(rank), + ) + + file_exists = os.path.exists(file_path) + if file_exists: + return np.load(file_path), file_exists + else: + return np.nan, file_exists + + def create_restart_folders(self): + """Function creates the restart folder""" + if os.path.exists(self.restart_file_path): + shutil.rmtree(self.restart_file_path) + os.mkdir(self.restart_file_path) + os.mkdir(os.path.join(self.restart_file_path, "outer_data")) + os.mkdir(os.path.join(self.restart_file_path, "inner_data")) + os.mkdir( + os.path.join(self.restart_file_path, "inner_data", "mutual_information") + ) + os.mkdir( + os.path.join( + self.restart_file_path, + "inner_data", + "individual_conditional_mutual_information", + ) + ) + os.mkdir( + os.path.join( + self.restart_file_path, + "inner_data", + "pair_conditional_mutual_information", + ) + ) + + def remove_file(self, file_name): + """Function deletes a file""" + if rank == 0: + file_path = os.path.join(self.save_path, file_name) + if os.path.exists(file_path): + os.remove(file_path) + else: + pass + + +class conditional_mutual_information(mutual_information): + def compute_individual_parameter_data_mutual_information_via_mc( + self, use_quadrature=False, single_integral_gaussian_quad_pts=60 + ): + """Function computes the mutual information between each parameter theta_i + and data Y given rest of the parameters""" + # Definitions + if os.path.exists("estimated_individual_mutual_information.npy"): + inidividual_mutual_information = np.load( + "estimated_individual_mutual_information.npy" + ) + else: + inidividual_mutual_information = np.nan * np.ones(self.num_parameters) + + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the individual parameter mutual information, " + "I(theta_i;Y|theta_[rest of parameters])" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + self.write_log_file( + "Use_quadrature : {} with single intergral quad pts: {}".format( + use_quadrature, single_integral_gaussian_quad_pts + ) + ) + + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + else: + self.write_log_file("Loaded outer prior samples") + self.write_log_file("----------------") + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + + self.write_log_file("Done computing outer data samples") + self.write_log_file("----------------") + else: + self.write_log_file("Loaded outer data samples") + self.write_log_file("----------------") + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + + else: + self.write_log_file("Loaded outer Likelihood prob") + self.write_log_file("----------------") + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + # Estimate p(y|theta_{-i}) + ( + individual_parameter_counter, + is_individual_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="individual_parameter_counter", sub_type="ICMI" + ) + if is_individual_parameter_counter_avail is False: + individual_parameter_counter = 0 + lower_parameter_loop_idx = 0 + else: + lower_parameter_loop_idx = individual_parameter_counter.item() + + for iparameter in range(lower_parameter_loop_idx, self.num_parameters): + self.write_log_file( + "Computing I(theta_{};Y| theta_[rest of parameters])".format(iparameter) + ) + + parameter_pair = np.array([iparameter]) + local_individual_likelihood = self.estimate_individual_likelihood( + parameter_pair=parameter_pair, + use_quadrature=use_quadrature, + num_gaussian_quad_pts=single_integral_gaussian_quad_pts, + case_type="individual", + ) + + local_log_individual_likelihood = np.log(local_individual_likelihood) + + comm.Barrier() + self.write_log_file("Done computing log individual likelihood") + + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_individual_likelihood = comm.gather( + local_log_individual_likelihood, root=0 + ) + + if rank == 0: + summation = sum( + [ + np.sum( + global_log_likelihood[ii] + - global_log_individual_likelihood[ii] + ) + for ii in range(size) + ] + ) + inidividual_mutual_information[iparameter] = ( + 1 / self.global_num_outer_samples + ) * summation + self.write_log_file("----------------") + + individual_parameter_counter += 1 + + self.save_restart_data( + data=individual_parameter_counter, + data_type="inner", + label="individual_parameter_counter", + sub_type="ICMI", + ) + + self.write_log_file(" MI : {}".format(inidividual_mutual_information)) + + self.save_quantity( + "estimated_individual_mutual_information.npy", + inidividual_mutual_information, + ) + + self.write_log_file( + " Normalized MI : {}".format( + inidividual_mutual_information + / np.sum(np.abs(inidividual_mutual_information)) + ) + ) + + self.write_log_file( + ">>> End computing the individual parameter mutual information," + "I(theta_i;Y)" + ) + + return inidividual_mutual_information + + def compute_posterior_pair_parameter_mutual_information( + self, + use_quadrature=False, + single_integral_gaussian_quad_pts=60, + double_integral_gaussian_quad_pts=30, + ): + """Function computes the posterior mutual information between parameters, + I(theta_i;theta_j|Y, theta_k)""" + # Definitions + parameter_combinations = np.array( + list(combinations(np.arange(self.num_parameters), 2)) + ) + + if os.path.exists("estimated_pair_mutual_information.npy"): + pair_mutual_information = np.load("estimated_pair_mutual_information.npy") + else: + pair_mutual_information = np.nan * np.ones(parameter_combinations.shape[0]) + + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the pair parameter mutual information," + "I(theta_i;theta_j|Y, theta_[rest of parameters])" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + + self.write_log_file( + "Use_quadrature : {} with single intergral quad pts: {} and double " + "integral quad pts : {}(x2)".format( + use_quadrature, + single_integral_gaussian_quad_pts, + double_integral_gaussian_quad_pts, + ) + ) + + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + else: + self.write_log_file("Done Loading outer Prior samples") + self.write_log_file("----------------") + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + self.write_log_file("Done computing outer data samples") + self.write_log_file("----------------") + else: + self.write_log_file("Done Loading outer data samples") + self.write_log_file("----------------") + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + else: + self.write_log_file("Done Loading outer Likelihood samples") + self.write_log_file("----------------") + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + ( + pair_parameter_counter, + is_pair_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="pair_parameter_counter", sub_type="PCMI" + ) + + if is_pair_parameter_counter_avail is False: + pair_parameter_counter = 0 + lower_parameter_pair_loop_idx = 0 + else: + lower_parameter_pair_loop_idx = pair_parameter_counter.item() + + for jj, parameter_pair in enumerate( + parameter_combinations[lower_parameter_pair_loop_idx:, :] + ): + + self.write_log_file( + ">>> Begin Pair {}/{} computations \n".format( + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + + 1, + parameter_combinations.shape[0], + ) + ) + + ( + individual_likelihood_parameter_counter, + is_individual_likelihood_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", + label="pair_outer_counter_individual_likelihood_parameter_pair_" + "{}_{}".format(parameter_pair[0], parameter_pair[1]), + sub_type="PCMI", + ) + + ( + local_individual_likelihood, + is_local_individual_likelihood_avail, + ) = self.load_restart_data( + data_type="inner", + label="pair_outer_individual_likelihood_parameter_pair_{}_{}".format( + parameter_pair[0], parameter_pair[1] + ), + sub_type="PCMI", + ) + + if is_individual_likelihood_parameter_counter_avail is False: + individual_likelihood_parameter_counter = 0 + lower_parameter_loop_idx = 0 + else: + lower_parameter_loop_idx = ( + individual_likelihood_parameter_counter.item() + ) + + if is_local_individual_likelihood_avail is False: + local_individual_likelihood = np.nan * np.ones( + (2, self.local_num_outer_samples) + ) + + for ii, iparameter in enumerate(parameter_pair[lower_parameter_loop_idx:]): + + self.write_log_file( + "Computing individual likelihood (sampling theta_{}), " + "p(y|theta_{}, theta_[rest of parameters])".format( + iparameter, parameter_pair[parameter_pair != iparameter] + ) + ) + + index = np.arange(2)[lower_parameter_loop_idx:][ii] + + local_individual_likelihood[ + index, : + ] = self.estimate_individual_likelihood( + parameter_pair=np.array([iparameter]), + use_quadrature=use_quadrature, + num_gaussian_quad_pts=single_integral_gaussian_quad_pts, + case_type="pair", + parent_pair=parameter_pair, + ) + + individual_likelihood_parameter_counter += 1 + + self.save_restart_data( + data=local_individual_likelihood, + data_type="inner", + label="pair_outer_individual_likelihood_parameter_pair" + "_{}_{}".format(parameter_pair[0], parameter_pair[1]), + sub_type="PCMI", + ) + + self.save_restart_data( + data=individual_likelihood_parameter_counter, + data_type="inner", + label="pair_outer_counter_individual_likelihood_parameter_pair_" + "{}_{}".format(parameter_pair[0], parameter_pair[1]), + sub_type="PCMI", + ) + + self.write_log_file( + "End computing individual likelihood (sampling theta_{}), " + "p(y|theta_{}, theta_[rest of parameters])".format( + iparameter, parameter_pair[parameter_pair != iparameter] + ) + ) + self.write_log_file("----------------") + + local_log_individual_likelihood = np.log(local_individual_likelihood) + + self.write_log_file( + "Computing pair likelihood (sampling theta_{} and theta_{})," + "p(y|theta_[rest of parameters])".format( + parameter_pair[0], parameter_pair[1] + ) + ) + + local_pair_likelihood = self.estimate_individual_likelihood( + parameter_pair=parameter_pair, + use_quadrature=use_quadrature, + num_gaussian_quad_pts=double_integral_gaussian_quad_pts, + case_type="pair", + parent_pair=parameter_pair, + ) + + self.write_log_file("----------------") + local_log_pair_likelihood = np.log(local_pair_likelihood) + + comm.Barrier() + + self.write_log_file( + "End computing pair likelihood (sampling theta_{} and theta_{})," + "p(y|theta_[rest of parameters])\n".format( + parameter_pair[0], parameter_pair[1] + ) + ) + + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_individual_likelihood = comm.gather( + local_log_individual_likelihood, root=0 + ) + global_log_pair_likelihood = comm.gather(local_log_pair_likelihood, root=0) + + if rank == 0: + summation = sum( + [ + np.sum( + global_log_likelihood[ii] + + global_log_pair_likelihood[ii] + - np.sum(global_log_individual_likelihood[ii], axis=0) + ) + for ii in range(size) + ] + ) + pair_mutual_information[ + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + ] = (1 / self.global_num_outer_samples) * summation + + self.write_log_file( + ">>> End Pair {}/{} computations\n".format( + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + + 1, + parameter_combinations.shape[0], + ) + ) + + self.write_log_file(" CMI : {}".format(pair_mutual_information)) + + self.save_quantity( + "estimated_pair_mutual_information.npy", pair_mutual_information + ) + + pair_parameter_counter += 1 + + self.save_restart_data( + data=pair_parameter_counter, + data_type="inner", + label="pair_parameter_counter", + sub_type="PCMI", + ) + + self.write_log_file( + " Normalized CMI : {}".format( + pair_mutual_information / np.sum(np.abs(pair_mutual_information)) + ) + ) + + return pair_mutual_information + + # def estimate_pair_likelihood( + # self, parameter_pair, use_quadrature, quadrature_rule="gaussian" + # ): + # """Function commputes the pair likelihood defined as p(y|theta_{k})""" + # if use_quadrature is True: + # individual_likelihood_prob = ( + # self.integrate_individual_likelihood_via_quadrature( + # quadrature_rule=quadrature_rule, parameter_pair=parameter_pair + # ) + # ) + # else: + # individual_likelihood_prob = self.integrate_individual_likelihood_via_mc( + # parameter_pair=parameter_pair + # ) + # return individual_likelihood_prob + + def estimate_individual_likelihood( + self, + num_gaussian_quad_pts, + parameter_pair, + use_quadrature, + case_type, + quadrature_rule="gaussian", + parent_pair=None, + ): + """Function commputes the individual likelihood defined as p(y|theta_{-i})""" + if use_quadrature is True: + quad_est = self.integrate_individual_likelihood_via_quadrature + individual_likelihood_prob = quad_est( + quadrature_rule=quadrature_rule, + parameter_pair=parameter_pair, + num_gaussian_quad_pts=num_gaussian_quad_pts, + case_type=case_type, + parent_pair=parent_pair, + importance_sampling_cov_factors=[1e-3, 1e-6], + ) + else: + assert self.restart is False, "Error" + individual_likelihood_prob = self.integrate_individual_likelihood_via_mc( + parameter_pair=parameter_pair + ) + return individual_likelihood_prob + + def integrate_individual_likelihood_via_mc(self, parameter_pair): + """Function integrates the individual likelihood via Monte-Carlo""" + # Definitions + individual_likelihood_prob = np.zeros(self.local_num_outer_samples) + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + # Extract sample parameter samples + inner_parameter_samples = self.sample_parameter_distribution( + num_samples=self.local_num_inner_samples, + parameter_pair=sample_parameter_idx, + ) + + for isample in range(self.local_num_outer_samples): + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + + fixed_parameters = self.local_outer_prior_samples[ + fixed_parameter_idx, isample + ].reshape(-1, 1) + eval_parameters = np.zeros( + (self.num_parameters, self.local_num_inner_samples) + ) + eval_parameters[fixed_parameter_idx, :] = np.tile( + fixed_parameters, (1, self.local_num_inner_samples) + ) + eval_parameters[sample_parameter_idx, :] = inner_parameter_samples + + inner_model_prediction = self.compute_model_prediction( + theta=eval_parameters + ) + + error = outer_sample - inner_model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + + sample_individual_likelihood = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + + individual_likelihood_prob[isample] = ( + 1 / self.local_num_inner_samples + ) * np.sum(sample_individual_likelihood) + + return individual_likelihood_prob + + def integrate_individual_likelihood_via_quadrature( + self, + parameter_pair, + num_gaussian_quad_pts, + case_type, + quadrature_rule="gaussian", + parent_pair=None, + importance_sampling_cov_factors=None, + ): + """Function integrates the individual likelihood via Quadrature""" + # Definitions + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + + if case_type == "individual": + likelihood_file_name = "individual_inner_likelihood_parameter_" + str( + sample_parameter_idx.item() + ) + counter_file_name = "individual_inner_counter_parameter_" + str( + sample_parameter_idx.item() + ) + sub_type = "ICMI" + elif case_type == "pair": + if parameter_pair.shape[0] == 1: + likelihood_file_name = ( + "pair_inner_likelihood_parameter_pair_{}_{}_" + "sample_parameter_{}".format( + parent_pair[0], parent_pair[1], sample_parameter_idx.item() + ) + ) + counter_file_name = ( + "pair_inner_counter_parameter_pair_{}_{}_" + "sample_parameter_{}".format( + parent_pair[0], parent_pair[1], sample_parameter_idx.item() + ) + ) + sub_type = "PCMI" + elif parameter_pair.shape[0] == 2: + likelihood_file_name = ( + "pair_inner_likelihood_parameter_pair_{}_{}_" + "sample_parameters_{}_and_{}".format( + parent_pair[0], parent_pair[1], parent_pair[0], parent_pair[1] + ) + ) + counter_file_name = ( + "pair_inner_counter_parameter_pair_{}_{}_" + "sample_parameters_{}_and_{}".format( + parent_pair[0], parent_pair[1], parent_pair[0], parent_pair[1] + ) + ) + sub_type = "PCMI" + else: + raise ValueError("Invalid selection") + else: + raise ValueError("Invalid selection") + + ( + individual_likelihood_prob, + is_individual_likelihood_prob_avail, + ) = self.load_restart_data( + data_type="inner", label=likelihood_file_name, sub_type=sub_type + ) + + ( + local_conditional_mutual_information_counter, + is_local_conditional_mutual_information_counter_avail, + ) = self.load_restart_data( + data_type="inner", label=counter_file_name, sub_type=sub_type + ) + + if is_individual_likelihood_prob_avail is False: + individual_likelihood_prob = np.nan * np.ones(self.local_num_outer_samples) + + if is_local_conditional_mutual_information_counter_avail is False: + local_conditional_mutual_information_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_conditional_mutual_information_counter.item() + + sample_parameter_mean, sample_parameter_cov = self.get_selected_parameter_stats( + parameter_pair=sample_parameter_idx + ) + + def target_density_estimator(eval_points): + probability = self.compute_gaussian_prob( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + samples=eval_points, + ) + + return probability + + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + if ( + isample % 1 == 0 or isample == self.local_num_outer_samples - 1 + ) and self.write_proc_log_file: + self.write_proc_log_file( + " \n(Inner) Sample# : {}/{} [Sampling : {}]\n".format( + isample, self.local_num_outer_samples - 1, parameter_pair + ) + ) + + # Extract outer sample + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + # Fixed parameters + fixed_parameters = self.local_outer_prior_samples[ + fixed_parameter_idx, isample + ].reshape(-1, 1) + + # Integrand + def integrand(eval_theta): + integrand_val = self.pair_wise_likelihood_integrand( + theta=eval_theta, + fixed_parameters=fixed_parameters, + outer_sample=outer_sample, + parameter_pair=parameter_pair, + ) + return integrand_val + + if quadrature_rule == "unscented": + unscented_quad = unscented_quadrature( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + integrand=integrand, + ) + + ( + individual_likelihood_mean, + individual_likelihood_cov, + ) = unscented_quad.compute_integeral() + + individual_likelihood_prob[ + isample + ] = individual_likelihood_mean + np.sqrt( + individual_likelihood_cov + ) * np.random.randn( + 1 + ) + + if individual_likelihood_prob[isample] == 0: + # Use importance sampling + for _, ifactor in enumerate(importance_sampling_cov_factors): + + cov_multiplication_factor = ifactor * np.eye( + sample_parameter_mean.shape[0] + ) + + extracted_sample = self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + + unscented_quad = unscented_quadrature( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=extracted_sample, + ), + cov=sample_parameter_cov * cov_multiplication_factor, + integrand=integrand, + ) + + def proposal_density_estimator(eval_points): + + extracted_sample = self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + + probability = self.compute_gaussian_prob( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=extracted_sample, + ), + cov=sample_parameter_cov * cov_multiplication_factor, + samples=eval_points, + ) + return probability + + quadrature_points = unscented_quad.compute_quadrature_points() + + imp_samp_weight_est = self.compute_importance_sampling_weights + + importance_sampling_weights = imp_samp_weight_est( + target_density_estimator=target_density_estimator, + proposal_density_estimator=proposal_density_estimator, + eval_points=quadrature_points, + ) + + ( + individual_likelihood_mean, + individual_likelihood_cov, + ) = unscented_quad.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + + individual_likelihood_prob[ + isample + ] = individual_likelihood_mean + np.sqrt( + individual_likelihood_cov + ) * np.random.randn( + 1 + ) + + if individual_likelihood_prob[isample] != 0: + break + + elif quadrature_rule == "gaussian": + + gh = gauss_hermite_quadrature( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + + individual_likelihood_prob[isample] = gh.compute_integeral() + + if individual_likelihood_prob[isample] == 0: + + for _, ifactor in enumerate(importance_sampling_cov_factors): + + cov_multiplication_factor = ifactor * np.eye( + sample_parameter_mean.shape[0] + ) + + extracted_sample = self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + + gh = gauss_hermite_quadrature( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=extracted_sample, + ), + cov=sample_parameter_cov * cov_multiplication_factor, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + + def proposal_density_estimator(eval_points): + + extracted_sample = self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + + probability = self.compute_gaussian_prob( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=extracted_sample, + ), + cov=sample_parameter_cov * cov_multiplication_factor, + samples=eval_points, + ) + return probability + + ( + quadrature_points, + _, + ) = gh.compute_quadrature_points_and_weights() + + imp_samp_weight_est = self.compute_importance_sampling_weights + + importance_sampling_weights = imp_samp_weight_est( + target_density_estimator=target_density_estimator, + proposal_density_estimator=proposal_density_estimator, + eval_points=quadrature_points, + ) + + individual_likelihood_prob[isample] = gh.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + + if individual_likelihood_prob[isample] != 0: + break + + else: + raise ValueError("Invalid quadrature rule") + + local_conditional_mutual_information_counter += 1 + + save_condition = ( + isample % self.conditional_mutual_information_save_restart_freq + ) + + if (save_condition == 0) or (isample == self.local_num_outer_samples - 1): + + self.save_restart_data( + data=individual_likelihood_prob, + data_type="inner", + label=likelihood_file_name, + sub_type=sub_type, + ) + + self.save_restart_data( + data=local_conditional_mutual_information_counter, + data_type="inner", + label=counter_file_name, + sub_type=sub_type, + ) + + return individual_likelihood_prob + + def pair_wise_likelihood_integrand( + self, theta, fixed_parameters, outer_sample, parameter_pair + ): + """Function returns the conditional likelihood integrand""" + # Definitions + num_eval_parameters = theta.shape[1] + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + + eval_parameters = np.zeros((self.num_parameters, num_eval_parameters)) + + eval_parameters[sample_parameter_idx, :] = theta + eval_parameters[fixed_parameter_idx, :] = np.tile( + fixed_parameters, (1, num_eval_parameters) + ) + + model_prediction = self.compute_model_prediction(theta=eval_parameters) + + error = outer_sample - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = (pre_exp**self.num_data_samples) * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + + return sample_evidence_estimates.reshape(1, -1) + + def get_fixed_parameter_id(self, parameter_pair): + """Function returns the idx of fixed parameters given the parameter pair""" + num_parameter = parameter_pair.shape[0] + parameter_list = np.arange(self.num_parameters) + conditions = [] + for ii in range(num_parameter): + conditions.append(parameter_pair[ii] != parameter_list) + idx = np.prod(np.array(conditions), axis=0) == 1 + return parameter_list[idx] + + def get_sample_parameter_id(self, parameter_pair): + """Function returns the idx of sample parameters given the parameter pair""" + return parameter_pair + + def get_sample_centered_mean(self, sample_parameter_extracted_sample): + """Function constructs the mean around the sample parameter""" + return sample_parameter_extracted_sample.reshape(-1, 1) + + def compute_gaussian_prob(self, mean, cov, samples): + """Function evaluates the proabability of the samples given the normal + distribution""" + d = mean.shape[0] + error = mean - samples + exp_term = np.exp(-0.5 * np.diag(error.T @ np.linalg.solve(cov, error))) + pre_exp_term = 1 / (((2 * np.pi) ** (d / 2)) * (np.linalg.det(cov) ** (0.5))) + return pre_exp_term * exp_term + + def compute_importance_sampling_weights( + self, target_density_estimator, proposal_density_estimator, eval_points + ): + """Function computes the weights for importace sampling""" + target_denstiy = target_density_estimator(eval_points) + proposal_density = proposal_density_estimator(eval_points) + return target_denstiy / proposal_density diff --git a/information_metrics/compute_identifiability_radiative_heat_transfer.py b/information_metrics/compute_identifiability_radiative_heat_transfer.py new file mode 100644 index 0000000..720a5e0 --- /dev/null +++ b/information_metrics/compute_identifiability_radiative_heat_transfer.py @@ -0,0 +1,1451 @@ +# Author: Sahil Bhola (University of Michigan) +# Date: 14 June 2022 +# Description : Computes the model identifiability using Mutual information specific ... +# the radiative heat transfer problem. + +import numpy as np +from mpi4py import MPI +import sys +from itertools import combinations +import os +import shutil +import matplotlib.pyplot as plt + +sys.path.append("/home/sbhola/Documents/CASLAB/GIM/quadrature") +from quadrature import unscented_quadrature, gauss_hermite_quadrature + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class mutual_information: + def __init__( + self, + forward_model, + prior_mean, + prior_cov, + model_noise_cov_scalar, + global_num_outer_samples, + global_num_inner_samples, + save_path, + restart, + ytrain=None, + log_file=None, + mutual_information_evidence_save_restart_freq=50, + conditional_mutual_information_save_restart_freq=50, + ): + self.forward_model = forward_model + self.prior_mean = prior_mean + self.prior_cov = prior_cov + self.model_noise_cov_scalar = model_noise_cov_scalar + self.global_num_outer_samples = global_num_outer_samples + self.global_num_inner_samples = global_num_inner_samples + self.log_file = log_file + self.ytrain = ytrain + self.save_path = save_path + self.mutual_information_evidence_save_restart_freq = ( + mutual_information_evidence_save_restart_freq + ) + self.conditional_mutual_information_save_restart_freq = ( + conditional_mutual_information_save_restart_freq + ) + self.restart = restart + + self.num_parameters = self.prior_mean.shape[0] + + self.local_num_inner_samples = self.global_num_inner_samples + self.local_num_outer_samples = self.compute_local_num_outer_samples() + + self.local_outer_prior_samples = np.zeros( + (self.num_parameters, self.local_num_outer_samples) + ) + + self.local_outer_model_prediction = np.zeros( + self.ytrain.shape + (self.local_num_outer_samples,) + ) + + self.local_outer_data_samples = np.zeros( + self.local_outer_model_prediction.shape + ) + + self.local_outer_likelihood_prob = np.zeros(self.local_num_outer_samples) + + self.outer_data_computation_req_flag = True + + self.num_data_samples = self.ytrain.shape[0] + self.spatial_res = self.ytrain.shape[1] + + self.restart_file_path = os.path.join(self.save_path, "restart_files") + if self.restart is False: + if rank == 0: + self.create_restart_folders() + self.remove_file("estimated_mutual_information.npy") + self.remove_file("estimated_individual_mutual_information.npy") + self.remove_file("estimated_pair_mutual_information.npy") + comm.Barrier() + + def compute_local_num_outer_samples(self): + """Function computes the number of local number of samples""" + assert ( + self.global_num_outer_samples % size == 0 + ), "Equally divide the outer expectation samples" + return int(self.global_num_outer_samples / size) + + def sample_gaussian(self, mean, cov, num_samples): + """Function samples the gaussian""" + # Definitions + d, N = mean.shape + product = d * N * num_samples + + if N > 1: + assert np.prod(cov.shape) == 1, "Must provide scalar cov" + L = np.linalg.cholesky(cov + np.eye(1) * 1e-8) + noise = L @ np.random.randn(product).reshape(d, N, num_samples) + return np.expand_dims(mean, axis=-1) + noise + else: + L = np.linalg.cholesky(cov + np.eye(d) * 1e-8) + noise = L @ np.random.randn(product).reshape(d, num_samples) + return mean + noise + + def sample_parameter_distribution(self, num_samples, parameter_pair=None): + """Function samples the parameter distribution""" + if parameter_pair is None: + # parameter_samples = self.sample_gaussian( + # mean=self.prior_mean, + # cov=self.prior_cov, + # num_samples=num_samples + # ) + raise ValueError("Must provide parameter pair") + else: + ( + selected_parameter_mean, + selected_parameter_cov, + ) = self.get_selected_parameter_stats(parameter_pair=parameter_pair) + parameter_samples = self.sample_gaussian( + mean=selected_parameter_mean, + cov=selected_parameter_cov, + num_samples=num_samples, + ) + + return parameter_samples + + def sample_prior_distribution(self, num_samples): + """Function samples the prior distribution""" + prior_samples = self.sample_gaussian( + mean=self.prior_mean, cov=self.prior_cov, num_samples=num_samples + ) + return prior_samples + + def get_selected_parameter_stats(self, parameter_pair): + """Function selectes the parameter pair + Assumptions: parameters are assumed to be uncorrelated (prior to observing the data)""" + mean = self.prior_mean[parameter_pair, :].reshape(parameter_pair.shape[0], 1) + cov = np.diag(np.diag(self.prior_cov)[parameter_pair]) + return mean, cov + + def compute_model_prediction(self, theta): + """Function computes the model prediction""" + num_samples = theta.shape[1] + prediction = np.zeros(self.ytrain.shape + (num_samples,)) + for isample in range(num_samples): + prediction[:, :, isample] = self.forward_model(theta[:, isample]) + return prediction + + def sample_likelihood(self, theta): + """Function samples the likelihood""" + model_prediction = self.compute_model_prediction(theta=theta) + product = np.prod(model_prediction.shape) + noise = np.sqrt(self.model_noise_cov_scalar) * np.random.randn(product).reshape( + model_prediction.shape + ) + structured_noise = self.enforce_noise_structure( + model_prediction=model_prediction, noise=noise + ) + likelihood_sample = model_prediction + structured_noise + return likelihood_sample, model_prediction + + def enforce_noise_structure(self, model_prediction, noise): + """Function enforces noise structure such that the temperature value for the + RHT problem is non-negative. Futher, no noise is added to the boundary points""" + structured_noise = noise.copy() + structured_noise[:, 0, :] = 0 + structured_noise[:, -1, :] = 0 + return structured_noise + + def evaluate_likelihood_probaility(self, data, model_prediction): + """Function evaluates the likelihood probability""" + error = data - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + assert data.shape[0] == 1, "need to tweek pre-exp for multiple samples" + likelihood = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + return likelihood + + def estimate_mutual_information_via_mc( + self, use_quadrature=False, quadrature_rule="gaussian", num_gaussian_quad_pts=50 + ): + """Function computes the mutual information via mc""" + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the mutual information, I(theta;Y) <<<" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + # Compute evidence + local_evidence_prob = self.estimate_evidence_probability( + use_quadrature=use_quadrature, + quadrature_rule=quadrature_rule, + num_gaussian_quad_pts=num_gaussian_quad_pts, + ) + local_log_evidence = np.log(local_evidence_prob) + + comm.Barrier() + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_evidence = comm.gather(local_log_evidence, root=0) + + if rank == 0: + summation = sum( + [ + np.sum(global_log_likelihood[ii] - global_log_evidence[ii]) + for ii in range(size) + ] + ) + mutual_information = (1 / self.global_num_outer_samples) * summation + + self.save_quantity("estimated_mutual_information.npy", mutual_information) + self.write_log_file( + ">>> End computing the mutual information, I(theta;Y) <<<" + ) + + def estimate_evidence_probability( + self, num_gaussian_quad_pts, use_quadrature=False, quadrature_rule="gaussian" + ): + """Function estimates the evidence probability""" + if use_quadrature is True: + evidence_prob = self.integrate_likelihood_via_quadrature( + quadrature_rule=quadrature_rule, + num_gaussian_quad_pts=num_gaussian_quad_pts, + ) + else: + evidence_prob = self.integrate_likelihood_via_mc() + + return evidence_prob + + def integrate_likelihood_via_mc(self): + """Function integrates the likelihood to estimate the evidence""" + # Definitions + evidence_prob, is_evidence_prob_avail = self.load_restart_data( + data_type="inner", label="evidence_prob", sub_type="MI" + ) + + ( + local_mutual_information_inner_counter, + is_mi_inner_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="mutual_infomation_inner_counter", sub_type="MI" + ) + + if is_evidence_prob_avail is False: + evidence_prob = np.nan * np.ones(self.local_num_outer_samples) + if is_mi_inner_counter_avail is False: + local_mutual_information_inner_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_mutual_information_inner_counter.item() + + # Pre computations + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + # Compute inner prior samples + inner_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_inner_samples + ) + + # Compute model prediction + inner_model_prediction = self.compute_model_prediction( + theta=inner_prior_samples + ) + + # Comptute the evidence + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + error = outer_sample - inner_model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + evidence_prob[isample] = (1 / self.local_num_inner_samples) * np.sum( + sample_evidence_estimates + ) + + local_mutual_information_inner_counter += 1 + + save_condition = ( + isample % self.mutual_information_evidence_save_restart_freq + ) + final_save_condition = ( + self.local_num_outer_samples + % self.mutual_information_evidence_save_restart_freq + ) + + if save_condition or final_save_condition == 0: + self.save_restart_data( + data=local_mutual_information_inner_counter, + data_type="inner", + label="mutual_infomation_inner_counter", + sub_type="MI", + ) + + self.save_restart_data( + data=evidence_prob, + data_type="inner", + label="evidence_prob", + sub_type="MI", + ) + + return evidence_prob + + def integrate_likelihood_via_quadrature( + self, quadrature_rule, num_gaussian_quad_pts + ): + """Function integrates the likelihood to estimate the evidence using quadratures""" + # Definitions + evidence_prob, is_evidence_prob_avail = self.load_restart_data( + data_type="inner", label="evidence_prob", sub_type="MI" + ) + + ( + local_mutual_information_inner_counter, + is_mi_inner_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="mutual_infomation_inner_counter", sub_type="MI" + ) + + if is_evidence_prob_avail is False: + evidence_prob = np.nan * np.ones(self.local_num_outer_samples) + if is_mi_inner_counter_avail is False: + local_mutual_information_inner_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_mutual_information_inner_counter.item() + + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + # Extract outer sample + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + + # Integrand + def integrand(eval_theta): + integrand_val = self.likelihood_integrand( + theta=eval_theta, outer_sample=outer_sample + ) + return integrand_val + + if quadrature_rule == "unscented": + unscented_quad = unscented_quadrature( + mean=self.prior_mean, cov=self.prior_cov, integrand=integrand + ) + + evidence_mean, evidence_cov = unscented_quad.compute_integeral() + + evidence_prob[isample] = evidence_mean + np.sqrt( + evidence_cov + ) * np.random.randn(1) + + elif quadrature_rule == "gaussian": + gh = gauss_hermite_quadrature( + mean=self.prior_mean, + cov=self.prior_cov, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + evidence_prob[isample] = gh.compute_integeral() + + else: + raise ValueError("Invalid quadrature rule") + + local_mutual_information_inner_counter += 1 + + save_condition = ( + isample % self.mutual_information_evidence_save_restart_freq + ) + final_save_condition = ( + self.local_num_outer_samples + % self.mutual_information_evidence_save_restart_freq + ) + + if save_condition or final_save_condition == 0: + self.save_restart_data( + data=local_mutual_information_inner_counter, + data_type="inner", + label="mutual_infomation_inner_counter", + sub_type="MI", + ) + + self.save_restart_data( + data=evidence_prob, + data_type="inner", + label="evidence_prob", + sub_type="MI", + ) + return evidence_prob + + def likelihood_integrand(self, theta, outer_sample): + """Function returns the integrand evaluated at the quadrature points + NOTE: shape of the model prediction should be :(num_data_samples, spatial_res, num_samples)""" + model_prediction = self.compute_model_prediction(theta=theta) + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + error = outer_sample - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + return sample_evidence_estimates.reshape(1, -1) + + def display_messsage(self, message, print_rank="root"): + """Print function""" + if print_rank == "root": + if rank == 0: + print(message) + elif print_rank == "all": + print(message + " at rank : {}".format(rank)) + elif rank == print_rank: + print(message + " at rank : {}".format(rank)) + + def save_quantity(self, file_name, data): + """Function saves the data""" + if rank == 0: + np.save(os.path.join(self.save_path, file_name), data) + else: + pass + + def write_log_file(self, message): + """Function writes the message on the log file""" + if rank == 0: + assert self.log_file is not None, "log file must be provided" + self.log_file.write(message + "\n") + self.log_file.flush() + + def save_restart_data(self, data, data_type, label, sub_type=None): + """Function saves the data""" + if data_type == "outer": + folder = "outer_data" + file_path = os.path.join( + self.restart_file_path, folder, label + "_rank_{}.npy".format(rank) + ) + else: + folder = "inner_data" + + if sub_type == "MI": + sub_folder = "mutual_information" + elif sub_type == "ICMI": + sub_folder = "individual_conditional_mutual_information" + elif sub_type == "PCMI": + sub_folder = "pair_conditional_mutual_information" + else: + raise ValueError("Invalid subfolder type") + + file_path = os.path.join( + self.restart_file_path, + folder, + sub_folder, + label + "_rank_{}.npy".format(rank), + ) + + np.save(file_path, data) + + def load_restart_data(self, data_type, label, sub_type=None): + """Function loads the data""" + if data_type == "outer": + folder = "outer_data" + file_path = os.path.join( + self.restart_file_path, folder, label + "_rank_{}.npy".format(rank) + ) + else: + folder = "inner_data" + + if sub_type == "MI": + sub_folder = "mutual_information" + elif sub_type == "ICMI": + sub_folder = "individual_conditional_mutual_information" + elif sub_type == "PCMI": + sub_folder = "pair_conditional_mutual_information" + + file_path = os.path.join( + self.restart_file_path, + folder, + sub_folder, + label + "_rank_{}.npy".format(rank), + ) + + file_exists = os.path.exists(file_path) + if file_exists: + return np.load(file_path), file_exists + else: + return np.nan, file_exists + + def create_restart_folders(self): + """Function creates the restart folder""" + if os.path.exists(self.restart_file_path): + shutil.rmtree(self.restart_file_path) + os.mkdir(self.restart_file_path) + os.mkdir(os.path.join(self.restart_file_path, "outer_data")) + os.mkdir(os.path.join(self.restart_file_path, "inner_data")) + os.mkdir( + os.path.join(self.restart_file_path, "inner_data", "mutual_information") + ) + os.mkdir( + os.path.join( + self.restart_file_path, + "inner_data", + "individual_conditional_mutual_information", + ) + ) + os.mkdir( + os.path.join( + self.restart_file_path, + "inner_data", + "pair_conditional_mutual_information", + ) + ) + + def remove_file(self, file_name): + """Function deletes a file""" + if rank == 0: + if os.path.exists(file_name): + os.remove(file_name) + else: + pass + + +class conditional_mutual_information(mutual_information): + def compute_individual_parameter_data_mutual_information_via_mc( + self, use_quadrature=False, single_integral_gaussian_quad_pts=60 + ): + """Function computes the mutual information between each parameter theta_i and data Y given rest of the parameters""" + # Definitions + if os.path.exists("estimated_individual_mutual_information.npy"): + inidividual_mutual_information = np.load( + "estimated_individual_mutual_information.npy" + ) + else: + inidividual_mutual_information = np.nan * np.ones(self.num_parameters) + + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the individual parameter mutual information, I(theta_i;Y|theta_[rest of parameters])" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + else: + self.write_log_file("Loaded outer prior samples") + self.write_log_file("----------------") + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + + self.write_log_file("Done computing outer data samples") + self.write_log_file("----------------") + else: + self.write_log_file("Loaded outer data samples") + self.write_log_file("----------------") + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + else: + self.write_log_file("Loaded outer Likelihood prob") + self.write_log_file("----------------") + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + # Estimate p(y|theta_{-i}) + ( + individual_parameter_counter, + is_individual_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="individual_parameter_counter", sub_type="ICMI" + ) + if is_individual_parameter_counter_avail is False: + individual_parameter_counter = 0 + lower_parameter_loop_idx = 0 + else: + lower_parameter_loop_idx = individual_parameter_counter.item() + + for iparameter in range(lower_parameter_loop_idx, self.num_parameters): + self.write_log_file( + "Computing I(theta_{};Y| theta_[rest of parameters])".format(iparameter) + ) + + parameter_pair = np.array([iparameter]) + local_individual_likelihood = self.estimate_individual_likelihood( + parameter_pair=parameter_pair, + use_quadrature=use_quadrature, + num_gaussian_quad_pts=single_integral_gaussian_quad_pts, + case_type="individual", + ) + + local_log_individual_likelihood = np.log(local_individual_likelihood) + self.write_log_file("Done computing log individual likelihood") + + comm.Barrier() + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_individual_likelihood = comm.gather( + local_log_individual_likelihood, root=0 + ) + + if rank == 0: + summation = sum( + [ + np.sum( + global_log_likelihood[ii] + - global_log_individual_likelihood[ii] + ) + for ii in range(size) + ] + ) + inidividual_mutual_information[iparameter] = ( + 1 / self.global_num_outer_samples + ) * summation + self.write_log_file("----------------") + + individual_parameter_counter += 1 + + self.save_restart_data( + data=individual_parameter_counter, + data_type="inner", + label="individual_parameter_counter", + sub_type="ICMI", + ) + + self.save_quantity( + "estimated_individual_mutual_information.npy", + inidividual_mutual_information, + ) + + self.write_log_file( + ">>> End computing the individual parameter mutual information, I(theta_i;Y)" + ) + + def compute_posterior_pair_parameter_mutual_information( + self, + use_quadrature=False, + single_integral_gaussian_quad_pts=60, + double_integral_gaussian_quad_pts=30, + ): + """Function computes the posterior mutual information between parameters, I(theta_i;theta_j|Y, theta_k)""" + # Definitions + parameter_combinations = np.array( + list(combinations(np.arange(self.num_parameters), 2)) + ) + + if os.path.exists("estimated_pair_mutual_information.npy"): + pair_mutual_information = np.load("estimated_pair_mutual_information.npy") + else: + pair_mutual_information = np.nan * np.ones(parameter_combinations.shape[0]) + + self.write_log_file( + ">>>-----------------------------------------------------------<<<" + ) + self.write_log_file( + ">>> Begin computing the pair parameter mutual information, I(theta_i;theta_j|Y, theta_[rest of parameters])" + ) + self.write_log_file( + ">>>-----------------------------------------------------------<<<\n" + ) + + if self.outer_data_computation_req_flag is True: + # Generate the prior samples ~ p(\theta) + ( + self.local_outer_prior_samples, + is_local_outer_prior_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_prior_samples") + if is_local_outer_prior_samp_avail is False: + self.local_outer_prior_samples = self.sample_prior_distribution( + num_samples=self.local_num_outer_samples + ) + else: + self.write_log_file("Done Loading outer Prior samples") + self.write_log_file("----------------") + + # Generate data samples from the conditional distribution, p(y | \theta) + ( + self.local_outer_data_samples, + is_local_outer_data_samp_avail, + ) = self.load_restart_data(data_type="outer", label="outer_data_samples") + if is_local_outer_data_samp_avail is False: + ( + self.local_outer_data_samples, + self.local_outer_model_prediction, + ) = self.sample_likelihood(theta=self.local_outer_prior_samples) + self.write_log_file("Done computing outer data samples") + self.write_log_file("----------------") + else: + self.write_log_file("Done Loading outer data samples") + self.write_log_file("----------------") + + # Likelihood probability + ( + self.local_outer_likelihood_prob, + is_local_outer_likelihood_prob_avail, + ) = self.load_restart_data(data_type="outer", label="outer_likelihood_prob") + + if is_local_outer_likelihood_prob_avail is False: + self.local_outer_likelihood_prob = self.evaluate_likelihood_probaility( + data=self.local_outer_data_samples, + model_prediction=self.local_outer_model_prediction, + ) + else: + self.write_log_file("Done Loading outer Likelihood samples") + self.write_log_file("----------------") + + self.save_restart_data( + data=self.local_outer_prior_samples, + data_type="outer", + label="outer_prior_samples", + ) + self.save_restart_data( + data=self.local_outer_data_samples, + data_type="outer", + label="outer_data_samples", + ) + + self.save_restart_data( + data=self.local_outer_likelihood_prob, + data_type="outer", + label="outer_likelihood_prob", + ) + + self.outer_data_computation_req_flag = False + + local_log_likelihood = np.log(self.local_outer_likelihood_prob) + + ( + pair_parameter_counter, + is_pair_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", label="pair_parameter_counter", sub_type="PCMI" + ) + + if is_pair_parameter_counter_avail is False: + pair_parameter_counter = 0 + lower_parameter_pair_loop_idx = 0 + else: + lower_parameter_pair_loop_idx = pair_parameter_counter.item() + + for jj, parameter_pair in enumerate( + parameter_combinations[lower_parameter_pair_loop_idx:, :] + ): + + self.write_log_file( + ">>> Begin Pair {}/{} computations \n".format( + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + + 1, + parameter_combinations.shape[0], + ) + ) + + ( + individual_likelihood_parameter_counter, + is_individual_likelihood_parameter_counter_avail, + ) = self.load_restart_data( + data_type="inner", + label="pair_outer_counter_individual_likelihood_parameter_pair_{}_{}".format( + parameter_pair[0], parameter_pair[1] + ), + sub_type="PCMI", + ) + + ( + local_individual_likelihood, + is_local_individual_likelihood_avail, + ) = self.load_restart_data( + data_type="inner", + label="pair_outer_individual_likelihood_parameter_pair_{}_{}".format( + parameter_pair[0], parameter_pair[1] + ), + sub_type="PCMI", + ) + + if is_individual_likelihood_parameter_counter_avail is False: + individual_likelihood_parameter_counter = 0 + lower_parameter_loop_idx = 0 + else: + lower_parameter_loop_idx = ( + individual_likelihood_parameter_counter.item() + ) + + if is_local_individual_likelihood_avail is False: + local_individual_likelihood = np.nan * np.ones( + (2, self.local_num_outer_samples) + ) + + for ii, iparameter in enumerate(parameter_pair[lower_parameter_loop_idx:]): + + self.write_log_file( + "Computing individual likelihood (sampling theta_{}), p(y|theta_{}, theta_[rest of parameters])".format( + iparameter, parameter_pair[parameter_pair != iparameter] + ) + ) + + index = np.arange(2)[lower_parameter_loop_idx:][ii] + + local_individual_likelihood[ + index, : + ] = self.estimate_individual_likelihood( + parameter_pair=np.array([iparameter]), + use_quadrature=use_quadrature, + num_gaussian_quad_pts=single_integral_gaussian_quad_pts, + case_type="pair", + parent_pair=parameter_pair, + ) + + individual_likelihood_parameter_counter += 1 + + self.save_restart_data( + data=local_individual_likelihood, + data_type="inner", + label="pair_outer_individual_likelihood_parameter_pair_{}_{}".format( + parameter_pair[0], parameter_pair[1] + ), + sub_type="PCMI", + ) + + self.save_restart_data( + data=individual_likelihood_parameter_counter, + data_type="inner", + label="pair_outer_counter_individual_likelihood_parameter_pair_{}_{}".format( + parameter_pair[0], parameter_pair[1] + ), + sub_type="PCMI", + ) + + self.write_log_file( + "End computing individual likelihood (sampling theta_{}), p(y|theta_{}, theta_[rest of parameters])".format( + iparameter, parameter_pair[parameter_pair != iparameter] + ) + ) + self.write_log_file("----------------") + + local_log_individual_likelihood = np.log(local_individual_likelihood) + + self.write_log_file( + "Computing pair likelihood (sampling theta_{} and theta_{}), p(y|theta_[rest of parameters])".format( + parameter_pair[0], parameter_pair[1] + ) + ) + + local_pair_likelihood = self.estimate_individual_likelihood( + parameter_pair=parameter_pair, + use_quadrature=use_quadrature, + num_gaussian_quad_pts=double_integral_gaussian_quad_pts, + case_type="pair", + parent_pair=parameter_pair, + ) + + self.write_log_file("----------------") + local_log_pair_likelihood = np.log(local_pair_likelihood) + self.write_log_file( + "End computing pair likelihood (sampling theta_{} and theta_{}), p(y|theta_[rest of parameters])\n".format( + parameter_pair[0], parameter_pair[1] + ) + ) + + comm.Barrier() + + global_log_likelihood = comm.gather(local_log_likelihood, root=0) + global_log_individual_likelihood = comm.gather( + local_log_individual_likelihood, root=0 + ) + global_log_pair_likelihood = comm.gather(local_log_pair_likelihood, root=0) + + if rank == 0: + summation = sum( + [ + np.sum( + global_log_likelihood[ii] + + global_log_pair_likelihood[ii] + - np.sum(global_log_individual_likelihood[ii], axis=0) + ) + for ii in range(size) + ] + ) + pair_mutual_information[ + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + ] = (1 / self.global_num_outer_samples) * summation + + self.write_log_file( + ">>> End Pair {}/{} computations\n".format( + np.arange(parameter_combinations.shape[0])[ + lower_parameter_pair_loop_idx: + ][jj] + + 1, + parameter_combinations.shape[0], + ) + ) + + self.save_quantity( + "estimated_pair_mutual_information.npy", pair_mutual_information + ) + + pair_parameter_counter += 1 + + self.save_restart_data( + data=pair_parameter_counter, + data_type="inner", + label="pair_parameter_counter", + sub_type="PCMI", + ) + + def estimate_pair_likelihood( + self, parameter_pair, use_quadrature, quadrature_rule="gaussian" + ): + """Function commputes the pair likelihood defined as p(y|theta_{k})""" + if use_quadrature is True: + individual_likelihood_prob = ( + self.integrate_individual_likelihood_via_quadrature( + quadrature_rule=quadrature_rule, parameter_pair=parameter_pair + ) + ) + else: + individual_likelihood_prob = self.integrate_individual_likelihood_via_mc( + parameter_pair=parameter_pair + ) + return individual_likelihood_prob + + def estimate_individual_likelihood( + self, + num_gaussian_quad_pts, + parameter_pair, + use_quadrature, + case_type, + quadrature_rule="gaussian", + parent_pair=None, + ): + """Function commputes the individual likelihood defined as p(y|theta_{-i})""" + if use_quadrature is True: + individual_likelihood_prob = ( + self.integrate_individual_likelihood_via_quadrature( + quadrature_rule=quadrature_rule, + parameter_pair=parameter_pair, + num_gaussian_quad_pts=num_gaussian_quad_pts, + case_type=case_type, + parent_pair=parent_pair, + importance_sampling_cov_factors=[1e-3, 1e-6], + ) + ) + else: + assert self.restart is False, "Error" + individual_likelihood_prob = self.integrate_individual_likelihood_via_mc( + parameter_pair=parameter_pair + ) + return individual_likelihood_prob + + def integrate_individual_likelihood_via_mc(self, parameter_pair): + """Function integrates the individual likelihood via Monte-Carlo""" + # Definitions + individual_likelihood_prob = np.zeros(self.local_num_outer_samples) + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + + # Extract sample parameter samples + inner_parameter_samples = self.sample_parameter_distribution( + num_samples=self.local_num_inner_samples, + parameter_pair=sample_parameter_idx, + ) + + for isample in range(self.local_num_outer_samples): + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + + fixed_parameters = self.local_outer_prior_samples[ + fixed_parameter_idx, isample + ].reshape(-1, 1) + eval_parameters = np.zeros( + (self.num_parameters, self.local_num_inner_samples) + ) + eval_parameters[fixed_parameter_idx, :] = np.tile( + fixed_parameters, (1, self.local_num_inner_samples) + ) + eval_parameters[sample_parameter_idx, :] = inner_parameter_samples + + inner_model_prediction = self.compute_model_prediction( + theta=eval_parameters + ) + + error = outer_sample - inner_model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + + sample_individual_likelihood = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + + individual_likelihood_prob[isample] = ( + 1 / self.local_num_inner_samples + ) * np.sum(sample_individual_likelihood) + + return individual_likelihood_prob + + def integrate_individual_likelihood_via_quadrature( + self, + parameter_pair, + num_gaussian_quad_pts, + case_type, + quadrature_rule="gaussian", + parent_pair=None, + importance_sampling_cov_factors=None, + ): + """Function integrates the individual likelihood via Quadrature""" + # Definitions + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + + if case_type == "individual": + likelihood_file_name = "individual_inner_likelihood_parameter_" + str( + sample_parameter_idx.item() + ) + counter_file_name = "individual_inner_counter_parameter_" + str( + sample_parameter_idx.item() + ) + sub_type = "ICMI" + elif case_type == "pair": + if parameter_pair.shape[0] == 1: + likelihood_file_name = "pair_inner_likelihood_parameter_pair_{}_{}_sample_parameter_{}".format( + parent_pair[0], parent_pair[1], sample_parameter_idx.item() + ) + counter_file_name = "pair_inner_counter_parameter_pair_{}_{}_sample_parameter_{}".format( + parent_pair[0], parent_pair[1], sample_parameter_idx.item() + ) + sub_type = "PCMI" + elif parameter_pair.shape[0] == 2: + likelihood_file_name = "pair_inner_likelihood_parameter_pair_{}_{}_sample_parameters_{}_and_{}".format( + parent_pair[0], parent_pair[1], parent_pair[0], parent_pair[1] + ) + counter_file_name = "pair_inner_counter_parameter_pair_{}_{}_sample_parameters_{}_and_{}".format( + parent_pair[0], parent_pair[1], parent_pair[0], parent_pair[1] + ) + sub_type = "PCMI" + else: + raise ValueError("Invalid selection") + else: + raise ValueError("Invalid selection") + + ( + individual_likelihood_prob, + is_individual_likelihood_prob_avail, + ) = self.load_restart_data( + data_type="inner", label=likelihood_file_name, sub_type=sub_type + ) + + ( + local_conditional_mutual_information_counter, + is_local_conditional_mutual_information_counter_avail, + ) = self.load_restart_data( + data_type="inner", label=counter_file_name, sub_type=sub_type + ) + + if is_individual_likelihood_prob_avail is False: + individual_likelihood_prob = np.nan * np.ones(self.local_num_outer_samples) + + if is_local_conditional_mutual_information_counter_avail is False: + local_conditional_mutual_information_counter = 0 + local_lower_loop_idx = 0 + else: + local_lower_loop_idx = local_conditional_mutual_information_counter.item() + + sample_parameter_mean, sample_parameter_cov = self.get_selected_parameter_stats( + parameter_pair=sample_parameter_idx + ) + + def target_density_estimator(eval_points): + probability = self.compute_gaussian_prob( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + samples=eval_points, + ) + + return probability + + for isample in range(local_lower_loop_idx, self.local_num_outer_samples): + # Extract outer sample + outer_sample = np.expand_dims( + self.local_outer_data_samples[:, :, isample], axis=-1 + ) + # Fixed parameters + fixed_parameters = self.local_outer_prior_samples[ + fixed_parameter_idx, isample + ].reshape(-1, 1) + + # Integrand + def integrand(eval_theta): + integrand_val = self.pair_wise_likelihood_integrand( + theta=eval_theta, + fixed_parameters=fixed_parameters, + outer_sample=outer_sample, + parameter_pair=parameter_pair, + ) + return integrand_val + + if quadrature_rule == "unscented": + unscented_quad = unscented_quadrature( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + integrand=integrand, + ) + + ( + individual_likelihood_mean, + individual_likelihood_cov, + ) = unscented_quad.compute_integeral() + + individual_likelihood_prob[ + isample + ] = individual_likelihood_mean + np.sqrt( + individual_likelihood_cov + ) * np.random.randn( + 1 + ) + + if individual_likelihood_prob[isample] == 0: + # Use importance sampling + for _, ifactor in enumerate(importance_sampling_cov_factors): + + cov_multiplication_factor = ifactor * np.eye( + sample_parameter_mean.shape[0] + ) + + unscented_quad = unscented_quadrature( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + ), + cov=sample_parameter_cov * cov_multiplication_factor, + integrand=integrand, + ) + + def proposal_density_estimator(eval_points): + probability = self.compute_gaussian_prob( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + ), + cov=sample_parameter_cov * cov_multiplication_factor, + samples=eval_points, + ) + return probability + + quadrature_points = unscented_quad.compute_quadrature_points() + + importance_sampling_weights = ( + self.compute_importance_sampling_weights( + target_density_estimator=target_density_estimator, + proposal_density_estimator=proposal_density_estimator, + eval_points=quadrature_points, + ) + ) + + ( + individual_likelihood_mean, + individual_likelihood_cov, + ) = unscented_quad.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + + individual_likelihood_prob[ + isample + ] = individual_likelihood_mean + np.sqrt( + individual_likelihood_cov + ) * np.random.randn( + 1 + ) + + if individual_likelihood_prob[isample] != 0: + break + + elif quadrature_rule == "gaussian": + + gh = gauss_hermite_quadrature( + mean=sample_parameter_mean, + cov=sample_parameter_cov, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + + individual_likelihood_prob[isample] = gh.compute_integeral() + + if individual_likelihood_prob[isample] == 0: + + for _, ifactor in enumerate(importance_sampling_cov_factors): + + cov_multiplication_factor = ifactor * np.eye( + sample_parameter_mean.shape[0] + ) + + gh = gauss_hermite_quadrature( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + ), + cov=sample_parameter_cov * cov_multiplication_factor, + integrand=integrand, + num_points=num_gaussian_quad_pts, + ) + + def proposal_density_estimator(eval_points): + probability = self.compute_gaussian_prob( + mean=self.get_sample_centered_mean( + sample_parameter_extracted_sample=self.local_outer_prior_samples[ + sample_parameter_idx, isample + ] + ), + cov=sample_parameter_cov * cov_multiplication_factor, + samples=eval_points, + ) + return probability + + ( + quadrature_points, + _, + ) = gh.compute_quadrature_points_and_weights() + + importance_sampling_weights = ( + self.compute_importance_sampling_weights( + target_density_estimator=target_density_estimator, + proposal_density_estimator=proposal_density_estimator, + eval_points=quadrature_points, + ) + ) + + individual_likelihood_prob[isample] = gh.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + + if individual_likelihood_prob[isample] != 0: + break + + else: + raise ValueError("Invalid quadrature rule") + + local_conditional_mutual_information_counter += 1 + + save_condition = ( + isample % self.conditional_mutual_information_save_restart_freq + ) + + if (save_condition == 0) or (isample == self.local_num_outer_samples - 1): + + self.save_restart_data( + data=individual_likelihood_prob, + data_type="inner", + label=likelihood_file_name, + sub_type=sub_type, + ) + + self.save_restart_data( + data=local_conditional_mutual_information_counter, + data_type="inner", + label=counter_file_name, + sub_type=sub_type, + ) + + return individual_likelihood_prob + + def pair_wise_likelihood_integrand( + self, theta, fixed_parameters, outer_sample, parameter_pair + ): + """Function returns the conditional likelihood integrand""" + # Definitions + num_eval_parameters = theta.shape[1] + pre_exp = 1 / ( + (2 * np.pi * self.model_noise_cov_scalar) ** (self.spatial_res / 2) + ) + fixed_parameter_idx = self.get_fixed_parameter_id(parameter_pair=parameter_pair) + sample_parameter_idx = self.get_sample_parameter_id( + parameter_pair=parameter_pair + ) + + eval_parameters = np.zeros((self.num_parameters, num_eval_parameters)) + + eval_parameters[sample_parameter_idx, :] = theta + eval_parameters[fixed_parameter_idx, :] = np.tile( + fixed_parameters, (1, num_eval_parameters) + ) + + model_prediction = self.compute_model_prediction(theta=eval_parameters) + + error = outer_sample - model_prediction + error_norm_sq = np.linalg.norm(error, axis=1) ** 2 + sample_evidence_estimates = pre_exp * np.exp( + -0.5 * np.sum(error_norm_sq / self.model_noise_cov_scalar, axis=0) + ) + + return sample_evidence_estimates.reshape(1, -1) + + def get_fixed_parameter_id(self, parameter_pair): + """Function returns the idx of fixed parameters given the parameter pair""" + num_parameter = parameter_pair.shape[0] + parameter_list = np.arange(self.num_parameters) + conditions = [] + for ii in range(num_parameter): + conditions.append(parameter_pair[ii] != parameter_list) + idx = np.prod(np.array(conditions), axis=0) == 1 + return parameter_list[idx] + + def get_sample_parameter_id(self, parameter_pair): + """Function returns the idx of sample parameters given the parameter pair""" + return parameter_pair + + def get_sample_centered_mean(self, sample_parameter_extracted_sample): + """Function constructs the mean around the sample parameter""" + return sample_parameter_extracted_sample.reshape(-1, 1) + + def compute_gaussian_prob(self, mean, cov, samples): + """Function evaluates the proabability of the samples given the normal distribution""" + d = mean.shape[0] + error = mean - samples + exp_term = np.exp(-0.5 * np.diag(error.T @ np.linalg.solve(cov, error))) + pre_exp_term = 1 / (((2 * np.pi) ** (d / 2)) * (np.linalg.det(cov) ** (0.5))) + return pre_exp_term * exp_term + + def compute_importance_sampling_weights( + self, target_density_estimator, proposal_density_estimator, eval_points + ): + """Function computes the weights for importace sampling""" + target_denstiy = target_density_estimator(eval_points) + proposal_density = proposal_density_estimator(eval_points) + return target_denstiy / proposal_density diff --git a/information_metrics/test_SobolIndex.py b/information_metrics/test_SobolIndex.py new file mode 100644 index 0000000..25de51a --- /dev/null +++ b/information_metrics/test_SobolIndex.py @@ -0,0 +1,46 @@ +import numpy as np +from SobolIndex import SobolIndex + + +def comp_sobol_indices(): + """Compute the Sobol indices for the linear gaussian model""" + num_samples = 100 + # Define the model + + def forward_model(theta): + return comp_linear_gaussian_prediciton(num_samples, theta) + + sobol_index = SobolIndex( + forward_model=forward_model, + prior_mean=np.ones(3).reshape(-1, 1), + prior_cov=np.eye(3), + global_num_outer_samples=1200, + global_num_inner_samples=1200, + model_noise_cov_scalar=0, + data_shape=(1, num_samples), + write_log_file=True, + save_path="./Output/SobolIndex", + ) + + sobol_index.comp_first_order_sobol_indices() + sobol_index.comp_total_effect_sobol_indices() + + +def comp_linear_gaussian_prediciton(num_samples, theta): + """Compute the prediction of a linear Gaussian model.""" + x = np.linspace(0, 1, num_samples) + + # Unequal contribution of the parameters + A = np.vander(x, len(theta), increasing=True) + + # Equal contribution of each parameter + # A = np.ones((x.shape[0], len(theta))) + return np.dot(A, theta).reshape(1, -1) + + +def main(): + comp_sobol_indices() + + +if __name__ == "__main__": + main() diff --git a/mcmc/mcmc.py b/mcmc/mcmc.py new file mode 100644 index 0000000..1f60c9a --- /dev/null +++ b/mcmc/mcmc.py @@ -0,0 +1,417 @@ +""" +Developer: Sahil Bhola +Date: 11-17-2022 +Description: This script implements the Metropolis-Hastings algorithm +in an objective oriented manner. +""" +import numpy as np +from mpi4py import MPI +from mcmc_utils import * +import time +import matplotlib.pyplot as plt + +comm = MPI.COMM_WORLD +rank = comm.Get_rank() +size = comm.Get_size() + + +class mcmc: + def __init__( + self, + initial_sample, + target_log_pdf_evaluator, + num_samples, + prop_log_pdf_evaluator, + prop_log_pdf_sampler, + ): + + self.initial_sample = initial_sample + self.target_log_pdf_evaluator = target_log_pdf_evaluator + self.prop_log_pdf_evaluator = prop_log_pdf_evaluator + self.prop_log_pdf_sampler = prop_log_pdf_sampler + self.num_samples = num_samples + + self.dim = len(initial_sample) + + self.samples = np.zeros((num_samples, self.dim)) + self.samples[0, :] = self.initial_sample + + self.accepted_samples = 0 + + def compute_acceptance_ratio(self): + """Compute the acceptance ratio""" + return self.accepted_samples / self.num_samples + + def compute_acceptance_probability( + self, + current_sample, + proposed_sample, + target_log_pdf_at_current, + target_log_pdf_at_proposed, + ): + """Compute the acceptance probability""" + prop_reverse = self.evaluate_proposal_log_pdf(proposed_sample, current_sample) + prop_forward = self.evaluate_proposal_log_pdf(current_sample, proposed_sample) + check = ( + target_log_pdf_at_proposed + + prop_reverse + - target_log_pdf_at_current + - prop_forward + ) + if check < 0: + return np.exp(check) + else: + return 1 + + def evaluate_target_log_pdf(self, sample): + """Evaluate the target log pdf""" + target_log_pdf = self.target_log_pdf_evaluator(sample) + return target_log_pdf.item() + + def compute_mcmc_samples(self, verbose=False): + """Compute the samples""" + # Evaluate the target log pdf at the initial sample + target_log_pdf_at_current = self.evaluate_target_log_pdf(self.initial_sample) + + for ii in range(1, self.num_samples): + + # Sample from the proposal pdf + proposed_sample = self.sample_proposal_pdf(self.samples[ii - 1, :]) + + # Evaluate the target log pdf at the proposal sample + target_log_pdf_at_proposed = self.evaluate_target_log_pdf(proposed_sample) + + # Compute the acceptance probaility + acceptance_propability = self.compute_acceptance_probability( + self.samples[ii - 1, :], + proposed_sample, + target_log_pdf_at_current, + target_log_pdf_at_proposed, + ) + + # Accept or reject the sample + if np.random.rand() < acceptance_propability: + self.samples[ii, :] = proposed_sample + self.accepted_samples += 1 + else: + self.samples[ii, :] = self.samples[ii - 1, :] + + if rank == 0 and verbose: + acceptance_propability = self.compute_acceptance_ratio() + print( + "Iteration: ", + ii, + "Acceptance probability: ", + acceptance_propability, + flush=True, + ) + + def sample_proposal_pdf(self, current_sample): + """Sample from the proposal pdf""" + proposed_sample = self.prop_log_pdf_sampler(current_sample) + return proposed_sample + + def evaluate_proposal_log_pdf(self, current_sample, proposed_sample): + proposal_log_pdf = self.prop_log_pdf_evaluator(current_sample, proposed_sample) + return proposal_log_pdf + + +class metropolis_hastings(mcmc): + def __init__( + self, + initial_sample, + target_log_pdf_evaluator, + num_samples, + sd=None, + initial_cov=None, + ): + + dim = len(initial_sample) + if sd is None: + self.sd = 2.4 / np.sqrt(dim) + else: + self.sd = sd + + if initial_cov is None: + self.proposal_cov = (self.sd) * np.eye(dim) + else: + self.proposal_cov = initial_cov + + assert np.all( + np.linalg.eigvals(self.proposal_cov) > 0 + ), "The covariance matrix is not positive definite" + + prop_log_pdf_evaluator = lambda x, y: self.evaluate_random_walk_pdf(x, y) + prop_log_pdf_sampler = lambda x: self.sample_random_walk_pdf(x) + + # Initialize the parent class + super().__init__( + initial_sample, + target_log_pdf_evaluator, + num_samples, + prop_log_pdf_evaluator, + prop_log_pdf_sampler, + ) + + def evaluate_random_walk_pdf(self, x, y): + """evaluate the random walk pdf""" + proposal_log_pdf = evaluate_gaussian_log_pdf( + x[:, None], y[:, None], self.proposal_cov + ) + return proposal_log_pdf.item() + + def sample_random_walk_pdf(self, x): + """Sample from the random walk pdf""" + proposed_sample = sample_gaussian(x.reshape(-1, 1), self.proposal_cov, 1) + return proposed_sample.reshape(-1) + + +class adaptive_metropolis_hastings(mcmc): + def __init__( + self, + initial_sample, + target_log_pdf_evaluator, + num_samples, + adapt_sample_threshold=100, + sd=None, + initial_cov=None, + eps=1e-8, + reset_frequency=100, + ): + + # Assert statements + assert ( + adapt_sample_threshold < num_samples + ), "The adaptation sample threshold must be less than the total number of samples" + + # Initialize the child class + + dim = len(initial_sample) + self.eps = eps + if sd is None: + self.sd = 2.4 / np.sqrt(dim) + else: + self.sd = sd + + if initial_cov is None: + self.proposal_cov = (self.sd) * np.eye(dim) + else: + self.proposal_cov = initial_cov + + assert np.all( + np.linalg.eigvals(self.proposal_cov) > 0 + ), "The initial covariance matrix must be positive definite" + assert self.proposal_cov.shape == ( + dim, + dim, + ), "The initial covariance matrix must be a square matrix" + + self.adapt_sample_threshold = adapt_sample_threshold + + self.initial_cov = self.proposal_cov + + self.sample_count = 0 + self.k = 0 + + self.old_mean = initial_sample + self.new_mean = initial_sample + + self.old_cov = self.proposal_cov + self.new_cov = np.nan * np.ones((dim, dim)) + + self.reset_frequency = reset_frequency + + prop_log_pdf_evaluator = lambda x, y: self.evaluate_adaptive_random_walk_pdf( + x, y + ) + prop_log_pdf_sampler = lambda x: self.sample_adaptive_random_walk_pdf(x) + + # Initialize the parent class + super().__init__( + initial_sample, + target_log_pdf_evaluator, + num_samples, + prop_log_pdf_evaluator, + prop_log_pdf_sampler, + ) + + def evaluate_adaptive_random_walk_pdf(self, x, y): + """Evaluate the adaptive random walk pdf""" + proposal_log_pdf = evaluate_gaussian_log_pdf( + x[:, None], y[:, None], self.proposal_cov + ) + return proposal_log_pdf.item() + + def sample_adaptive_random_walk_pdf(self, x): + """Sample from the adaptive random walk pdf""" + + assert ( + np.prod(x.shape) == self.dim + ), "The current sample must be a vector of length dim" + + # Update K + self.k = self.sample_count + + # Update the sample count + self.increase_count() + + # Update the old mean + self.update_old_mean() + + # Recursively update the new mean + self.update_recursive_mean(x) + + # Recursively update the new covariance + self.update_recursive_cov(x) + + if self.sample_count > self.adapt_sample_threshold: + + # Update the old covariance + self.update_old_cov() + + # Update the proposal covariance + self.update_proposal_covariance() + + proposed_sample = sample_gaussian(x.reshape(-1, 1), self.proposal_cov, 1) + return proposed_sample.reshape(-1) + + def update_proposal_covariance(self): + """Function updates the covariance matrix via the samples""" + self.proposal_cov = self.new_cov + + def increase_count(self): + """Function increases the sample count""" + self.sample_count += 1 + + def update_recursive_mean(self, x): + """Function updates the recursive mean""" + # Update new mean + self.new_mean = (1 / (self.k + 1)) * x + (self.k / (self.k + 1)) * self.old_mean + + assert self.new_mean.shape == x.shape, "The new mean shape is not correct" + + def update_recursive_cov(self, x): + """Function updates the recursive covariance""" + + multiplier = ( + (self.eps * np.eye(self.dim)) + + (self.k * np.outer(self.old_mean, self.old_mean)) + - (self.k + 1) * np.outer(self.new_mean, self.new_mean) + + (np.outer(x, x)) + ) + + if self.sample_count > self.adapt_sample_threshold: + + if self.sample_count % self.reset_frequency == 0: + self.reset_mean() + self.reset_cov() + + # Update new covariance matrix + self.new_cov = ((self.k - 1) / self.k) * self.old_cov + ( + self.sd / self.k + ) * multiplier + + assert self.new_cov.shape == ( + x.shape[0], + x.shape[0], + ), "The new covariance shape is not correct" + assert np.all( + np.linalg.eigvals(self.new_cov) > 0 + ), "The new covariance matrix is not positive definite" + + elif self.sample_count > 1: + + # Update the old covariance matrix + self.old_cov = ((self.k - 1) / self.k) * self.proposal_cov + ( + self.sd / self.k + ) * multiplier + + else: + + pass + + def update_old_mean(self): + """Function updates the old mean""" + self.old_mean = self.new_mean + + def update_old_cov(self): + """Function updates the old covariance""" + self.old_cov = self.new_cov + + def reset_mean(self): + """Function resets the mean""" + self.old_mean = self.initial_sample + self.new_mean = self.initial_sample + + def reset_cov(self): + """Function resets the covariance""" + self.old_cov = self.initial_cov + self.new_cov = np.nan * np.ones((self.dim, self.dim)) + + +def test_gaussian(sample): + """Function evaluates the log normal pdf + :param sample: The sample to evaluate (D, 1) + :return: The log pdf value + """ + mean = np.arange(1, 3).reshape(-1, 1) + cov = build_cov_mat(1.0, 1.0, 0.5) # std, std, correlation + error = sample - mean + inner_solve = np.linalg.solve(cov, error) + inner_exponential_term = error.T @ inner_solve + return -0.5 * inner_exponential_term + + +def test_banana(sample): + """Function evaluates the log normal pdf + :param sample: The sample to evaluate (D, 1) + :return: The log pdf value + """ + a = 1.0 + b = 100.0 + x = sample[0, 0] + y = sample[1, 0] + logpdf = (a - x) ** 2 + b * (y - x**2) ** 2 + return -logpdf + + +def main(): + target_log_pdf = lambda x: test_gaussian(x.reshape(-1, 1)) + # target_log_pdf = lambda x: test_banana(x.reshape(-1, 1)) + num_samples = 10000 + initial_sample = np.random.randn(2) + + mcmc_sampler = adaptive_metropolis_hastings( + initial_sample=initial_sample, + target_log_pdf_evaluator=target_log_pdf, + num_samples=num_samples, + adapt_sample_threshold=1000, + reset_frequency=50, + ) + + # Compute the samples + mcmc_sampler.compute_mcmc_samples(verbose=True) + print(mcmc_sampler.proposal_cov) + + # Compute acceptance rate + ar = mcmc_sampler.compute_acceptance_ratio() + print("Acceptance rate: ", ar) + + # Compute the burn in samples + burned_samples = sub_sample_data(mcmc_sampler.samples, frac_burn=0.5, frac_use=0.7) + + # Plot the samples + fig, axs = plot_chains(burned_samples, title="Adaptive Metropolis Hastings") + plt.show() + + # Plot samples from posterior + fig, axs, gs = scatter_matrix( + [burned_samples], labels=[r"$x_1$", r"$x_2$"], hist_plot=False, gamma=0.4 + ) + + fig.set_size_inches(7, 7) + plt.show() + + +if __name__ == "__main__": + main() diff --git a/mcmc/mcmc_utils.py b/mcmc/mcmc_utils.py new file mode 100644 index 0000000..2aa88e7 --- /dev/null +++ b/mcmc/mcmc_utils.py @@ -0,0 +1,326 @@ +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import cm +from matplotlib.gridspec import GridSpec +import matplotlib.colors as mcolors + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=20) +plt.rc("axes", labelsize=20, titlepad=20) +plt.rc("lines", linewidth=3) + + +def sample_gaussian(mean, cov, n): + """Function samples from a Gaussian distribution + :param mean: mean of the Gaussian distribution + :param cov: covariance matrix of the Gaussian distribution + :param n: number of samples to draw + :return: samples from the Gaussian distribution + """ + assert np.allclose(cov, cov.T), "Covariance matrix must be symmetric" + assert np.all( + np.linalg.eigvals(cov) > 0 + ), "Covariance matrix must be positive definite" + chol = np.linalg.cholesky(cov + 1e-8 * np.eye(cov.shape[0])) + return mean + np.dot(chol, np.random.randn(mean.shape[0], n)) + + +def evaluate_gaussian_log_pdf(sample, mean, cov): + """Function evaluates the log normal pdf + :param sample: sample to evaluate the log pdf at + :param mean: mean of the Gaussian distribution + :param cov: covariance matrix of the Gaussian distribution + :return: log pdf of the sample + """ + assert np.allclose(cov, cov.T), "Covariance matrix must be symmetric" + assert np.all( + np.linalg.eigvals(cov) > 0 + ), "Covariance matrix must be positive definite" + error = sample - mean + inner_solve = np.linalg.solve(cov, error) + log_pdf = -0.5 * np.dot(error.T, inner_solve) + return log_pdf + + +def build_cov_mat(std1, std2, rho): + """Build a covariance matrix for a bivariate Gaussian distribution + :param std1: standard deviation of the first variable + :param std2: standard deviation of the second variable + :param rho: correlation coefficient + :return: covariance matrix + """ + assert std1 > 0, "standard deviation must be greater than 0" + assert std2 > 0, "standard deviation must be greater than 0" + assert np.abs(rho) <= 1, "correlation must be betwene -1 and 1" + return np.array([[std1**2, rho * std1 * std2], [rho * std1 * std2, std2**2]]) + + +def sub_sample_data(samples, frac_burn=0.2, frac_use=0.7): + """Subsample data by burning off the front fraction and using another fraction + Written by: Alex Gorodedsky + :param samples: samples to subsample + :param frac_burn: fraction of samples to burn off + :param frac_use: fraction of samples to use + :return: subsampled samples + + """ + num_samples = samples.shape[0] + inds = np.arange(num_samples, dtype=np.int) + start = int(frac_burn * num_samples) + inds = inds[start:] + num_samples = num_samples - start + step = int(num_samples / (num_samples * frac_use)) + inds2 = np.arange(0, num_samples, step) + inds = inds[inds2] + return samples[inds, :] + + +def scatter_matrix( + samples, # list of chains + mins=None, + maxs=None, + upper_right=None, + specials=None, + hist_plot=True, # if false then only data + nbins=200, + gamma=0.5, + labels=None, +): + + """Scatter matrix hist + Written by: Alex Gorodedsky + :param samples: list of samples + """ + + nchains = len(samples) + dim = samples[0].shape[1] + + if mins is None: + mins = np.zeros((dim)) + maxs = np.zeros((dim)) + + for ii in range(dim): + # print("ii = ", ii) + mm = [np.quantile(samp[:, ii], 0.01, axis=0) for samp in samples] + # print("\t mins = ", mm) + mins[ii] = np.min(mm) + mm = [np.quantile(samp[:, ii], 0.99, axis=0) for samp in samples] + # print("\t maxs = ", mm) + maxs[ii] = np.max(mm) + + # if specials is not None: + # if isinstance(specials, list): + # minspec = np.min([spec["vals"][ii] for spec in specials]) + # maxspec = np.max([spec["vals"][ii] for spec in specials]) + # else: + # minspec = spec["vals"][ii] + # maxspec = spec["vals"][ii] + # mins[ii] = min(mins[ii], minspec) + # maxs[ii] = max(maxs[ii], maxspec) + + deltas = (maxs - mins) / 10.0 + use_mins = mins - deltas + use_maxs = maxs + deltas + + # cmuse = cm.get_cmap(name="tab10") + + # fig = plt.figure(constrained_layout=True) + fig = plt.figure() + if upper_right is None: + gs = GridSpec(dim, dim, figure=fig) + axs = [None] * dim * dim + start = 0 + end = dim + l = dim + else: + gs = GridSpec(dim + 1, dim + 1, figure=fig) + axs = [None] * (dim + 1) * (dim + 1) + start = 1 + end = dim + 1 + l = dim + 1 + + # print("mins = ", mins) + # print("maxs = ", maxs) + + for ii in range(dim): + # print("ii = ", ii) + axs[ii] = fig.add_subplot(gs[ii + start, ii]) + ax = axs[ii] + + # Turn everythinng off + if ii < dim - 1: + ax.tick_params(axis="x", bottom=False, top=False, labelbottom=False) + else: + ax.tick_params(axis="x", bottom=True, top=False, labelbottom=True) + if labels: + ax.set_xlabel(labels[ii]) + + ax.tick_params(axis="y", left=False, right=False, labelleft=False) + ax.set_frame_on(False) + + sampii = np.concatenate([samples[kk][:, ii] for kk in range(nchains)]) + # for kk in range(nchains): + # print("sampii == ", sampii) + ax.hist( + sampii, + # ax.hist(samples[kk][:, ii], + bins="sturges", + density=True, + edgecolor="black", + stacked=True, + range=(use_mins[ii], use_maxs[ii]), + alpha=0.4, + ) + if specials is not None: + for special in specials: + if special["vals"][ii] is not None: + # ax.axvline(special[ii], color='red', lw=2) + if "color" in special: + ax.axvline(special["vals"][ii], color=special["color"], lw=2) + else: + ax.axvline(special["vals"][ii], lw=2) + + ax.set_xlim((use_mins[ii] - 1e-10, use_maxs[ii] + 1e-10)) + + for jj in range(ii + 1, dim): + # print("jj = ", jj) + axs[jj * l + ii] = fig.add_subplot(gs[jj + start, ii]) + ax = axs[jj * l + ii] + + if jj < dim - 1: + ax.tick_params(axis="x", bottom=False, top=False, labelbottom=False) + else: + ax.tick_params(axis="x", bottom=True, top=False, labelbottom=True) + if labels: + ax.set_xlabel(labels[ii]) + if ii > 0: + ax.tick_params(axis="y", left=False, right=False, labelleft=False) + else: + ax.tick_params(axis="y", left=True, right=False, labelleft=True) + if labels: + ax.set_ylabel(labels[jj]) + + ax.set_frame_on(True) + + for kk in range(nchains): + if hist_plot is True: + ax.hist2d( + samples[kk][:, ii], + samples[kk][:, jj], + bins=nbins, + norm=mcolors.PowerNorm(gamma), + density=True, + ) + else: + ax.plot( + samples[kk][:, ii], samples[kk][:, jj], "o", ms=1, alpha=gamma + ) + + # ax.hist2d(samples[kk][:, ii], samples[kk][:, jj], bins=nbins) + + if specials is not None: + for special in specials: + if "color" in special: + ax.plot( + special["vals"][ii], + special["vals"][jj], + "x", + color=special["color"], + ms=2, + mew=2, + ) + else: + ax.plot( + special["vals"][ii], special["vals"][jj], "x", ms=2, mew=2 + ) + + ax.set_xlim((use_mins[ii], use_maxs[ii])) + ax.set_ylim((use_mins[jj] - 1e-10, use_maxs[jj] + 1e-10)) + + plt.tight_layout(pad=0.01) + if upper_right is not None: + size_ur = int(dim / 2) + + name = upper_right["name"] + vals = upper_right["vals"] + if "log_transform" in upper_right: + log_transform = upper_right["log_transform"] + else: + log_transform = None + ax = fig.add_subplot( + gs[0 : int(dim / 2), size_ur + 1 : size_ur + int(dim / 2) + 1] + ) + + lb = np.min([np.quantile(val, 0.01) for val in vals]) + ub = np.max([np.quantile(val, 0.99) for val in vals]) + for kk in range(nchains): + if log_transform is not None: + pv = np.log10(vals[kk]) + ra = (np.log10(lb), np.log10(ub)) + else: + pv = vals[kk] + ra = (lb, ub) + ax.hist( + pv, + density=True, + range=ra, + edgecolor="black", + stacked=True, + bins="auto", + alpha=0.2, + ) + ax.tick_params(axis="x", bottom="both", top=False, labelbottom=True) + ax.tick_params(axis="y", left="both", right=False, labelleft=False) + ax.set_frame_on(True) + ax.set_xlabel(name) + plt.subplots_adjust(left=0.15, right=0.95) + return fig, axs, gs + + +def plot_chains(samples, title=None): + """Function plots the MCMC chains + :param samples: samples from the MCMC, (Num of samples, Num of parameters) + :return: + """ + nchains, nparameters = samples.shape + fig, axs = plt.subplots(nparameters, 1, figsize=(10, 10)) + for ii in range(nparameters): + axs[ii].plot(samples[:, ii], color="k") + axs[ii].set_ylabel(r"$\Theta_{{{}}}$".format(ii + 1)) + if title is not None: + fig.suptitle(title) + return fig, axs + + +def autocorrelation(samples, maxlag=100, step=1): + """Compute the correlation of a set of samples + Written by: Alex Gorodedsky + :param samples: samples from the MCMC, (Num of samples, Num of parameters) + :param maxlag: maximum lag to compute the correlation + :param step: step to compute the correlation + :return: lags, correlation + """ + + # Get the shapes + ndim = samples.shape[1] + nsamples = samples.shape[0] + + # Compute the mean + mean = np.mean(samples, axis=0) + + # Compute the denominator, which is variance + denominator = np.zeros((ndim)) + for ii in range(nsamples): + denominator = denominator + (samples[ii, :] - mean) ** 2 + + lags = np.arange(0, maxlag, step) + autos = np.zeros((len(lags), ndim)) + for zz, lag in enumerate(lags): + autos[zz, :] = np.zeros((ndim)) + # compute the covariance between all samples *lag apart* + for ii in range(nsamples - lag): + autos[zz, :] = autos[zz, :] + (samples[ii, :] - mean) * ( + samples[ii + lag, :] - mean + ) + autos[zz, :] = autos[zz, :] / denominator + return lags, autos diff --git a/quadrature/quadrature.py b/quadrature/quadrature.py new file mode 100644 index 0000000..79c8787 --- /dev/null +++ b/quadrature/quadrature.py @@ -0,0 +1,331 @@ +import numpy as np +import matplotlib.pyplot as plt +from scipy.sparse import diags + +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=20) + + +class unscented_quadrature: + def __init__(self, mean, cov, integrand, alpha=1, beta=0, kappa=0): + self.mean = mean + self.cov = cov + self.alpha = 1 + self.beta = beta + self.kappa = kappa + self.integrand = integrand + + def compute_quadrature_points(self): + """Function commputs the unscented quadrature points + Ouputs: + quad_points: quadrature points + + """ + # Definitions + dim = self.cov.shape[0] + quad_points = np.zeros((dim, 2 * dim + 1)) + lam = self.alpha * self.alpha * (dim + self.kappa) - dim + + L = np.linalg.cholesky(self.cov + np.eye(dim) * 1e-8) + + # Quadrature points + quad_points[:, 0] = self.mean.ravel() + for ipoint in range(1, dim + 1): + quad_points[:, ipoint] = ( + self.mean.ravel() + np.sqrt(dim + lam) * L[:, ipoint - 1] + ) + quad_points[:, ipoint + dim] = ( + self.mean.ravel() - np.sqrt(dim + lam) * L[:, ipoint - 1] + ) + + return quad_points + + def compute_weights(self): + """Function computs the weights""" + dim = self.cov.shape[0] + lam = self.alpha * self.alpha * (dim + self.kappa) - dim + + W0m = lam / (dim + lam) + W0c = lam / (dim + lam) + (1 - self.alpha * self.alpha + self.beta) + Wim = 1 / (2 * (dim + lam)) + Wic = 1 / (2 * (dim + lam)) + return (W0m, Wim, W0c, Wic) + + def compute_integeral(self, importance_sampling_weights=None): + """Function comptues the integral""" + # Compute the quadrature points + quadrature_points = self.compute_quadrature_points() + # Compute the weights + weights = self.compute_weights() + # Compute integrand at the quadrature points + model_prediction = self.integrand(quadrature_points) + # Compute integrand val + if importance_sampling_weights is None: + integrand = model_prediction.copy() + else: + integrand = model_prediction * importance_sampling_weights + + integral_mean = weights[0] * integrand[:, 0] + integral_mean += np.sum(weights[1] * integrand[:, 1:], axis=1) + + def compute_error(x): + return (x - integral_mean).reshape(-1, 1) + + def compute_outer(x): + error = compute_error(x) + return error @ error.T + + integral_cov = weights[2] * compute_outer(integrand[:, 0]) + for ii in range(1, integrand.shape[1]): + integral_cov += weights[-1] * compute_outer(integrand[:, ii]) + + return integral_mean, integral_cov + + +class gauss_hermite_quadrature: + def __init__(self, mean, cov, integrand, num_points): + self.mean = mean + self.cov = cov + self.integrand = integrand + self.num_points = num_points + + def compute_quadrature_points_and_weights(self): + """Function comptues the quadrature points (unrotated) and weights using the Golub-Welsch Algorithm + Adapted from : Dr. Alex gorodedsky notes on Inference, Estimation, and Learning + NOTE: Uses hermite polynomials""" + alpha = np.zeros(self.num_points) + beta = np.sqrt(np.arange(1, self.num_points)) + diagonals = [alpha, beta, beta] + J = diags(diagonals, [0, 1, -1]).toarray() + quad_points, evec = np.linalg.eig(J) + weights = evec[0, :] ** 2 + + unrotated_points, weights = self.tensorize_quadrature_points_and_weights( + quad_points=quad_points, weights=weights + ) + quad_points = self.rotate_points(unrotated_points=unrotated_points) + + return quad_points, weights + + def tensorize(self, vector): + """Function tensorizes an input vector""" + n1d = vector.shape[0] + twodnodes = np.zeros((n1d * n1d, 2)) + ind = 0 + for ii in range(n1d): + for jj in range(n1d): + twodnodes[ind, :] = np.array([vector[ii], vector[jj]]) + ind += 1 + return twodnodes + + def tensorize_quadrature_points_and_weights(self, quad_points, weights): + """Tensorize the quadrature points and weights, essentially creating a meshgrid""" + d = self.mean.shape[0] + if d == 1: + quad_points_T = quad_points.reshape(1, -1) + weights_T = weights + else: + assert d == 2, "Tensorization net implelented for more than 2 dimensions" + quad_points_T = self.tensorize(quad_points).T + weights_T = self.tensorize(weights) + weights_T = np.prod(weights_T, axis=1) + + return quad_points_T, weights_T + + def rotate_points(self, unrotated_points): + """Function rotates the points""" + d = self.mean.shape[0] + L = np.linalg.cholesky(self.cov + np.eye(d) * 1e-8) + rotated_points = np.zeros(unrotated_points.shape) + for ipoint in range(rotated_points.shape[1]): + rotated_points[:, ipoint] = self.mean.ravel() + np.dot( + L, unrotated_points[:, ipoint] + ) + return rotated_points + + def compute_integeral(self, importance_sampling_weights=None): + """Function comptues the intergral""" + # Compute the quadrature points and weights + quadrature_points, weights = self.compute_quadrature_points_and_weights() + # Compute integrand at the quadrature points + model_prediction = self.integrand(quadrature_points) + # Compute integrand val + if importance_sampling_weights is None: + integrand = model_prediction.copy() + else: + integrand = model_prediction * importance_sampling_weights + # Compute the integral + integral = np.array( + [integrand[:, ii] * weights[ii] for ii in range(weights.shape[0])] + ) + return np.sum(integral, axis=0) + + +def test_function(x): + """test function to test the quadrature rules""" + num_samples = x.shape[1] + output = np.zeros((2, num_samples)) + output[0, :] = x[0, :] * np.tanh(x[0, :] * x[1, :]) + output[1, :] = np.sqrt(x[1, :] ** 2) + return output + + +def sample_gaussian(mean, cov, num_samples): + """Function samples the gaussian""" + d = cov.shape[0] + L = np.linalg.cholesky(cov + np.eye(d) * 1e-8) + noise = L @ np.random.randn(d * num_samples).reshape(d, num_samples) + return mean + noise + + +def plot_points( + num_samples, gaussian_mean, gaussian_cov, integrand_function, view_points=None +): + """Function plots the points""" + gaussian_samples = sample_gaussian( + mean=gaussian_mean, cov=gaussian_cov, num_samples=num_samples + ) + + if view_points is not None: + model_evaluated_at_view_points = integrand_function(x=view_points) + + # Compute the model prediction + model_samples = integrand_function(x=gaussian_samples) + + fig, axs = plt.subplots(1, 2, figsize=(10, 5)) + axs[0].scatter(gaussian_samples[0, :], gaussian_samples[1, :], color="red", s=5) + axs[1].scatter(model_samples[0, :], model_samples[1, :], color="red", s=5) + if view_points is not None: + axs[0].scatter(view_points[0, :], view_points[1, :], color="k", s=60) + axs[1].scatter( + model_evaluated_at_view_points[0, :], + model_evaluated_at_view_points[1, :], + color="k", + s=60, + ) + + axs[0].set_title(r"Input space") + axs[0].set_xlabel(r"$X_1$") + axs[0].set_ylabel(r"$X_2$") + axs[1].set_title(r"Output space") + axs[1].set_xlabel(r"$Y_1$") + axs[1].set_ylabel(r"$Y_2$") + plt.tight_layout() + plt.show() + + +def compute_gaussian_prob(mean, cov, samples): + """Function evaluates the proabability of the samples given the normal distribution""" + d = mean.shape[0] + error = mean - samples + exp_term = np.exp(-0.5 * np.diag(error.T @ np.linalg.solve(cov, error))) + pre_exp_term = 1 / (((2 * np.pi) ** (d / 2)) * (np.linalg.det(cov) ** (0.5))) + return pre_exp_term * exp_term + + +def reference_cov(): + "test gaussian" + std1 = 1 + std2 = 2 + rho = 0.9 + cov = np.array([[std1 * std1, rho * std1 * std2], [rho * std1 * std2, std2 * std2]]) + return cov + + +def importance_sampling_cov(): + factor = 0.9 + std1 = 1 * factor + std2 = 2 * factor + rho = 0.9 + cov = np.array([[std1 * std1, rho * std1 * std2], [rho * std1 * std2, std2 * std2]]) + return cov + + +def compute_importance_sampling_weights(eval_points): + """Function comptues the importance sampling weights""" + + def target_density_estimator(eval_points): + proabability = compute_gaussian_prob( + mean=np.zeros((2, 1)), cov=reference_cov(), samples=eval_points + ) + return proabability + + def proposal_density_estimator(eval_points): + proabability = compute_gaussian_prob( + mean=np.zeros((2, 1)), cov=importance_sampling_cov(), samples=eval_points + ) + return proabability + + target_denstiy = target_density_estimator(eval_points) + proposal_density = proposal_density_estimator(eval_points) + + return target_denstiy / proposal_density + + +def main(): + gaussian_mean = np.zeros((2, 1)) + gaussian_cov = reference_cov() + proposal_cov = importance_sampling_cov() + + # """ Without importance sampling (Unscented quadrature) + unscented_quad = unscented_quadrature( + mean=gaussian_mean, + cov=gaussian_cov, + integrand=test_function, + ) + mean, cov = unscented_quad.compute_integeral() + print("Without importance sampling (Unscented) : {}".format(mean)) + # """ + + # """ With Importance sampling (Unscented quadrature) + unscented_quad = unscented_quadrature( + mean=gaussian_mean, + cov=proposal_cov, + integrand=test_function, + ) + + quadrature_points = unscented_quad.compute_quadrature_points() + importance_sampling_weights = compute_importance_sampling_weights( + eval_points=quadrature_points + ) + mean, cov = unscented_quad.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + + print("With importance sampling (Unscented) : {}".format(mean)) + # """ + + print("---------------------------------") + + # """ Without importance sampling (Gaussian) + gh = gauss_hermite_quadrature( + mean=gaussian_mean, cov=gaussian_cov, integrand=test_function, num_points=3 + ) + gh_int = gh.compute_integeral() + print("Without importance sampling (Gaussian) : {}".format(gh_int)) + # """ + + # """ With importance sampling (Gaussian) + gh = gauss_hermite_quadrature( + mean=gaussian_mean, cov=gaussian_cov, integrand=test_function, num_points=3 + ) + + quadrature_points, _ = gh.compute_quadrature_points_and_weights() + + importance_sampling_weights = compute_importance_sampling_weights( + eval_points=quadrature_points + ) + + gh_int = gh.compute_integeral( + importance_sampling_weights=importance_sampling_weights + ) + print("With importance sampling (Gaussian) : {}".format(gh_int)) + # """ + + # # gh_points, gh_weights = gh.compute_quadrature_points_and_weights() + + # # plot_points(1000, gaussian_mean=gaussian_mean, gaussian_cov=gaussian_cov, integrand_function=test_function, view_points=gh_points) + + +if __name__ == "__main__": + main() diff --git a/readme.md b/readme.md index acd6d05..572da75 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,3 @@ # Generalizable Information-theoretic Modeling The goal is to develop generalizable models by exploring Information-theoretic measures such as Mutual-Information, R-D theory, Information bottleneck, etc. We aim to use these measures to combine difference sources of information such as experiments, numerical simulations, and emerical modeling. - diff --git a/scripts/AssembleSobolIndex.py b/scripts/AssembleSobolIndex.py new file mode 100644 index 0000000..f567f14 --- /dev/null +++ b/scripts/AssembleSobolIndex.py @@ -0,0 +1,112 @@ +import numpy as np +import os + +# Begin user input +num_parameters = None +specific_parameters = [1] # 1 indexed +num_ranks = 180 +num_global_outer_samples = 9000 +data_shape = (1, 4) +compute_first_effect_sobol = True +compute_total_effect_sobol = False +# End user input + +if num_parameters is None: + assert specific_parameters is not None, "Provide a specific parameter to evaluate" + +if specific_parameters is None: + assert num_parameters is not None, "Provide total number of parameters" + + +batch_size = num_global_outer_samples // num_ranks + +# Read the sobol denominator +sobol_denominator = np.load("sobol_denominator.npy") + +# Compute the first order sobol indices +if num_parameters is None: + sobol_numerator = np.zeros((data_shape + (len(specific_parameters),))) + total_parameters = len(specific_parameters) + parameter_list = np.array(specific_parameters) - 1 +else: + sobol_numerator = np.zeros((data_shape + (num_parameters,))) + total_parameters = num_parameters + parameter_list = np.arange(num_parameters) + +unavailable_ranks = [] +if compute_first_effect_sobol: + for ii in range(total_parameters): + + iparam = parameter_list[ii] + unavailable_ranks = [] + + # Check if file is available + for irank in range(num_ranks): + file_name = "first_order_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, irank + ) + + data_ = os.path.exists(file_name) + if os.path.exists(file_name) is False: + unavailable_ranks.append(irank) + + total_available_outer_samples = num_global_outer_samples - ( + len(unavailable_ranks) * batch_size + ) + inner_expectation = np.zeros((data_shape + (total_available_outer_samples,))) + print("unavailable ranks : {}".format(unavailable_ranks)) + available_ranks = np.delete(np.arange(num_ranks), unavailable_ranks) + + for jj, irank in enumerate(available_ranks): + file_name = "first_order_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, irank + ) + data = np.load(file_name) + + inner_expectation[:, :, jj * batch_size : (jj + 1) * batch_size] = data + + sobol_numerator[:, :, ii] = np.var(inner_expectation, axis=2) + + ratio = sobol_numerator / sobol_denominator[:, :, np.newaxis] + sobol_index = np.mean(ratio, axis=(0, 1)) + print("First order sobol index: {}".format(sobol_index)) + +# Compute the total effect sobol indices +if compute_total_effect_sobol: + unavailable_ranks = [] + + for ii in range(total_parameters): + + iparam = parameter_list[ii] + unavailable_ranks = [] + + # Check if file is available + for irank in range(num_ranks): + file_name = "total_effect_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, irank + ) + + data_ = os.path.exists(file_name) + if os.path.exists(file_name) is False: + unavailable_ranks.append(irank) + + total_available_outer_samples = num_global_outer_samples - ( + len(unavailable_ranks) * batch_size + ) + inner_expectation = np.zeros((data_shape + (total_available_outer_samples,))) + + available_ranks = np.delete(np.arange(num_ranks), unavailable_ranks) + + for ii, irank in enumerate(available_ranks): + file_name = "first_order_inner_expectation_param_{}_rank_{}.npy".format( + iparam + 1, irank + ) + data = np.load(file_name) + + inner_expectation[:, :, ii * batch_size : (ii + 1) * batch_size] = data + + sobol_numerator[:, :, iparam] = np.var(inner_expectation, axis=2) + + ratio = sobol_numerator / sobol_denominator[:, :, np.newaxis] + sobol_index = np.mean(ratio, axis=(0, 1)) + print("Total effect sobol index: {}".format(1 - sobol_index)) diff --git a/scripts/assemble_salib_sobol.py b/scripts/assemble_salib_sobol.py new file mode 100644 index 0000000..93cf62f --- /dev/null +++ b/scripts/assemble_salib_sobol.py @@ -0,0 +1,65 @@ +from SALib.analyze import sobol +import os +import numpy as np + +# Begin User Input +root = "/home/sbhola/Documents/CASLAB/GIM/examples/ignition_model/learning_model" +sobol_input_sample_path = os.path.join(root, "sobol_samples") +sobol_output_sample_path = os.path.join(root, "campaign_results/campaign_6/SALib_Sobol") +eval_ranks = 5 # 1 Indexed +prior_mean = np.zeros(3) +prior_cov = np.eye(3) + +# End User Input + +bounds = [[prior_mean[ii], prior_cov[ii, ii]] for ii in range(len(prior_mean))] +param_dict = { + "num_vars": len(prior_mean), + "names": ["x1", "x2", "x3"], + "bounds": bounds, + "dists": ["norm"] * len(prior_mean), +} + +stacked_predictions = [] +for ii in range(eval_ranks): + input_file = sobol_input_sample_path + "/sobol_input_samples_rank_{}.npy".format(ii) + output_file = sobol_output_sample_path + "/sobol_output_samples_rank_{}.npy".format( + ii + ) + + assert os.path.exists(output_file), "Output file {} does not exist".format( + output_file + ) + + model_predictions = np.load(output_file) + print(model_predictions.shape) + num_data_pts, spatial_res, _ = model_predictions.shape + stacked_predictions.append(model_predictions[:, 0, :].T) + +stacked_predictions = np.vstack(stacked_predictions) +# plt.figure() +# plt.plot(1/np.arange(1, stacked_predictions.shape[-1] + 1), stacked_predictions.T) +# plt.show() +# breakpoint() + +# Compute Sobol Indices + +first_order_samples = np.zeros((param_dict["num_vars"], num_data_pts)) +second_order_samples = np.zeros((param_dict["num_vars"], num_data_pts)) + +for idim in range(num_data_pts): + Si = sobol.analyze( + param_dict, + stacked_predictions[:, idim], + calc_second_order=True, + print_to_console=False, + ) + first_order_samples[:, idim] = Si["S1"] + second_order_samples[:, idim] = np.array( + [Si["S2"][0, 1], Si["S2"][0, 2], Si["S2"][1, 2]] + ) + +first_order = np.mean(first_order_samples, axis=1) +second_order = np.mean(second_order_samples, axis=1) +print("First Order Sobol Indices: {}".format(first_order)) +print("Second Order Sobol Indices: {}".format(second_order)) diff --git a/scripts/generate_sobol_input_samples.py b/scripts/generate_sobol_input_samples.py new file mode 100644 index 0000000..ed0ce91 --- /dev/null +++ b/scripts/generate_sobol_input_samples.py @@ -0,0 +1,79 @@ +from SALib.sample import saltelli +import numpy as np +import os +import shutil + +# Begin User Input +prior_mean = np.zeros(3) +prior_cov = np.eye(3) +N = 2**15 +input_procs = 1 +output_procs = 200 +repartition = True # True if you want to repartition the samples +# End User Input +bounds = [[prior_mean[ii], prior_cov[ii, ii]] for ii in range(len(prior_mean))] + +param_dict = { + "num_vars": len(prior_mean), + "names": ["x1", "x2", "x3"], + "bounds": bounds, + "dists": ["norm"] * len(prior_mean), +} + +if repartition: + input_parameter_samples = [] + for ii in range(input_procs): + assert os.path.exists( + "sobol_samples/sobol_input_samples_rank_{}.npy".format(ii) + ), "sobol_input_samples.npy does not exist" + input_parameter_samples.append( + np.load("sobol_samples/sobol_input_samples_rank_{}.npy".format(ii)) + ) + assert ( + len(input_parameter_samples) == input_procs + ), "Number of input parameter samples is incorrect" + parameter_samples = np.vstack(input_parameter_samples) + + if os.path.exists("repart_sobol_samples"): + shutil.rmtree("repart_sobol_samples") + print("Removed existing repart_sobol_samples directory") + os.makedirs("repart_sobol_samples") + + num_samples_per_proc = np.array( + [int(parameter_samples.shape[0] / output_procs)] * output_procs + ) + num_samples_per_proc[: parameter_samples.shape[0] % output_procs] += 1 + assert parameter_samples.shape[0] == np.sum( + num_samples_per_proc + ), "Number of samples per proc is incorrect" + upper_idx = np.cumsum(num_samples_per_proc) + lower_idx = upper_idx - num_samples_per_proc + + for ii in range(output_procs): + np.save( + "repart_sobol_samples/sobol_input_samples_rank_{}.npy".format(ii), + parameter_samples[lower_idx[ii] : upper_idx[ii], :], + ) + +else: + assert input_procs == 1, "input_procs must be 1 if repartition is False" + parameter_samples = saltelli.sample(param_dict, N) + num_samples_per_proc = np.array( + [int(parameter_samples.shape[0] / output_procs)] * output_procs + ) + num_samples_per_proc[: parameter_samples.shape[0] % output_procs] += 1 + assert parameter_samples.shape[0] == np.sum( + num_samples_per_proc + ), "Number of samples per proc is incorrect" + upper_idx = np.cumsum(num_samples_per_proc) + lower_idx = upper_idx - num_samples_per_proc + if os.path.exists("sobol_samples"): + shutil.rmtree("sobol_samples") + print("Removed existing sobol_samples directory") + os.makedirs("sobol_samples") + + for ii in range(output_procs): + np.save( + "sobol_samples/sobol_input_samples_rank_{}.npy".format(ii), + parameter_samples[lower_idx[ii] : upper_idx[ii], :], + ) diff --git a/scripts/plot_script/color_schemes.py b/scripts/plot_script/color_schemes.py new file mode 100644 index 0000000..f92bc7a --- /dev/null +++ b/scripts/plot_script/color_schemes.py @@ -0,0 +1,118 @@ +def dark_colors(idx=None): + colors = [ + "darkgreen", + "darkorange", + "darkred", + "darkblue", + "darkviolet", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkmagenta", + "darkolivegreen", + "darkorchid", + "darkslateblue", + "darkslategray", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dodgerblue", + "firebrick", + "forestgreen", + "fuchsia", + "gainsboro", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + ] + if idx is None: + return colors + else: + return colors[idx] diff --git a/scripts/plot_script/plot_chains.py b/scripts/plot_script/plot_chains.py new file mode 100644 index 0000000..e32be94 --- /dev/null +++ b/scripts/plot_script/plot_chains.py @@ -0,0 +1,35 @@ +import numpy as np +import matplotlib.pyplot as plt + +# import seaborn as sns +# import pandas as pd +import sys +import os + +plt.rc("font", family="serif", size=25) +plt.rc("text", usetex=True) +plt.rc("xtick", labelsize=25) +plt.rc("ytick", labelsize=25) +plt.rc("axes", labelpad=25, titlepad=20) +plt.rc("lines", linewidth=3) + +num_parameters = 4 +num_procs = 8 +theta = [r"$\Theta_{}$".format(i) for i in range(1, num_parameters + 1)] +campaign = "campaign_{}".format(sys.argv[1]) +path = "./campaign_results/{}/".format(campaign) + +data = [] +for ichain in range(num_procs): + data_path = os.path.join(path, "burned_samples_rank_{}_.npy".format(ichain)) + data.append(np.load(data_path)) +data = np.concatenate(data, axis=0) + +fig, axs = plt.subplots(num_parameters, 1, figsize=(20, 20)) +for i in range(num_parameters): + axs[i].plot(data[:, i], color="k") + axs[i].set_xlabel("Iteration") + axs[i].set_ylabel(theta[i]) + +plt.tight_layout() +plt.savefig(os.path.join(path, "Figures/chains.png"), dpi=300) diff --git a/scripts/plot_script/plot_hist.py b/scripts/plot_script/plot_hist.py new file mode 100644 index 0000000..a9fb8a1 --- /dev/null +++ b/scripts/plot_script/plot_hist.py @@ -0,0 +1,38 @@ +import numpy as np +import matplotlib.pyplot as plt +import seaborn as sns +import pandas as pd +import sys +import os + +plt.rc("font", family="serif", size=20) +plt.rc("text", usetex=True) +plt.rc("xtick", labelsize=20) +plt.rc("ytick", labelsize=20) +plt.rc("axes", labelpad=25, titlepad=20) + +num_parameters = 4 +num_procs = 8 +theta = [r"$\Theta_{}$".format(i) for i in range(1, num_parameters + 1)] +campaign = "campaign_{}".format(sys.argv[1]) +path = "./campaign_results/{}/".format(campaign) + +data = [] +for ichain in range(num_procs): + data_path = os.path.join(path, "burned_samples_rank_{}_.npy".format(ichain)) + data.append(np.load(data_path)) +data = np.concatenate(data, axis=0) + +df = pd.DataFrame(data, columns=theta) + +sns.pairplot( + df, + diag_kind="kde", + corner=True, + # kind="kde", + plot_kws={"color": "k", "alpha": 0.3}, + diag_kws={"color": "k"}, +) +plt.tight_layout() +plt.savefig(os.path.join(path, "Figures/histogram.png"), dpi=300) +plt.close() diff --git a/scripts/plot_script/plot_information_content.py b/scripts/plot_script/plot_information_content.py new file mode 100644 index 0000000..2e868cd --- /dev/null +++ b/scripts/plot_script/plot_information_content.py @@ -0,0 +1,32 @@ +import numpy as np +import os +import seaborn as sns +import matplotlib.pyplot as plt + +# from itertools import combinations +from matplotlib.ticker import AutoMinorLocator + +sns.set_style("whitegrid") +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=20) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=30, titlepad=20) +plt.rc("legend", fontsize=18, framealpha=1.0) +path = ( + "/home/sbhola/Documents/CASLAB/GIM/examples/ignition_model/" + "learning_model/campaign_results/campaign_1" +) +file_name = "estimated_individual_mutual_information.npy" +mi = np.load(os.path.join(path, file_name)) + +num_parameters = 3 +xticklabels = [r"$\Theta_{}$".format(ii + 1) for ii in range(num_parameters)] + +fig, axs = plt.subplots() +axs.bar(xticklabels, mi, color="b", edgecolor="k", linewidth=3, width=0.2) +axs.set_ylabel(r"$I(\Theta_i;Y\mid \Theta_{\sim i}, d)$") +axs.yaxis.set_minor_locator(AutoMinorLocator()) +axs.set_ylim([0, 3]) +plt.tight_layout() +plt.savefig("information_content.png") +plt.close() diff --git a/scripts/plot_script/plot_matrix.py b/scripts/plot_script/plot_matrix.py new file mode 100644 index 0000000..cf57d9a --- /dev/null +++ b/scripts/plot_script/plot_matrix.py @@ -0,0 +1,57 @@ +import numpy as np +import seaborn as sns +import matplotlib.pyplot as plt +from itertools import combinations +import os + +sns.set_style("whitegrid") +plt.rc("text", usetex=True) +plt.rc("font", family="serif", size=20) +plt.rc("lines", linewidth=3) +plt.rc("axes", labelpad=30, titlepad=20) +plt.rc("legend", fontsize=18, framealpha=1.0) +path = ( + "/home/sbhola/Documents/CASLAB/GIM/examples/ignition_model/" + "learning_model/campaign_results/campaign_1" +) +file_name = "estimated_pair_mutual_information.npy" +cmi = np.load(os.path.join(path, file_name)) + +num_parameters = 3 +com = list(combinations(np.arange(num_parameters), 2)) +matrix = np.nan * np.eye(num_parameters) + +for kk, ipair in enumerate(com): + ii, jj = ipair + matrix[ii, jj] = cmi[kk] + matrix[jj, ii] = cmi[kk] +print(matrix) +xticklabels = [r"$\Theta_{}$".format(ii + 1) for ii in range(num_parameters)] +colormap = "inferno" +num_matrix = matrix.shape[-1] + +fig, axs = plt.subplots(1, 1, edgecolor="k", figsize=(10, 10)) +g = sns.heatmap( + matrix, + cmap=colormap, + annot=True, + square=True, + linewidth=3, + linecolor="k", + fmt=".3e", + cbar_kws={"aspect": 15, "shrink": 0.8}, + xticklabels=xticklabels, + yticklabels=xticklabels, + annot_kws={"bbox": {"facecolor": "w"}, "color": "k"}, + ax=axs, + vmin=0, + vmax=1.2, +) +for _, spine in g.spines.items(): + spine.set_visible(True) + spine.set_linewidth(3) + spine.set_color("k") +axs.set_title(r"$I(\Theta_i;\Theta_j\mid Y, \Theta_{\sim i, j}, d)$") +plt.tight_layout() +plt.savefig("parameter_dependence.png") +plt.close()