Validation of Gain with Monte Carlo

Scott Prahl

July 2025

This notebook compares the calculated gain values against those from a Monte Carlo simulation of light bouncing in spheres.

Since these are stochastic tests, it is likely that a few of the results will be outside the 90% confidence interval.

[1]:
import numpy as np
import scipy.stats as stats

import iadpython

%config InlineBackend.figure_format='retina'


def p_value(x, x_std_err, y):
    """p-value that x matches known y."""
    # Calculate the z-score
    z_score = (y - x) / x_std_err

    # Calculate the probability from the z-score
    p = 2 * (1 - stats.norm.cdf(abs(z_score)))

    return p


def calculate_confidence_interval(data, confidence=0.90):
    """Compute `calculate_confidence_interval`.

    Args:
        data: Input parameter.
        confidence: Input parameter.

    Returns:
        Computed result from `calculate_confidence_interval`.
    """
    n = len(data)
    mean = np.mean(data)
    std_dev = np.std(data, ddof=1)  # ddof=1 returns the sample standard deviation
    std_err = std_dev / np.sqrt(n)

    # Find the critical value for the 90% confidence interval
    t_critical = stats.t.ppf((1 + confidence) / 2, df=n - 1)

    margin_of_error = t_critical * std_err
    confidence_interval = (mean - margin_of_error, mean + margin_of_error)

    return mean, confidence_interval


def test_gain(s, N=100000):
    """Compute `test_gain`.

    Args:
        s: Input parameter.
        N: Input parameter.
    """
    analytic_gain = s.gain()

    gains, _ = s.do_N_photons_gain_array(N, num_trials=20)
    _, ci = calculate_confidence_interval(gains)

    status = "bad"
    if ci[0] <= analytic_gain <= ci[1]:
        status = "good"

    print("upper 90%% ci       = %.3f" % ci[1])
    print("analytic gain      = %.3f (%s)" % (analytic_gain, status))
    print("lower 90%% ci       = %.3f" % ci[0])


def default_sphere():
    """Compute `default_sphere`.

    Returns:
        Computed result from `default_sphere`.
    """
    R = 30
    d_sphere = 2 * R
    d_sample = 20
    d_third = 15
    d_detector = 10
    s = iadpython.Sphere(
        d_sphere,
        d_sample,
        d_third=d_third,
        d_detector=d_detector,
        r_detector=0.5,
        r_wall=0.75,
        r_std=0.8,
    )
    return s

Check no baffle calculations

case 1, third = empty, sample = empty, no baffle

[10]:
print("case 1, third = empty, sample = empty, no baffle")
s = default_sphere()
s.sample.uru = 0.0
s.third.uru = 0.00
print(repr(s))
test_gain(s)
print()
case 1, third = empty, sample = empty, no baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= False
    Sample: d= 20.0mm, uru=  0.0%
     Third: d= 15.0mm, uru=  0.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.699
analytic gain      = 3.518 (good)
lower 90% ci       = 3.515

case 2, third = empty, sample = 50%, no baffle

[11]:
print("case 2, third = empty, sample = 50%, no baffle")
s = default_sphere()
s.sample.uru = 0.5
s.third.uru = 0.0
print(repr(s))
test_gain(s)
print()
case 2, third = empty, sample = 50%, no baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= False
    Sample: d= 20.0mm, uru= 50.0%
     Third: d= 15.0mm, uru=  0.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.766
analytic gain      = 3.698 (good)
lower 90% ci       = 3.591

case 3, third = 95%, sample = 50%, no baffle

[12]:
print("case 3, third = 95%, sample = 50%, no baffle")
s = default_sphere()
s.sample.uru = 0.5
s.third.uru = 0.95
print(repr(s))
test_gain(s)
case 3, third = 95%, sample = 50%, no baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= False
    Sample: d= 20.0mm, uru= 50.0%
     Third: d= 15.0mm, uru= 95.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 4.085
analytic gain      = 3.913 (good)
lower 90% ci       = 3.901

Check baffle calculations

case 1, third = empty, sample = empty, baffle

[5]:
print("case 1, third = empty, sample = empty, baffle")
s = default_sphere()
s.baffle = True
s.sample.uru = 0.0
s.third.uru = 0.00
print(repr(s))
test_gain(s)
print()
case 1, third = empty, sample = empty, baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= True
    Sample: d= 20.0mm, uru=  0.0%
     Third: d= 15.0mm, uru=  0.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.661
analytic gain      = 3.506 (bad)
lower 90% ci       = 3.525

case 2, third = empty, sample = 50%, baffle

[6]:
print("case 2, third = empty, sample = 50%, baffle")
s = default_sphere()
s.baffle = True
s.sample.uru = 0.5
s.third.uru = 0.0
print(repr(s))
test_gain(s)
print()
case 2, third = empty, sample = 50%, baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= True
    Sample: d= 20.0mm, uru= 50.0%
     Third: d= 15.0mm, uru=  0.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.751
analytic gain      = 3.637 (good)
lower 90% ci       = 3.597

case 3, third = 95%, sample = 50%, baffle

[16]:
print("case 3, third = 95%, sample = 50%, baffle")
s = default_sphere()
s.baffle = True
s.sample.uru = 0.5
s.third.uru = 0.95
print(repr(s))
test_gain(s)
case 3, third = 95%, sample = 50%, baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= True
    Sample: d= 20.0mm, uru= 50.0%
     Third: d= 15.0mm, uru= 95.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.969
analytic gain      = 3.849 (good)
lower 90% ci       = 3.777

case 4, third = 25, sample = 95%, baffle

[18]:
print("case 4, third = 25%, sample = 95%, baffle")
s = default_sphere()
s.baffle = True
s.sample.uru = 0.95
s.third.uru = 0.25
print(repr(s))
test_gain(s)
case 4, third = 25%, sample = 95%, baffle
Sphere: d=60.00, r_wall= 75.0%, r_std= 80.0%, baffle= True
    Sample: d= 20.0mm, uru= 95.0%
     Third: d= 15.0mm, uru= 25.0%
  Detector: d= 10.0mm, uru= 50.0%

upper 90% ci       = 3.882
analytic gain      = 3.821 (good)
lower 90% ci       = 3.705
[ ]: