|
| 1 | + |
| 2 | +import torch |
| 3 | +import numpy as np |
| 4 | +from datetime import datetime |
| 5 | +from torch.autograd.functional import jacobian |
| 6 | +import pyro |
| 7 | +import pyro.distributions as dist |
| 8 | +from pyro.infer import Predictive |
| 9 | + |
| 10 | +from pyro.nn import PyroModule |
| 11 | + |
| 12 | +import gempy as gp |
| 13 | +import gempy_engine |
| 14 | +from gempy_engine.core.backend_tensor import BackendTensor |
| 15 | + |
| 16 | + |
| 17 | +from helpers import * |
| 18 | + |
| 19 | + |
| 20 | +class GempyModel(PyroModule): |
| 21 | + def __init__(self, interpolation_input_, geo_model_test, num_layers, slope, dtype): |
| 22 | + super(GempyModel, self).__init__() |
| 23 | + |
| 24 | + BackendTensor.change_backend_gempy(engine_backend=gp.data.AvailableBackends.PYTORCH) |
| 25 | + self.interpolation_input_ = interpolation_input_ |
| 26 | + self.geo_model_test = geo_model_test |
| 27 | + self.num_layers = num_layers |
| 28 | + self.dtype = dtype |
| 29 | + self.geo_model_test.interpolation_options.sigmoid_slope = slope |
| 30 | + ############################################################################### |
| 31 | + # Seed the randomness |
| 32 | + ############################################################################### |
| 33 | + seed = 42 |
| 34 | + np.random.seed(seed) |
| 35 | + torch.manual_seed(seed) |
| 36 | + # Ensure deterministic behavior |
| 37 | + torch.backends.cudnn.deterministic = True |
| 38 | + torch.backends.cudnn.benchmark = False |
| 39 | + # Setting the seed for Pyro sampling |
| 40 | + |
| 41 | + pyro.set_rng_seed(42) |
| 42 | + |
| 43 | + |
| 44 | + def create_sample(self): |
| 45 | + |
| 46 | + """ |
| 47 | + This Pyro model represents the probabilistic aspects of the geological model. |
| 48 | + It defines a prior distribution for the top layer's location and |
| 49 | + computes the thickness of the geological layer as an observed variable. |
| 50 | +
|
| 51 | + |
| 52 | + interpolation_input_: represents the dictionary of random variables for surface parameters |
| 53 | + geo_model_test : gempy model |
| 54 | + |
| 55 | + num_layers: represents the number of layers we want to include in the model |
| 56 | + |
| 57 | + """ |
| 58 | + |
| 59 | + Random_variable ={} |
| 60 | + |
| 61 | + # Create a random variable based on the provided dictionary used to modify input data of gempy |
| 62 | + counter=1 |
| 63 | + for interpolation_input_data in self.interpolation_input_[:self.num_layers]: |
| 64 | + |
| 65 | + # Check if user wants to create random variable based on modifying the surface points of gempy |
| 66 | + if interpolation_input_data["update"]=="interface_data": |
| 67 | + # Check what kind of distribution is needed |
| 68 | + if interpolation_input_data["prior_distribution"]=="normal": |
| 69 | + mean = interpolation_input_data["normal"]["mean"] |
| 70 | + std = interpolation_input_data["normal"]["std"] |
| 71 | + Random_variable["mu_"+ str(counter)] = pyro.sample("mu_"+ str(counter), dist.Normal(mean, std)) |
| 72 | + #print(Random_variable["mu_"+ str(counter)]) |
| 73 | + |
| 74 | + elif interpolation_input_data["prior_distribution"]=="uniform": |
| 75 | + min = interpolation_input_data["uniform"]["min"] |
| 76 | + max = interpolation_input_data["uniform"]["min"] |
| 77 | + Random_variable["mu_"+ str(interpolation_input_data['id'])] = pyro.sample("mu_"+ str(interpolation_input_data['id']), dist.Uniform(min, max)) |
| 78 | + |
| 79 | + |
| 80 | + else: |
| 81 | + print("We have to include the distribution") |
| 82 | + |
| 83 | + # # Check which co-ordinates direction we wants to allow and modify the surface point data |
| 84 | + counter=counter+1 |
| 85 | + |
| 86 | + |
| 87 | + |
| 88 | + def GenerateInputSamples(self, number_samples): |
| 89 | + |
| 90 | + pyro.clear_param_store() |
| 91 | + # We can build a probabilistic model using pyro by calling it |
| 92 | + |
| 93 | + dot = pyro.render_model(self.create_sample, model_args=()) |
| 94 | + # Generate 50 samples |
| 95 | + num_samples = number_samples # N |
| 96 | + predictive = Predictive(self.create_sample, num_samples=num_samples) |
| 97 | + samples = predictive() |
| 98 | + |
| 99 | + samples_list=[] |
| 100 | + for i in range(len(self.interpolation_input_)): |
| 101 | + samples_list.append(samples["mu_"+str(i+1)].reshape(-1,1)) |
| 102 | + ######store the samples ###### |
| 103 | + parameters=torch.hstack(samples_list) # (N, p) = number of sample X number of paramter |
| 104 | + |
| 105 | + return parameters.detach().numpy() |
| 106 | + |
| 107 | + def GempyForward(self, *params): |
| 108 | + index=0 |
| 109 | + interpolation_input = self.geo_model_test.interpolation_input |
| 110 | + |
| 111 | + for interpolation_input_data in self.interpolation_input_[:self.num_layers]: |
| 112 | + # Check which co-ordinates direction we wants to allow and modify the surface point data |
| 113 | + if interpolation_input_data["direction"]=="X": |
| 114 | + interpolation_input.surface_points.sp_coords = torch.index_put( |
| 115 | + interpolation_input.surface_points.sp_coords, |
| 116 | + (torch.tensor([interpolation_input_data["id"]]), torch.tensor([0])), |
| 117 | + params[index]) |
| 118 | + elif interpolation_input_data["direction"]=="Y": |
| 119 | + interpolation_input.surface_points.sp_coords = torch.index_put( |
| 120 | + interpolation_input.surface_points.sp_coords, |
| 121 | + (torch.tensor([interpolation_input_data["id"]]), torch.tensor([1])), |
| 122 | + params[index]) |
| 123 | + elif interpolation_input_data["direction"]=="Z": |
| 124 | + interpolation_input.surface_points.sp_coords = torch.index_put( |
| 125 | + interpolation_input.surface_points.sp_coords, |
| 126 | + (interpolation_input_data["id"], torch.tensor([2])), |
| 127 | + params[index]) |
| 128 | + else: |
| 129 | + print("Wrong direction") |
| 130 | + |
| 131 | + index=index+1 |
| 132 | + |
| 133 | + self.geo_model_test.solutions = gempy_engine.compute_model( |
| 134 | + interpolation_input=interpolation_input, |
| 135 | + options=self.geo_model_test.interpolation_options, |
| 136 | + data_descriptor=self.geo_model_test.input_data_descriptor, |
| 137 | + geophysics_input=self.geo_model_test.geophysics_input, |
| 138 | + ) |
| 139 | + |
| 140 | + # Compute and observe the thickness of the geological layer |
| 141 | + |
| 142 | + m_samples = self.geo_model_test.solutions.octrees_output[0].last_output_center.custom_grid_values |
| 143 | + return m_samples |
| 144 | + |
| 145 | + def GenerateOutputSamples(self, Inputs_samples): |
| 146 | + |
| 147 | + Inputs_samples = torch.tensor(Inputs_samples, dtype=self.dtype) |
| 148 | + m_data =[] |
| 149 | + dmdc_data =[] |
| 150 | + for i in range(Inputs_samples.shape[0]): |
| 151 | + params_tuple = tuple([Inputs_samples[i,j].clone().requires_grad_(True) for j in range(Inputs_samples.shape[1])]) |
| 152 | + |
| 153 | + m_samples = self.GempyForward(*params_tuple) |
| 154 | + m_data.append(m_samples) |
| 155 | + J = jacobian(self.GempyForward, params_tuple) |
| 156 | + J_matrix = torch.tensor([[J[j][i] for j in range(len(J))] for i in range(J[0].shape[0])]) |
| 157 | + dmdc_data.append(J_matrix) |
| 158 | + |
| 159 | + return torch.stack(m_data).detach().numpy() , torch.stack(dmdc_data).detach().numpy() |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | +def generate_input_output_gempy_data(mesh, number_samples, slope=200, filename=None): |
| 165 | + # ---------------- 1️⃣ Check and create a 3D custom grid ---------------- |
| 166 | + mesh_coordinates = mesh |
| 167 | + data ={} |
| 168 | + geo_model_test = create_initial_gempy_model(refinement=3, save=False) |
| 169 | + if mesh_coordinates.shape[1]==2: |
| 170 | + xyz_coord = np.insert(mesh_coordinates, 1, 0, axis=1) |
| 171 | + elif mesh_coordinates.shape[1]==3: |
| 172 | + xyz_coord = mesh_coordinates |
| 173 | + |
| 174 | + gp.set_custom_grid(geo_model_test.grid, xyz_coord=xyz_coord) |
| 175 | + geo_model_test.interpolation_options.mesh_extraction = False |
| 176 | + |
| 177 | + sp_coords_copy_test = geo_model_test.interpolation_input.surface_points.sp_coords.copy() |
| 178 | + |
| 179 | + ############################################################################### |
| 180 | + # 2️⃣ Make a list of gempy parameter which would be treated as a random variable |
| 181 | + ############################################################################### |
| 182 | + dtype =torch.float64 |
| 183 | + test_list=[] |
| 184 | + std = 0.06 |
| 185 | + test_list.append({"update":"interface_data","id":torch.tensor([1]), "direction":"Z", "prior_distribution":"normal","normal":{"mean":torch.tensor(sp_coords_copy_test[1,2],dtype=dtype), "std":torch.tensor(std,dtype=dtype)}}) |
| 186 | + test_list.append({"update":"interface_data","id":torch.tensor([4]), "direction":"Z", "prior_distribution":"normal","normal":{"mean":torch.tensor(sp_coords_copy_test[4,2],dtype=dtype), "std":torch.tensor(std,dtype=dtype)}}) |
| 187 | + num_layers = len(test_list) # length of the list |
| 188 | + |
| 189 | + Gempy = GempyModel(test_list, geo_model_test, num_layers, slope=slope, dtype=torch.float64) |
| 190 | + |
| 191 | + # ---------------- 3️⃣ Generate the samples for input parameters. c ∼ N(µ, Σ) ---------------- |
| 192 | + |
| 193 | + start_sample_input_time = datetime.now() |
| 194 | + c = Gempy.GenerateInputSamples(number_samples=number_samples) |
| 195 | + end_sample_input_time = datetime.now() |
| 196 | + total_time_input = end_sample_input_time - start_sample_input_time |
| 197 | + |
| 198 | + data["input"] = c.tolist() |
| 199 | + # ---------------- 3️⃣ Generate the samples for output of gempy and the Jacobian matrix. m= Gempy(c) and dm/dc ---------------- |
| 200 | + start_sample_output_time = datetime.now() |
| 201 | + m_data, dmdc_data = Gempy.GenerateOutputSamples(Inputs_samples=c) |
| 202 | + end_sample_output_time = datetime.now() |
| 203 | + total_time_output = end_sample_output_time - start_sample_output_time |
| 204 | + |
| 205 | + data["Gempy_output"] = m_data.tolist() |
| 206 | + data["Jacobian_Gempy"] = dmdc_data.tolist() |
| 207 | + |
| 208 | + return data, total_time_input, total_time_output |
| 209 | + |
| 210 | + |
| 211 | + |
0 commit comments