337.1 / 777.4 nm photometer

Physicist verification companion · source release r4 · mathematical checks and unresolved limits

Review index · Download editable notebook

Executed top-to-bottom in a fresh kernel.
Cell 0 · 45428974c97b

04. Clear-air and ideal-model diagnostics

This notebook makes the analytic models inspectable and independently recalculates their tabulated values. It does not replace cloud radiative transfer or validate source depth from morphology. The models are useful precisely because their assumptions and failure modes can be written down.

Stable identifiers P01-P09 refer to methods/design_and_diagnostics.md. The independent code does not import the reference scripts. Poisson cut values used in the FOV/auroral conversion rows are declared inputs and must be checked in the statistics notebook.

Cell 1 · 327d0ac33ceb · execution 1
from pathlib import Path
import sys, os, json, math, hashlib
import numpy as np
from io import BytesIO
from IPython.display import display, Image, Markdown
candidates = [Path.cwd(), *Path.cwd().parents]
ROOT = next(p for p in candidates if (p/'data/design_results.json').exists() and (p/'modules/design_review.py').exists())
if (ROOT/'runtime/deps').is_dir(): sys.path.insert(0, str(ROOT/'runtime/deps'))
sys.path.insert(0, str(ROOT))
os.environ.setdefault('MPLCONFIGDIR', str(ROOT/'checks/diagnostics_mplconfig'))
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from modules import design_review as v
from modules.common import table
plt.rcParams.update({'figure.figsize': (8.2, 4.3), 'font.size': 10, 'axes.grid': True, 'grid.alpha': .22})
def show(fig):
    b=BytesIO(); fig.savefig(b, format='png', dpi=125, bbox_inches='tight')
    display(Image(data=b.getvalue())); plt.close(fig)
design = v.load('design_results.json')
analytic = v.load('analytic_results.json')
print('Independent module:', ROOT/'modules/design_review.py')
print('SHA256:', hashlib.sha256((ROOT/'modules/design_review.py').read_bytes()).hexdigest())
Independent module: D:\Photometer_research_20261022\verification_20261022_r1\modules\design_review.py
SHA256: af4c3056a61716d48c5eb8eec3f1abb67be7dee123d78c6bbc7c6f6cc37322ca
Cell 2 · 963d7a1d5c44

P01-P02. A standard atmospheric column, not an invented cloud

The retained dry USSA1976 layers specify temperature and hydrostatic pressure in geopotential altitude H=Rz/(R+z)H=Rz/(R+z). Rather than repeating the original 1 m density trapezoid, use

Ncol=R∗g0MkB∫dp[1−H(p)/R]2+Ntail,τR=σRNcol,Tdirect=e−τR.N_{col}={R^*\over g_0 M k_B}\int {dp\over[1-H(p)/R]^2}+N_{tail},\qquad \tau_R=\sigma_RN_{col},\quad T_{direct}=e^{-\tau_R}.
Inspect original LaTeX
N_{col}={R^*\over g_0 M k_B}\int {dp\over[1-H(p)/R]^2}+N_{tail},\qquad \tau_R=\sigma_RN_{col},\quad T_{direct}=e^{-\tau_R}.

The same small pressure-tail approximation above about 86 km is exposed, not replaced by a fabricated thermosphere. The original exponential CloudScat profile is a separate reference with its own sea-level number density/scale height. Neither profile is polar weather, ozone/aerosol absorption or total cloudy directional transmission. The molecular fit retains the original nominal wavelength coordinate.

Cell 3 · cbc5f7cc0192 · execution 2
atm=v.ReferenceAtmosphere();sigma,tau0=v.rayleigh_bodhaine(337.1e-9);sc,n0,Hscale=v.rayleigh_frozen(337.1e-9)
rows=[]
for z in [0.,15000.,16000.,18000.,18500.]:
 p,T=atm.state(z);col,tail=atm.pressure_column(z,96);hi,_=atm.pressure_column(z,192)
 rows.append({'z_km':z/1000,'p_Pa':p,'T_K':T,'column_m-2':col,'tail_m-2':tail,
   'quadrature96_vs192_relative':abs(hi-col)/col,'standard_tau':sigma*col,'standard_direct':math.exp(-sigma*col),
   'frozen_direct':math.exp(-sc*n0*Hscale*math.exp(-z/Hscale))})
