YPlusModel Tutorial

Trey V. Wenger (c) January 2025

Here we demonstrate the basic features of the YPlusModel model. The YPlusModel models the radio recombiation line emission to infer y+, the helium abundance by number.

[1]:
# General imports
import os
import pickle
import time

import matplotlib.pyplot as plt
import arviz as az
import pandas as pd
import numpy as np
import pymc as pm

print("pymc version:", pm.__version__)

import bayes_spec
print("bayes_spec version:", bayes_spec.__version__)

import bayes_yplus
print("bayes_yplus version:", bayes_yplus.__version__)

# Notebook configuration
pd.options.display.max_rows = None
pymc version: 5.16.2
bayes_spec version: 1.7.2+4.gfef408d
bayes_yplus version: 1.3.1+0.gb4f2b7d.dirty

Simulating Data

To test the model, we must simulate some data. We can do this with YPlusModel, but we must pack a “dummy” data structure first.

[2]:
from bayes_spec import SpecData

# spectral axis definition
spec_axis = np.linspace(-200.0, 100.0, 251) # km s-1

# data noise can either be a scalar (assumed constant noise across the spectrum)
# or an array of the same length as the data
noise = 1.0 # mK

# brightness data. In this case, we just throw in some random data for now
# since we are only doing this in order to simulate some actual data.
brightness_data = noise * np.random.randn(len(spec_axis)) # mK

# The data can be named anything. We name our data "observation"
observation = SpecData(
    spec_axis,
    brightness_data,
    noise,
    xlabel=r"$V_{\rm LSR}$ (km s$^{-1}$)",
    ylabel="Brightness Temperature (mK)",
)
dummy_data = {"observation": observation}

for label, dataset in dummy_data.items():
    print(label, len(dataset.spectral))
    # HACK: normalize data by noise
    dataset._brightness_offset = np.median(dataset.brightness)
    dataset._brightness_scale = dataset.noise

# Plot the dummy data
plt.plot(dummy_data["observation"].spectral, dummy_data["observation"].brightness, 'k-')
plt.xlabel(dummy_data["observation"].xlabel)
_ = plt.ylabel(dummy_data["observation"].ylabel)
observation 251
../_images/notebooks_tutorial_3_1.png

Now that we have a dummy data format, we can generate a simulated observation by evaluating the likelihood for a given set of model parameters.

[3]:
from bayes_yplus import YPlusModel

# Initialize and define the model
n_clouds = 3
baseline_degree = 1
model = YPlusModel(
    dummy_data,
    n_clouds=n_clouds,
    baseline_degree=baseline_degree,
    seed=1234,
    verbose=True
)
model.add_priors(
    prior_H_area = 1000.0, # width of the H_area prior (mK km s-1)
    prior_H_center = [0.0, 25.0], # mean and width of H_center prior (km s-1)
    prior_H_fwhm = [25.0, 10.0], # mean and width of H FWHM prior (km s-1)
    prior_He_H_fwhm_ratio = [1.0, 0.1], # mean and width of He/H FWHM ratio prior
    prior_yplus = 0.05, # width of yplus prior
    prior_rms = None, # do not infer spectral rms
    prior_baseline_coeffs = None, # use default baseline priors
    ordered = False, # do not assume ordered components
)
model.add_likelihood()

sim_brightness = model.model.observation.eval({
    "H_area": [800.0, 1000.0, 1200.0],
    "H_center": [-30.0, 5.0, 25.0],
    "H_fwhm": [20.0, 25.0, 30.0],
    "He_H_fwhm_ratio": [1.1, 1.0, 0.9],
    "yplus": [0.1, 0.15, 0.05],
    "baseline_observation_norm": [-2.0, 1.5], # normalized baseline coefficients
})

# Plot the simulated data
plt.plot(dummy_data["observation"].spectral, sim_brightness, 'k-')
plt.xlabel(dummy_data["observation"].xlabel)
_ = plt.ylabel(dummy_data["observation"].ylabel)
../_images/notebooks_tutorial_5_0.png
[4]:
# Now we pack the simulated spectrum into a new SpecData instance
observation = SpecData(
    spec_axis,
    sim_brightness,
    noise,
    xlabel=r"$V_{\rm LSR}$ (km s$^{-1}$)",
    ylabel="Brightness Temperature (mK)",
)
data = {"observation": observation}

