Electron Reflectometry¶
Electron reflectometry (ER) uses the electron energy–pitch-angle distribution measured on the spacecraft to infer where electrons heading toward the Moon were turned back (the reflection barrier). SOPRAN's experimental implementation fits the magnetic mirror ratio \(R_m\) and the potential difference \(\Delta U\) to the boundary of the loss cone in the pitch-angle distribution.
Limits of interpretation
A numerically converged fit does not mean the lunar surface field was measured. \(B_\mathrm{eff} = R_m B_\mathrm{sc}\) (nT) is an effective field describing the reflection boundary, not the crustal field vector. \(\Delta U\) is "surface potential − spacecraft potential" (eV), not the absolute surface potential. Decide whether to adopt a field value from the quality flags and profile intervals described below.
Choosing a method¶
There are two estimators: finite-bin and Halekas.
| Purpose | Method and settings |
|---|---|
| Main analysis | finite-bin with the beta-binomial count likelihood (default); enable angular transport \(D_\mathrm{out}\) as needed |
| Deciding whether to adopt a field | profile_mirror_ratio intervals and field_flags |
| Flux-only inputs, comparison with earlier analyses | finite-bin with loss="huber" (0.1 dex) |
| Comparison with a fixed 0.1 reflectance and outer level 1 | Halekas |
Model equations, objectives, and flag definitions are in ER Model Definitions.
Estimating from KAGUYA data¶
import sopran as spn
from sopran.experimental.kaguya.er import KaguyaErInstrument
from sopran.experimental.electron_reflection import (
FiniteBinFitSettings, FiniteBinObservation, HalekasFitSettings,
)
er = KaguyaErInstrument(spn.Kaguya())
pitch_counts = er.pitch_angle_spectrum(spn.day("2008-04-26"))
# magnetic_field and position are arrays or SopranArrays in the same frame
counts = er.paired_counts(
pitch_counts, index=0, magnetic_field=magnetic_field, position=position,
)
observation = FiniteBinObservation.from_counts(
counts, energy_edges_eV=energy_edges, pitch_edges_deg=folded_pitch_edges,
)
result = er.effective_field.fit_finite_bin(
observation, settings=FiniteBinFitSettings(angular_transport="out"),
)
profile = er.effective_field.profile_finite_bin(observation, result)
comparison = er.effective_field.fit_halekas(counts, settings=HalekasFitSettings())
Preparing inputs¶
KaguyaErInstrument shares ESA loading and calibration, LMAG loading with margins, and SPICE setup between its two loading entry points.
pitch_angle_spectra(..., align=False) returns pitch-angle spectra that keep each sensor's own times.
paired_counts builds an ElectronReflectionCounts (energy × folded pitch angle) that pairs 0–90° with 90–180° for one sample.
- The hemisphere of electrons coming back from the surface (after reflection) is "affected", and the hemisphere of electrons heading toward the surface (incident) is "reference".
When +B points away from the Moon, low pitch angles are affected; otherwise high pitch angles are.
affected_side="low"or"high"sets it explicitly. - Pass the calibrated, actual bin edges to finite-bin as
energy_edges_eVandpitch_edges_deg; edges are never inferred from centers. - Exposure is taken from an explicit value, then the spectrum's coordinates, then equal exposure. An explicit value must broadcast to energy × pitch angle of the selected sample. Assuming equal exposure is recorded in the input metadata.
Retrieval and calibration use the kaguya extra, estimation experimental, and plotting viz.
Reading results¶
result.effective_field_nT # B_eff
result.field_flags # reasons the field is not constrained
result.field_unconstrained # True when any field_flags is set
profile.interval_nT # 95% interval from re-optimizing with R_m fixed
No flags does not mean the field is uniquely determined. Use the profile interval to decide, and check individually any result whose interval
reaches the search range (interval_bounded=False) or where a better solution was found (improved_minimum=True).
In windows where the count and Huber losses land in different minima, the count-loss profile interval may contain both.
Judge identifiability by the interval width, not by the difference between point estimates.
Saving results¶
Estimates come back as typed dataclasses per method and are not saved to the Store automatically. Save them yourself together with the settings, inputs, masks, bins, and a code hash. For processing the full mission with fixed settings and code, see ER Full-Period Batch.
Module layout¶
| Location | Role |
|---|---|
electron_reflection/common.py |
Shared input type ElectronReflectionCounts, exposure validation, boundary formula mirror_boundary_sin2 |
electron_reflection/finite_bin.py |
finite-bin model, losses, loss-cone test, profiles |
electron_reflection/halekas.py |
Halekas model |
experimental/kaguya/er.py |
ESA1/ESA2, LMAG, and SPICE preparation, pitch-angle spectra, paired_counts, estimator calls |
experimental/kaguya/er_batch.py |
Full-period daily sharding, frozen settings, parallel fits, Store registration |
crates/sopran-native/src/finite_bin.rs |
finite-bin response, \(D_\mathrm{out}\), losses, and optimization (Rust) |
Retired estimators¶
The paired-count beta-binomial estimator (its count likelihood merged into finite-bin loss on 2026-10-10), binary surface, joint,
global-joint, incident, and integrated estimators, and their dedicated warm starts, likelihood scans, time series, catalogs, schemas, and evaluation helpers, have been removed.
Use fit_finite_bin and fit_halekas instead of the old fit, fit_joint, fit_global_joint, fit_timeseries, and build.
The former sopran-er-finite-bin JSONL CLI is also retired in favor of the PyO3 API. There are no compatibility aliases or fallbacks to old estimators.
Previously saved data remains in the Store and can be read as regular datasets.
Validation¶
| Target | Tests |
|---|---|
| finite-bin, \(D_\mathrm{out}\), losses (checked against scipy binomial and beta-binomial), count selection, loss-cone test, offset and prior, profile intervals, missing bins, fixed \(B_\mathrm{eff}\) | tests/test_er_finite_bin.py |
| Halekas hard/probit recovery, \(R_m<1\), search agreement | test_electron_reflection.py, test_electron_reflection_subunity.py, test_halekas_search.py |
| KAGUYA inputs, exposure, geometry, loading | test_kaguya_er.py, test_kaguya_er_geometry.py, test_kaguya_pace.py |
| The regular API does not load experiments | test_experimental_boundary.py |