table(rows)
fig,ax=plt.subplots();sel=rows[1:]
ax.plot([r['z_km'] for r in sel],[r['standard_direct'] for r in sel],'o-',label='standard density column')
ax.plot([r['z_km'] for r in sel],[r['frozen_direct'] for r in sel],'s-',label='frozen exponential profile')
ax.set(xlabel='Geometric source altitude (km)',ylabel='Uncollided molecular transmission',title='Reference profiles; no cloud/gas absorption or polar weather');ax.legend();show(fig)
z_kmp_PaT_Kcolumn_m-2tail_m-2quadrature96_vs192_relativestandard_taustandard_directfrozen_direct
0101325288.152.1531676e+298.131782e+2300.739215860.477488190.51475381
1512111.826216.652.585234e+288.131782e+2300.0887550960.915069650.92064028
1610352.832216.652.2104888e+288.131782e+2300.075889510.926918620.93056479
187565.2345216.651.6163322e+288.131782e+2300.0554911920.946020360.94694913
18.56994.8203216.651.4947028e+288.131782e+2300.0513154680.949978930.95041843

5 rows total; 5 shown.

Executed notebook figure
Cell 4 · f7ac740643f7

P03. Direct dilution versus diffuse light

For an isotropic point source and small nadir aperture, fdirect=Aexp⁡[−τR−τcloud]/(4πr2)f_{direct}=A\exp[-\tau_R-\tau_{cloud}]/(4\pi r^2). This is the fraction of all emitted photons crossing the aperture without collision. Detector efficiency is applied separately. Scattering redirects photons; it does not automatically absorb them.

The source/satellite altitude difference sets range. A source at or above the cloud top has zero overhead cloud optical depth, but downward light can still return from the cloud below. Therefore the table is a ballistic reference, not a total cloud-transfer curve.

Cell 5 · 18e0c902f2d6 · execution 3
area=v.aperture_area(.025);rows=[]
for z in [15000.,18000.]:
 col,_=atm.pressure_column(z);tau=sigma*col;distance=550000.-z
 for tc in [0.,.1,.3,1.,3.,10.]:
  fraction=v.direct_fraction(area,distance,tau,tc)
  rows.append({'source_km':z/1000,'overlying_cloud_tau':tc,'range_km':distance/1000,
   'direct_aperture_fraction':fraction,'detected_fraction_assumed_eta0.2':.2*fraction})
table(rows,max_rows=12)
source_kmoverlying_cloud_taurange_kmdirect_aperture_fractiondetected_fraction_assumed_eta0.2
1505351.2488395e-162.497679e-17
150.15351.1299967e-162.2599934e-17
150.35359.2516305e-171.8503261e-17
1515354.5942237e-179.1884474e-18
1535356.2176057e-181.2435211e-18
15105355.6697225e-211.1339445e-21
1805321.3056815e-162.611363e-17
180.15321.1814295e-162.362859e-17
180.35329.6727265e-171.9345453e-17
1815324.8033338e-179.6066676e-18
1835326.5006054e-181.3001211e-18
18105325.9277849e-211.185557e-21

12 rows total; 12 shown.

Cell 6 · bf5fa7f2a3f2

P04-P05. A cloud-below counterexample with an explicit surface model

Use an infinite plane with instantaneous Lambert reflection ρ/π\rho/\pi, an isotropic point source height hh above it and an observer height HH above it. Integrate ring contributions with both projection factors. The code uses the independent coordinate q=ln⁡(1+r/h)q=\ln(1+r/h).

Extra delay is [h2+r2+H2+r2−(H−h)]/c[\sqrt{h^2+r^2}+\sqrt{H^2+r^2}-(H-h)]/c. Common direct flight time has been subtracted. At h→0h\to0, directional gain is 1+2ρ1+2\rho, but all-direction upward escape is only (1+ρ)/2(1+\rho)/2. This creates no photons.

The pulse is an infinite plateau after a linear ramp. Each plotted shape is normalized to its own final plateau. There is no finite-cloud residence time, gas, physical source extent, electronics or noise. The h=0 mathematical limit is not a real embedded cloud-top boundary.

Cell 7 · 270652e7f838 · execution 4
rows=[];fig,ax=plt.subplots();times=np.linspace(0,200e-6,160)
for h in [0.,100.,1000.,3000.]:
 model=v.LambertPlane(535000.,h,1.,math.pi/6)
 rise=model.rise(30e-6)*1e6
 rows.append({'height_above_plane_m':h,'nadir_vacuum_gain':1+model.B,'total_upward_fraction':1.,
              'minimum_extra_delay_us':2*h/v.C*1e6,'output10-90_us':rise})
 ax.plot(times*1e6,[model.received_ramp(t,30e-6/.8) for t in times],label=f'h={h:g}m')
