|
| 1 | +import numpy as np |
| 2 | +import os |
| 3 | +import pyro.distributions as dist |
| 4 | +import torch |
| 5 | + |
| 6 | +import gempy as gp |
| 7 | +from gempy_engine.core.backend_tensor import BackendTensor |
| 8 | + |
| 9 | + |
| 10 | +def test_basic_gempy_I() -> None: |
| 11 | + current_dir = os.path.dirname(os.path.abspath(__file__)) |
| 12 | + data_path = os.path.abspath(os.path.join(current_dir, '..', '..', 'examples', 'tutorials', 'data')) |
| 13 | + geo_model = gp.create_geomodel( |
| 14 | + project_name='Wells', |
| 15 | + extent=[0, 12000, -500, 500, 0, 4000], |
| 16 | + refinement=3, |
| 17 | + importer_helper=gp.data.ImporterHelper( |
| 18 | + path_to_orientations=os.path.join(data_path, "2-layers", "2-layers_orientations.csv"), |
| 19 | + path_to_surface_points=os.path.join(data_path, "2-layers", "2-layers_surface_points.csv") |
| 20 | + ) |
| 21 | + ) |
| 22 | + |
| 23 | + # TODO: Convert this into an options preset |
| 24 | + geo_model.interpolation_options.uni_degree = 0 |
| 25 | + geo_model.interpolation_options.mesh_extraction = False |
| 26 | + geo_model.interpolation_options.sigmoid_slope = 1100. |
| 27 | + |
| 28 | + # region Minimal grid for the specific likelihood function |
| 29 | + x_loc = 6000 |
| 30 | + y_loc = 0 |
| 31 | + z_loc = np.linspace(0, 4000, 100) |
| 32 | + xyz_coord = np.array([[x_loc, y_loc, z] for z in z_loc]) |
| 33 | + gp.set_custom_grid(geo_model.grid, xyz_coord=xyz_coord) |
| 34 | + # endregion |
| 35 | + |
| 36 | + geo_model.grid.active_grids = gp.data.Grid.GridTypes.CUSTOM |
| 37 | + assert geo_model.grid.values.shape[0] == 100, "Custom grid should have 100 cells" |
| 38 | + gp.compute_model(gempy_model=geo_model) |
| 39 | + |
| 40 | + # TODO: Make this a more elegant way |
| 41 | + BackendTensor.change_backend_gempy(engine_backend=gp.data.AvailableBackends.PYTORCH) |
| 42 | + |
| 43 | + normal = dist.Normal( |
| 44 | + loc=(geo_model.surface_points_copy_transformed.xyz[0, 2]), |
| 45 | + scale=torch.tensor(0.1, dtype=torch.float64) |
| 46 | + ) |
| 47 | + |
| 48 | + from gempy_probability.modules.model_definition.model_examples import model |
| 49 | + _prob_run( |
| 50 | + geo_model=geo_model, |
| 51 | + prob_model=model, |
| 52 | + normal=normal, |
| 53 | + y_obs_list=torch.tensor([200, 210, 190]) |
| 54 | + ) |
| 55 | + |
| 56 | + |
| 57 | +def _prob_run(geo_model: gp.data.GeoModel, prob_model: callable, |
| 58 | + normal: torch.distributions.Distribution, y_obs_list: torch.Tensor) -> None: |
| 59 | + # Run prior sampling and visualization |
| 60 | + from pyro.infer import Predictive |
| 61 | + import pyro |
| 62 | + import arviz as az |
| 63 | + import matplotlib.pyplot as plt |
| 64 | + |
| 65 | + from pyro.infer import NUTS |
| 66 | + from pyro.infer import MCMC |
| 67 | + from pyro.infer.autoguide import init_to_mean |
| 68 | + |
| 69 | + # region prior sampling |
| 70 | + predictive = Predictive( |
| 71 | + model=prob_model, |
| 72 | + num_samples=50 |
| 73 | + ) |
| 74 | + prior = predictive(geo_model, normal, y_obs_list) |
| 75 | + |
| 76 | + data = az.from_pyro(prior=prior) |
| 77 | + az.plot_trace(data.prior) |
| 78 | + plt.show() |
| 79 | + |
| 80 | + # endregion |
| 81 | + |
| 82 | + # region inference |
| 83 | + pyro.primitives.enable_validation(is_validate=True) |
| 84 | + nuts_kernel = NUTS( |
| 85 | + prob_model, |
| 86 | + step_size=0.0085, |
| 87 | + adapt_step_size=True, |
| 88 | + target_accept_prob=0.9, |
| 89 | + max_tree_depth=10, |
| 90 | + init_strategy=init_to_mean |
| 91 | + ) |
| 92 | + mcmc = MCMC( |
| 93 | + kernel=nuts_kernel, |
| 94 | + num_samples=200, |
| 95 | + warmup_steps=50, |
| 96 | + disable_validation=False |
| 97 | + ) |
| 98 | + mcmc.run(geo_model, normal, y_obs_list) |
| 99 | + posterior_samples = mcmc.get_samples() |
| 100 | + posterior_predictive_fn = Predictive( |
| 101 | + model=prob_model, |
| 102 | + posterior_samples=posterior_samples |
| 103 | + ) |
| 104 | + posterior_predictive = posterior_predictive_fn(geo_model, normal, y_obs_list) |
| 105 | + data = az.from_pyro(posterior=mcmc, prior=prior, posterior_predictive=posterior_predictive) |
| 106 | + # endregion |
| 107 | + |
| 108 | + az.plot_trace(data) |
| 109 | + plt.show() |
| 110 | + from gempy_probability.modules.plot.plot_posterior import default_red, default_blue |
| 111 | + az.plot_density( |
| 112 | + data=[data.posterior_predictive, data.prior_predictive], |
| 113 | + shade=.9, |
| 114 | + var_names=[r'$\mu_{thickness}$'], |
| 115 | + data_labels=["Posterior Predictive", "Prior Predictive"], |
| 116 | + colors=[default_red, default_blue], |
| 117 | + ) |
| 118 | + plt.show() |
| 119 | + az.plot_density( |
| 120 | + data=[data, data.prior], |
| 121 | + shade=.9, |
| 122 | + hdi_prob=.99, |
| 123 | + data_labels=["Posterior", "Prior"], |
| 124 | + colors=[default_red, default_blue], |
| 125 | + ) |
| 126 | + plt.show() |
| 127 | + |
| 128 | + # Test posterior mean values |
| 129 | + posterior_top_mean = float(data.posterior[r'$\mu_{top}$'].mean()) |
| 130 | + target_top = 0.00875 |
| 131 | + target_thickness = 223 |
| 132 | + assert abs(posterior_top_mean - target_top) < 0.0200, f"Top layer mean {posterior_top_mean} outside expected range" |
| 133 | + posterior_thickness_mean = float(data.posterior_predictive[r'$\mu_{thickness}$'].mean()) |
| 134 | + assert abs(posterior_thickness_mean - target_thickness) < 5, f"Thickness mean {posterior_thickness_mean} outside expected range" |
| 135 | + # Test convergence diagnostics |
| 136 | + assert float(data.sample_stats.diverging.sum()) == 0, "MCMC sampling has divergences" |
| 137 | + |
| 138 | + print("Posterior mean values:") |
| 139 | + print(f"Top layer mean: {posterior_top_mean}") |
| 140 | + print(f"Thickness mean: {posterior_thickness_mean}") |
| 141 | + print("MCMC convergence diagnostics:") |
| 142 | + print(f"Divergences: {float(data.sample_stats.diverging.sum())}") |
| 143 | + print(f"Acceptance rate: {float(data.sample_stats.acceptance_rate.mean())}") |
| 144 | + print(f"Mean tree depth: {float(data.sample_stats.mean_tree_depth.mean())}") |
0 commit comments