for label, dataset in data.items():
    print(label, len(dataset.spectral))
    # HACK: normalize data by noise
    dataset._brightness_offset = np.median(dataset.brightness)
    dataset._brightness_scale = dataset.noise
observation 251

Model Definition

Finally, with our model definition and (simulated) data in hand, we can explore the capabilities of YPlusModel. Here we create a new model with the simulated data.

[5]:
# Initialize and define the model
model = YPlusModel(
    data,
    n_clouds=n_clouds,
    baseline_degree=baseline_degree,
    seed=1234,
    verbose=True
)
model.add_priors(
    prior_H_area = 1000.0, # width of the H_area prior (mK km s-1)
    prior_H_center = [0.0, 25.0], # mean and width of H_center prior (km s-1)
    prior_H_fwhm = [25.0, 10.0], # mean and width of H FWHM prior (km s-1)
    prior_He_H_fwhm_ratio = [1.0, 0.1], # mean and width of He/H FWHM ratio prior
    prior_yplus = 0.05, # width of yplus prior
    prior_rms = None, # do not infer spectral rms
    prior_baseline_coeffs = None, # use default baseline priors
    ordered = False, # do not assume ordered components
)
model.add_likelihood()
[6]:
# Plot model graph
model.graph().render("yplus_model", format="png")
model.graph()
[6]:
../_images/notebooks_tutorial_9_0.svg
[7]:
# model string representation
print(model.model.str_repr())
baseline_observation_norm ~ Normal(0, <constant>)
            H_center_norm ~ Normal(0, 1)
              H_area_norm ~ HalfNormal(0, 1)
                   H_fwhm ~ Gamma(6.25, f())
          He_H_fwhm_ratio ~ Gamma(100, f())
               yplus_norm ~ HalfNormal(0, 1)
                 H_center ~ Deterministic(f(H_center_norm))
                   H_area ~ Deterministic(f(H_area_norm))
                    yplus ~ Deterministic(f(yplus_norm))
              H_amplitude ~ Deterministic(f(H_fwhm, H_area_norm))
             He_amplitude ~ Deterministic(f(He_H_fwhm_ratio, yplus_norm, H_fwhm, H_area_norm))
                He_center ~ Deterministic(f(H_center_norm))
                  He_fwhm ~ Deterministic(f(He_H_fwhm_ratio, H_fwhm))
                  He_area ~ Deterministic(f(He_H_fwhm_ratio, H_fwhm, yplus_norm, H_area_norm))
              observation ~ Normal(f(H_area_norm, H_fwhm, He_H_fwhm_ratio, baseline_observation_norm, yplus_norm, H_center_norm), <constant>)

We check that our prior distributions are reasonable by drawing prior predictive checks. Each colored line is a simulated “observation” with parameters drawn from the prior distributions. You should check that these simulated observations at least somewhat overlap your actual observation (black line).

[8]:
from bayes_spec.plots import plot_predictive

# prior predictive check
prior = model.sample_prior_predictive(
    samples=100,  # prior predictive samples
)
_ = plot_predictive(model.data, prior.prior_predictive)
Sampling: [H_area_norm, H_center_norm, H_fwhm, He_H_fwhm_ratio, baseline_observation_norm, observation, yplus_norm]
../_images/notebooks_tutorial_12_1.png
[9]:
from bayes_spec.plots import plot_pair