table(rows);ax.set(xlabel='Retarded arrival time (µs)',ylabel='Response / own final plateau',title='Instantaneous Lambert surface, rho 1,30 µs input,60° field');ax.legend();show(fig)
height_above_plane_mnadir_vacuum_gaintotal_upward_fractionminimum_extra_delay_usoutput10-90_us
031030
1002.99823110.6671281930.450332
10002.982375916.671281939.353568
30002.9475617120.01384681.623177

4 rows total; 4 shown.

Executed notebook figure
Cell 8 · f6a81320fd47

P06. Diffusion: exact mathematics, limited physical scope

For a half-space with absorbing boundary, point injection at depth LL, diffusion DD and uniform killing rate γ\gamma,

h(t)=L4πDt−3/2exp⁡[−L2/(4Dt)−γt].h(t)={L\over\sqrt{4\pi D}}t^{-3/2}\exp[-L^2/(4Dt)-\gamma t].
Inspect original LaTeX
h(t)={L\over\sqrt{4\pi D}}t^{-3/2}\exp[-L^2/(4Dt)-\gamma t].

For γ=0\gamma=0, tpeak=L2/(6D)t_{peak}=L^2/(6D) and the leading amplitude 10-90rise is 0.07432773536L2/D0.07432773536 L^2/D. It is not onset-to-peak, a cumulative arrival quantile, or the rise after source convolution.

Exactly the same L2/DL^2/D and gamma produce exactly the same entire kernel. The example family therefore demonstrates an inverse degeneracy, not measured source altitudes. Also, parabolic diffusion has nonzero response before L/cL/c; it is not a causal early-time RTE benchmark.

Cell 9 · a4464f1690fe · execution 5
fig,axs=plt.subplots(1,2,figsize=(11,4));tt=np.linspace(1e-6,700e-6,550)
for i,(L,D) in enumerate([(1000.,3e9),(2000.,1.2e10),(3000.,2.7e10)]):
 y=v.diffusion_kernel(tt,L,D)
 axs[0].plot(tt*1e6,y,label=f'L {L/1000:g} km, D {D:.2g}',ls=['-','--',':'][i],lw=2)
axs[0].set(xlabel='Time since ideal impulse (µs)',ylabel='First-exit density (s⁻¹)',title='Equal L²/D: identical entire kernels');axs[0].legend(fontsize=8)
D=7.9e10;coefficient=v.diffusion_rise(1.,1.)[1]-v.diffusion_rise(1.,1.)[0]
L=math.sqrt(D*5.5e-6/coefficient);t10,t90,peak=v.diffusion_rise(L,D)
tt=np.linspace(.05e-6,50e-6,600);y=v.diffusion_kernel(tt,L,D)
axs[1].plot(tt*1e6,y/y.max());axs[1].axvspan(0,L/v.C*1e6,color='tomato',alpha=.2,label='before straight light flight')
axs[1].axvline(t10*1e6,color='black',ls='--',label='diffusion 10% threshold')
axs[1].set(xlabel='Time since ideal impulse (µs)',ylabel='Density / own peak',title='High D early-time approximation can be acausal');axs[1].legend(fontsize=8);show(fig)
print('Example depth:',L,'m;10%time:',t10*1e6,'µs; straight flight:',L/v.C*1e6,'µs')
Executed notebook figure
Example depth: 2417.794804432272 m;10%time: 3.1665804131838793 µs; straight flight: 8.064895363152438 µs
Cell 10 · 1e9dc8dc31b4

The all-time exit probability is exp⁡[−Lγ/D]\exp[-L\sqrt{\gamma/D}]. The finite-time closed CDF is

F(T)=12[e−berfc(a−q)+eberfc(a+q)],a=L/4DT, q=γT, b=Lγ/D.F(T)=\tfrac12[e^{-b}\mathrm{erfc}(a-q)+e^b\mathrm{erfc}(a+q)],\quad a=L/\sqrt{4DT},\ q=\sqrt{\gamma T},\ b=L\sqrt{\gamma/D}.
Inspect original LaTeX
F(T)=\tfrac12[e^{-b}\mathrm{erfc}(a-q)+e^b\mathrm{erfc}(a+q)],\quad a=L/\sqrt{4DT},\ q=\sqrt{\gamma T},\ b=L\sqrt{\gamma/D}.

The code also integrates the kernel numerically in log-u coordinates. Gamma is a uniform model parameter, not a measured ice or ozone profile. A 3 ms integration window does not imply a 3 ms intrinsic event or molecular lifetime.

Cell 11 · c0c019383096 · execution 6
L=1000.;D=3e9;rows=[]
for gamma in [0.,.1,10.,50.]:
 numeric=v.diffusion_exit_integral(L,D,gamma,.003);closed=v.diffusion_cdf(.003,L,D,gamma)
 total=math.exp(-L*math.sqrt(gamma/D))
 rows.append({'gamma_s-1_assumed':gamma,'all_time_exit':total,'within3ms_closed':closed,
    'within3ms_independent_quadrature':numeric,'fraction_of_eventual_exit_in3ms':closed/total})
table(rows)
gamma_s-1_assumedall_time_exitwithin3ms_closedwithin3ms_independent_quadraturefraction_of_eventual_exit_in3ms
010.813663720.813663720.81366372
0.10.994243130.813622410.813622410.81833344
100.943900020.809558620.809558620.85767412
500.878886560.793645130.793645130.90301202

4 rows total; 4 shown.

Cell 12 · d51ff88b125e

P07. Raw dead time can compress a rise

The nonparalyzable stationary mean is m=r/(1+rτ)m=r/(1+r\tau). For a true ramp b+Sfb+Sf, baseline-subtracted registered fraction is y=f(1+x)/(1+xf)y=f(1+x)/(1+xf) with x=Sτ/(1+bτ)x=S\tau/(1+b\tau). Therefore Tout/Tin=(1+x)/(1+x+0.09x2)T_{out}/T_{in}=(1+x)/(1+x+0.09x^2).

The independent implementation instead inverts each registered amplitude using r=m/(1−mτ)r=m/(1-m\tau). Tau=17ns is a retained study assumption, not a universal detector property. This is a quasi-static raw-mean diagnostic. It does not accuse corrected published MMIA data of the bias, simulate lost-count likelihood, or restore Poisson statistics after inversion.

Cell 13 · 7973c83293b3 · execution 7
signals=np.logspace(6,10,120);ratios=[v.deadtime_fractions(50.,s,17e-9)[2] for s in signals]
fig,ax=plt.subplots();ax.semilogx(signals,200*np.array(ratios));ax.axhline(200,color='black',ls='--',label='true 200 µs')
ax.set(xlabel='True peak source rate (s⁻¹)',ylabel='Uncorrected mean10-90rise (µs)',title='Assumed 17 ns nonparalyzable counter, background 50/s');ax.legend();show(fig)
table([{'true_peak_signal_s':s,'registered_peak_total_s':v.deadtime_mean(s+50,17e-9),
        'raw200us_rise_us':200*v.deadtime_fractions(50,s,17e-9)[2]} for s in [1e6,1e8,1e9,1e10]])
Executed notebook figure
true_peak_signal_sregistered_peak_total_sraw200us_rise_us
1000000983332.51199.99489
1e+0837037044182.42629
1e+095555555681.799634
1e+105847953212.337672

4 rows total; 4 shown.

Cell 14 · 5d8a734dc03a

P08-P09. A field/population sensitivity, not a useful event rate

Use independent shell-central angle psi. The cloud cap area is 4πrc2sin⁡2(ψmax/2)4\pi r_c^2\sin^2(\psi_{max}/2), and a point source needs Ncut=4πr2Srequired/(ηAcos⁡θ)N_{cut}=4\pi r^2S_{required}/(\eta A\cos\theta).

For an assumed cumulative law CN−αC N^{-\alpha}, integrate W=∫[Ncut/Npivot]−αdAcW=\int [N_{cut}/N_{pivot}]^{-\alpha}dA_c. W is an area-weighted bright-enough population proxy. Its pivot 102010^{20}photons is arbitrary, not a source observation. Source geography, occurrence, exposure and the actual brightness law are unknown.

The supplied ideal-count SrequiredS_{required} is used as an input; its Poisson threshold is checked in the statistics notebook. The weight omits below-cut detections and is not an integral of the full detection probability. High counter-occupancy scenarios cannot be turned into a real17ns receiver forecast.