_ = plot_pair(
    prior.prior, # samples
    model.cloud_freeRVs, # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_13_0.png
[10]:
from bayes_spec.plots import plot_pair

_ = plot_pair(
    prior.prior, # samples
    model.cloud_deterministics, # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_14_0.png

Variational Inference

We can approximate the posterior distribution using variational inference.

[11]:
start = time.time()
model.fit(
    n = 100_000, # maximum number of VI iterations
    draws = 1_000, # number of posterior samples
    rel_tolerance = 0.01, # VI relative convergence threshold
    abs_tolerance = 0.05, # VI absolute convergence threshold
    learning_rate = 1e-2, # VI learning rate
)
end = time.time()
print(f"Runtime: {(end-start)/60.0:.2f} minutes")
Convergence achieved at 5300
Interrupted at 5,299 [5%]: Average Loss = 5,181.6
Runtime: 0.39 minutes
[12]:
pm.summary(model.trace.posterior)
arviz - WARNING - Shape validation failed: input_shape: (1, 1000), minimum_shape: (chains=2, draws=4)
[12]:
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
H_amplitude[0] 37.278 0.461 36.365 38.152 0.015 0.011 916.0 849.0 NaN
H_amplitude[1] 28.858 0.449 28.042 29.699 0.014 0.010 1004.0 975.0 NaN
H_amplitude[2] 43.778 0.359 43.092 44.393 0.011 0.008 1029.0 1026.0 NaN
H_area[0] 813.683 6.609 800.374 825.175 0.206 0.145 1036.0 1023.0 NaN
H_area[1] 697.347 7.701 682.671 711.343 0.245 0.173 989.0 992.0 NaN
H_area[2] 1517.676 8.178 1502.908 1532.906 0.260 0.184 989.0 944.0 NaN
H_area_norm[0] 0.814 0.007 0.800 0.825 0.000 0.000 1036.0 1023.0 NaN
H_area_norm[1] 0.697 0.008 0.683 0.711 0.000 0.000 989.0 992.0 NaN
H_area_norm[2] 1.518 0.008 1.503 1.533 0.000 0.000 989.0 944.0 NaN
H_center[0] -29.891 0.104 -30.075 -29.683 0.003 0.002 1007.0 939.0 NaN
H_center[1] 3.106 0.137 2.844 3.346 0.004 0.003 958.0 897.0 NaN
H_center[2] 22.158 0.115 21.955 22.384 0.004 0.002 1055.0 936.0 NaN
H_center_norm[0] -1.196 0.004 -1.203 -1.187 0.000 0.000 1007.0 939.0 NaN
H_center_norm[1] 0.124 0.005 0.114 0.134 0.000 0.000 958.0 897.0 NaN
H_center_norm[2] 0.886 0.005 0.878 0.895 0.000 0.000 1055.0 936.0 NaN
H_fwhm[0] 20.507 0.191 20.122 20.831 0.006 0.004 937.0 943.0 NaN
H_fwhm[1] 22.704 0.246 22.229 23.141 0.008 0.005 1061.0 979.0 NaN
H_fwhm[2] 32.570 0.208 32.197 32.959 0.007 0.005 906.0 983.0 NaN
He_H_fwhm_ratio[0] 1.049 0.082 0.910 1.219 0.003 0.002 959.0 970.0 NaN
He_H_fwhm_ratio[1] 1.039 0.060 0.920 1.144 0.002 0.001 1066.0 937.0 NaN
He_H_fwhm_ratio[2] 0.950 0.075 0.821 1.100 0.002 0.002 1041.0 824.0 NaN
He_amplitude[0] 2.966 0.400 2.279 3.746 0.013 0.009 965.0 874.0 NaN
He_amplitude[1] 4.476 0.377 3.781 5.167 0.012 0.009 960.0 983.0 NaN
He_amplitude[2] 3.028 0.358 2.429 3.752 0.011 0.008 1000.0 875.0 NaN
He_area[0] 67.434 6.880 54.001 79.317 0.233 0.165 850.0 665.0 NaN
He_area[1] 111.992 6.972 97.582 124.095 0.219 0.155 1015.0 1069.0 NaN
He_area[2] 99.084 8.232 83.658 113.845 0.258 0.182 1010.0 914.0 NaN
He_center[0] -152.041 0.104 -152.225 -151.833 0.003 0.002 1007.0 939.0 NaN
He_center[1] -119.044 0.137 -119.306 -118.804 0.004 0.003 958.0 897.0 NaN
He_center[2] -99.992 0.115 -100.195 -99.766 0.004 0.002 1055.0 936.0 NaN
He_fwhm[0] 21.505 1.705 18.812 25.202 0.055 0.039 955.0 623.0 NaN
He_fwhm[1] 23.581 1.365 21.006 26.061 0.042 0.030 1030.0 1024.0 NaN
He_fwhm[2] 30.938 2.429 26.639 35.589 0.075 0.053 1055.0 916.0 NaN
baseline_observation_norm[0] -2.682 0.071 -2.802 -2.535 0.003 0.002 806.0 957.0 NaN
baseline_observation_norm[1] 1.052 0.253 0.599 1.546 0.008 0.006 1055.0 941.0 NaN
yplus[0] 0.083 0.008 0.066 0.097 0.000 0.000 818.0 614.0 NaN
yplus[1] 0.161 0.010 0.141 0.178 0.000 0.000 988.0 1015.0 NaN
yplus[2] 0.065 0.005 0.055 0.075 0.000 0.000 1010.0 991.0 NaN
yplus_norm[0] 1.658 0.169 1.329 1.948 0.006 0.004 818.0 614.0 NaN
yplus_norm[1] 3.212 0.197 2.828 3.570 0.006 0.004 988.0 1015.0 NaN
yplus_norm[2] 1.306 0.108 1.099 1.493 0.003 0.002 1010.0 991.0 NaN
[13]:
posterior = model.sample_posterior_predictive(
    thin=100, # keep one in {thin} posterior samples
)
_ = plot_predictive(model.data, posterior.posterior_predictive)
Sampling: [observation]
../_images/notebooks_tutorial_18_4.png

Posterior Sampling: MCMC

We can sample from the posterior distribution using MCMC.

[14]:
start = time.time()
model.sample(
    init = "advi+adapt_diag", # initialization strategy
    tune = 1000, # tuning samples
    draws = 1000, # posterior samples
    chains = 4, # number of independent chains
    cores = 4, # number of parallel chains
    init_kwargs = {"rel_tolerance": 0.01, "abs_tolerance": 0.05, "learning_rate": 1e-2}, # VI initialization arguments
    nuts_kwargs = {"target_accept": 0.8}, # NUTS arguments
)
end = time.time()
print(f"Runtime: {(end-start)/60.0:.2f} minutes")
Initializing NUTS using custom advi+adapt_diag strategy
Convergence achieved at 5300
Interrupted at 5,299 [5%]: Average Loss = 5,181.6
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [baseline_observation_norm, H_center_norm, H_area_norm, H_fwhm, He_H_fwhm_ratio, yplus_norm]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 59 seconds.
Adding log-likelihood to trace
Runtime: 1.41 minutes
[15]:
model.solve(kl_div_threshold=0.9)
GMM converged to unique solution
[16]:
pm.summary(model.trace.solution_0)
[16]:
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
H_amplitude[0] 37.455 0.362 36.772 38.132 0.005 0.003 5811.0 3136.0 1.00
H_amplitude[1] 38.676 3.628 31.908 45.512 0.120 0.085 907.0 1321.0 1.01
H_amplitude[2] 37.453 2.792 32.046 42.425 0.093 0.066 899.0 1185.0 1.01
H_area[0] 803.055 11.282 781.089 823.590 0.294 0.208 1471.0 2189.0 1.00
H_area[1] 1041.562 139.094 775.593 1301.150 4.666 3.305 890.0 1212.0 1.01
H_area[2] 1170.196 138.528 923.766 1448.213 4.670 3.303 880.0 1197.0 1.01
H_area_norm[0] 0.803 0.011 0.781 0.824 0.000 0.000 1471.0 2189.0 1.00
H_area_norm[1] 1.042 0.139 0.776 1.301 0.005 0.003 890.0 1212.0 1.01
H_area_norm[2] 1.170 0.139 0.924 1.448 0.005 0.003 880.0 1197.0 1.01
H_center[0] -29.990 0.114 -30.197 -29.767 0.002 0.002 2314.0 2321.0 1.00
H_center[1] 5.203 0.940 3.471 6.984 0.031 0.022 897.0 1230.0 1.01
H_center[2] 25.543 1.381 22.875 28.096 0.046 0.033 899.0 1239.0 1.01
H_center_norm[0] -1.200 0.005 -1.208 -1.191 0.000 0.000 2314.0 2321.0 1.00
H_center_norm[1] 0.208 0.038 0.139 0.279 0.001 0.001 897.0 1230.0 1.01
H_center_norm[2] 1.022 0.055 0.915 1.124 0.002 0.001 899.0 1239.0 1.01
H_fwhm[0] 20.143 0.291 19.606 20.670 0.007 0.005 1893.0 2559.0 1.00
H_fwhm[1] 25.208 1.117 23.097 27.246 0.036 0.026 958.0 1133.0 1.01
H_fwhm[2] 29.260 1.387 26.698 31.882 0.045 0.032 937.0 1375.0 1.01
He_H_fwhm_ratio[0] 1.031 0.090 0.863 1.200 0.002 0.001 2772.0 2444.0 1.00
He_H_fwhm_ratio[1] 1.029 0.065 0.914 1.158 0.001 0.001 2993.0 2837.0 1.00
He_H_fwhm_ratio[2] 0.980 0.087 0.818 1.139 0.002 0.001 3038.0 2670.0 1.00
He_amplitude[0] 2.984 0.348 2.333 3.641 0.006 0.004 3739.0 2939.0 1.00
He_amplitude[1] 5.207 0.386 4.443 5.891 0.008 0.006 2342.0 2751.0 1.00
He_amplitude[2] 2.030 0.500 1.016 2.913 0.015 0.010 1164.0 1435.0 1.00
He_area[0] 65.844 8.719 50.214 82.647 0.194 0.137 1994.0 2704.0 1.00
He_area[1] 143.813 15.864 111.302 171.022 0.435 0.308 1335.0 1977.0 1.00
He_area[2] 62.151 17.245 28.949 94.072 0.526 0.372 1065.0 1359.0 1.00
He_center[0] -152.140 0.114 -152.347 -151.917 0.002 0.002 2314.0 2321.0 1.00
He_center[1] -116.947 0.940 -118.679 -115.166 0.031 0.022 897.0 1230.0 1.01
He_center[2] -96.607 1.381 -99.275 -94.054 0.046 0.033 899.0 1239.0 1.01
He_fwhm[0] 20.772 1.836 17.292 24.148 0.036 0.025 2594.0 2517.0 1.00
He_fwhm[1] 25.936 1.979 22.492 29.773 0.048 0.034 1733.0 2229.0 1.00
He_fwhm[2] 28.656 2.760 23.720 34.030 0.059 0.042 2130.0 2564.0 1.00
baseline_observation_norm[0] -2.626 0.111 -2.841 -2.434 0.003 0.002 1955.0 2637.0 1.00
baseline_observation_norm[1] 1.153 0.277 0.632 1.673 0.005 0.004 3110.0 2555.0 1.00
yplus[0] 0.082 0.011 0.062 0.102 0.000 0.000 2100.0 2608.0 1.00
yplus[1] 0.139 0.013 0.114 0.163 0.000 0.000 1677.0 2111.0 1.00
yplus[2] 0.052 0.011 0.031 0.072 0.000 0.000 1486.0 1490.0 1.00
yplus_norm[0] 1.640 0.213 1.238 2.037 0.005 0.003 2100.0 2608.0 1.00
yplus_norm[1] 2.782 0.260 2.288 3.261 0.006 0.005 1677.0 2111.0 1.00
yplus_norm[2] 1.048 0.211 0.625 1.432 0.006 0.004 1486.0 1490.0 1.00
[17]:
print("solutions:", model.solutions)
display(az.summary(model.trace["solution_0"]))
# this also works: az.summary(model.trace.solution_0)
solutions: [0]
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
H_amplitude[0] 37.455 0.362 36.772 38.132 0.005 0.003 5811.0 3136.0 1.00
H_amplitude[1] 38.676 3.628 31.908 45.512 0.120 0.085 907.0 1321.0 1.01
H_amplitude[2] 37.453 2.792 32.046 42.425 0.093 0.066 899.0 1185.0 1.01
H_area[0] 803.055 11.282 781.089 823.590 0.294 0.208 1471.0 2189.0 1.00
H_area[1] 1041.562 139.094 775.593 1301.150 4.666 3.305 890.0 1212.0 1.01
H_area[2] 1170.196 138.528 923.766 1448.213 4.670 3.303 880.0 1197.0 1.01
H_area_norm[0] 0.803 0.011 0.781 0.824 0.000 0.000 1471.0 2189.0 1.00
H_area_norm[1] 1.042 0.139 0.776 1.301 0.005 0.003 890.0 1212.0 1.01
H_area_norm[2] 1.170 0.139 0.924 1.448 0.005 0.003 880.0 1197.0 1.01
H_center[0] -29.990 0.114 -30.197 -29.767 0.002 0.002 2314.0 2321.0 1.00
H_center[1] 5.203 0.940 3.471 6.984 0.031 0.022 897.0 1230.0 1.01
H_center[2] 25.543 1.381 22.875 28.096 0.046 0.033 899.0 1239.0 1.01
H_center_norm[0] -1.200 0.005 -1.208 -1.191 0.000 0.000 2314.0 2321.0 1.00
H_center_norm[1] 0.208 0.038 0.139 0.279 0.001 0.001 897.0 1230.0 1.01
H_center_norm[2] 1.022 0.055 0.915 1.124 0.002 0.001 899.0 1239.0 1.01
H_fwhm[0] 20.143 0.291 19.606 20.670 0.007 0.005 1893.0 2559.0 1.00
H_fwhm[1] 25.208 1.117 23.097 27.246 0.036 0.026 958.0 1133.0 1.01
H_fwhm[2] 29.260 1.387 26.698 31.882 0.045 0.032 937.0 1375.0 1.01
He_H_fwhm_ratio[0] 1.031 0.090 0.863 1.200 0.002 0.001 2772.0 2444.0 1.00
He_H_fwhm_ratio[1] 1.029 0.065 0.914 1.158 0.001 0.001 2993.0 2837.0 1.00
He_H_fwhm_ratio[2] 0.980 0.087 0.818 1.139 0.002 0.001 3038.0 2670.0 1.00
He_amplitude[0] 2.984 0.348 2.333 3.641 0.006 0.004 3739.0 2939.0 1.00
He_amplitude[1] 5.207 0.386 4.443 5.891 0.008 0.006 2342.0 2751.0 1.00
He_amplitude[2] 2.030 0.500 1.016 2.913 0.015 0.010 1164.0 1435.0 1.00
He_area[0] 65.844 8.719 50.214 82.647 0.194 0.137 1994.0 2704.0 1.00
He_area[1] 143.813 15.864 111.302 171.022 0.435 0.308 1335.0 1977.0 1.00
He_area[2] 62.151 17.245 28.949 94.072 0.526 0.372 1065.0 1359.0 1.00
He_center[0] -152.140 0.114 -152.347 -151.917 0.002 0.002 2314.0 2321.0 1.00
He_center[1] -116.947 0.940 -118.679 -115.166 0.031 0.022 897.0 1230.0 1.01
He_center[2] -96.607 1.381 -99.275 -94.054 0.046 0.033 899.0 1239.0 1.01
He_fwhm[0] 20.772 1.836 17.292 24.148 0.036 0.025 2594.0 2517.0 1.00
He_fwhm[1] 25.936 1.979 22.492 29.773 0.048 0.034 1733.0 2229.0 1.00
He_fwhm[2] 28.656 2.760 23.720 34.030 0.059 0.042 2130.0 2564.0 1.00
baseline_observation_norm[0] -2.626 0.111 -2.841 -2.434 0.003 0.002 1955.0 2637.0 1.00
baseline_observation_norm[1] 1.153 0.277 0.632 1.673 0.005 0.004 3110.0 2555.0 1.00
yplus[0] 0.082 0.011 0.062 0.102 0.000 0.000 2100.0 2608.0 1.00
yplus[1] 0.139 0.013 0.114 0.163 0.000 0.000 1677.0 2111.0 1.00
yplus[2] 0.052 0.011 0.031 0.072 0.000 0.000 1486.0 1490.0 1.00
yplus_norm[0] 1.640 0.213 1.238 2.037 0.005 0.003 2100.0 2608.0 1.00
yplus_norm[1] 2.782 0.260 2.288 3.261 0.006 0.005 1677.0 2111.0 1.00
yplus_norm[2] 1.048 0.211 0.625 1.432 0.006 0.004 1486.0 1490.0 1.00

We generate posterior predictive checks as well as a trace plot of the individual chains. In the posterior predictive plot, we show each chain as a different color. Each line is one posterior sample.

[18]:
posterior = model.sample_posterior_predictive(
    thin=100, # keep one in {thin} posterior samples
)
_ = plot_predictive(model.data, posterior.posterior_predictive)
Sampling: [observation]
../_images/notebooks_tutorial_25_4.png
[19]:
from bayes_spec.plots import plot_traces

_ = plot_traces(model.trace.solution_0, model.cloud_freeRVs + model.baseline_freeRVs + model.hyper_freeRVs)
../_images/notebooks_tutorial_26_0.png

We can inspect the posterior distribution pair plots. First, the normalized, free cloud parameters.

[20]:
_ = plot_pair(
    model.trace.solution_0, # samples
    model.cloud_freeRVs, # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_28_0.png

Notice that there are three posterior modes. These correspond to the three clouds of the model. We can plot the posterior distributions of the deterministic quantities for a single cloud (excluding the spectral rms hyper parameter).

[21]:
_ = plot_pair(
    model.trace.solution_0.sel(cloud=0), # samples
    model.cloud_deterministics + ["He_fwhm", "He_H_fwhm_ratio"], # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_30_0.png
[22]:
_ = plot_pair(
    model.trace.solution_0.sel(cloud=1), # samples
    model.cloud_deterministics + ["He_fwhm", "He_H_fwhm_ratio"], # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_31_0.png
[23]:
_ = plot_pair(
    model.trace.solution_0.sel(cloud=2), # samples
    model.cloud_deterministics + ["He_fwhm", "He_H_fwhm_ratio"], # var_names to plot
    labeller=model.labeller, # label manager
)
../_images/notebooks_tutorial_32_0.png

Finally, we can get the posterior statistics, Bayesian Information Criterion (BIC), etc.

[24]:
point_stats = az.summary(
    model.trace.solution_0,
    var_names=model.cloud_freeRVs + model.cloud_deterministics,
    kind='stats',
    hdi_prob=0.68
)
print("BIC:", model.bic())
display(point_stats)
BIC: 808.387686307096
mean sd hdi_16% hdi_84%
H_center_norm[0] -1.200 0.005 -1.204 -1.195
H_center_norm[1] 0.208 0.038 0.170 0.245
H_center_norm[2] 1.022 0.055 0.968 1.078
H_area_norm[0] 0.803 0.011 0.792 0.814
H_area_norm[1] 1.042 0.139 0.918 1.195
H_area_norm[2] 1.170 0.139 1.023 1.298
H_fwhm[0] 20.143 0.291 19.834 20.417
H_fwhm[1] 25.208 1.117 24.123 26.307
H_fwhm[2] 29.260 1.387 27.903 30.649
He_H_fwhm_ratio[0] 1.031 0.090 0.936 1.113
He_H_fwhm_ratio[1] 1.029 0.065 0.957 1.083
He_H_fwhm_ratio[2] 0.980 0.087 0.889 1.060
yplus_norm[0] 1.640 0.213 1.435 1.857
yplus_norm[1] 2.782 0.260 2.505 2.992
yplus_norm[2] 1.048 0.211 0.870 1.285
H_center[0] -29.990 0.114 -30.101 -29.877
H_center[1] 5.203 0.940 4.257 6.132
H_center[2] 25.543 1.381 24.201 26.952
H_area[0] 803.055 11.282 791.664 813.750
H_area[1] 1041.562 139.094 918.214 1194.842
H_area[2] 1170.196 138.528 1022.599 1297.617
yplus[0] 0.082 0.011 0.072 0.093
yplus[1] 0.139 0.013 0.125 0.150
yplus[2] 0.052 0.011 0.044 0.064
H_amplitude[0] 37.455 0.362 37.106 37.808
H_amplitude[1] 38.676 3.628 35.510 42.706
H_amplitude[2] 37.453 2.792 34.642 40.134
He_amplitude[0] 2.984 0.348 2.633 3.321
He_amplitude[1] 5.207 0.386 4.902 5.664
He_amplitude[2] 2.030 0.500 1.499 2.497
He_center[0] -152.140 0.114 -152.251 -152.027
He_center[1] -116.947 0.940 -117.893 -116.018
He_center[2] -96.607 1.381 -97.949 -95.198
He_fwhm[0] 20.772 1.836 18.958 22.546
He_fwhm[1] 25.936 1.979 23.754 27.637
He_fwhm[2] 28.656 2.760 26.003 31.532
He_area[0] 65.844 8.719 56.669 73.864
He_area[1] 143.813 15.864 129.455 160.414
He_area[2] 62.151 17.245 45.132 79.168
[ ]:
"""
    "H_area": [1000.0, 1500.0, 1250.0],
    "H_center": [-30.0, 5.0, 25.0],
    "H_fwhm": [35.0, 50.0, 20.0],
    "He_H_fwhm_ratio": [1.0, 0.9, 1.1],
    "yplus": [0.05, 0.1, 0.15],
    "baseline_observation_norm": [-2.0, 1.5], # normalized baseline coefficients
"""