Cell 15 · 376c0634cab2 · execution 8
area=v.aperture_area(.025);rows=[]
for alpha in [.5,1.,1.5,2.,2.5,3.]:
 pair=[]
 for full in [60.,90.]:
  row=next(r for r in analytic['fov_population']['rows'] if r['assumed_received_background_W_m2_sr_nm']==3e-9 and r['diameter_mm']==25 and r['full_circular_FOV_deg']==full and r['assumed_cumulative_source_number_slope_alpha']==alpha)
  S=row['required_source_count_for_95pct_detection']
  pair.append(v.population_weight(S,area,.2,math.radians(full/2),alpha))
 rows.append({'assumed_alpha':alpha,'W60_m2':pair[0],'W90_m2':pair[1],'conditional90_to60_weight':pair[1]/pair[0]})
table(rows)
fig,ax=plt.subplots();ax.plot([r['assumed_alpha'] for r in rows],[r['conditional90_to60_weight'] for r in rows],'o-')
ax.axhline(1,color='black',ls='--');ax.set(xlabel='Assumed cumulative source-number slope alpha',ylabel='90°/60° bright-enough population weight',title='Conditional comparison; neither observed rate nor full detection integral');show(fig)
table([{'full_circular_deg':f,'footprint_km2':v.spherical_field(math.radians(f/2),6921000.,6388000.)[1]/1e6} for f in [10,30,60,90]])
assumed_alphaW60_m2W90_m2conditional90_to60_weight
0.55.8688433e+111.3105194e+122.2330113
11.1296109e+121.8035178e+121.5965831
1.52.1838217e+122.5483613e+121.1669274
24.2403965e+123.693945e+120.87113197
2.58.26944e+125.484472e+120.6632217
31.6195566e+138.3235294e+120.51393879

6 rows total; 6 shown.

Executed notebook figure
full_circular_degfootprint_km2
106835.8283
3064472.954
60306259.03
90977806.91

4 rows total; 4 shown.

Cell 16 · 82750e03e4f3

Full diagnostic comparison and the retained initial tolerance failure

This cell independently checks all declared analytic rows. Original n/S count thresholds are excluded from this module's scope; their conversions are checked. The first attempt used a relative tolerance on a nearly cancelled Rayleigh transmission difference and retained four tiny failures. The final method propagates the original 1 m/2 m column refinement to that derived residual and records an independent 96/192 pressure-coordinate comparison. This is an implementation comparison, not an atmospheric confidence interval.

Separate fault controls must reject wrong aperture fractions, etendue and photocurrent; reproduce them with python checks/diagnostics_negative_controls.py.

The raw Noll spectrum and Bianchi sky cube are not present in this curated bundle, so their actual source spectral folds are explicitly not newly integrated here. No frozen calculation is silently relabeled as a new observation.

Cell 17 · 7ded4245f05b · execution 9
result,recomputed=v.check_diagnostics(write=False)
assert result['status']=='PASS',result['failures']
table([{'table':k,'rows_checked':n} for k,n in result['rows_checked'].items()])
print('Status:',result['status'],'scalar comparisons:',result['comparisons'])
print('Independent 10-90 coefficient:',result['diffusion_rise_coefficient'])
table(result['rayleigh_grid_tolerance_audit'])
display(Markdown('**Explicit scope exclusions:**\n\n'+'\n'.join('- '+s for s in result['scope_exclusions'])))
tablerows_checked
rayleigh5
reflection108
diffusion48
deadtime84
fov_population216
auroral_conversion192

6 rows total; 6 shown.

Status: PASS scalar comparisons: 6955
Independent 10-90 coefficient: 0.07432773536040116
altitude_moriginal_1m_2m_column_relative_differenceindependent_96_vs_192_column_relative_differencepropagated_comparison_tolerance_percentage_pointsactual_difference_percentage_points
02.6097847e-0901.9292442e-073.5274389e-08
150006.099721e-0905.4143133e-081.6739521e-08
160006.0879139e-0904.6205881e-081.4504686e-08
180006.0599464e-0903.3632365e-081.0840395e-08
185006.0518635e-0903.1060421e-081.0071777e-08

5 rows total; 5 shown.

<IPython.core.display.Markdown object>
Cell 18 · 83fec990bebf

Primary references and applicability

All row/field comparisons, input hashes, original-file:function map, tolerance audit and missing physical measurements are exposed in checks/diagnostics_* and methods/design_and_diagnostics.md. Passing these conditional tests does not validate an anvil, a timing-depth classifier, actual detector radiation/noise, source current or mission yield.