Skip to content

Modified optika.sensors.signal() to model charge diffusion using a Monte-Carlo simulation.#162

Open
roytsmart wants to merge 13 commits into
mainfrom
feature/signal-charge-diffusion
Open

Modified optika.sensors.signal() to model charge diffusion using a Monte-Carlo simulation.#162
roytsmart wants to merge 13 commits into
mainfrom
feature/signal-charge-diffusion

Conversation

@roytsmart

Copy link
Copy Markdown
Collaborator

No description provided.

@roytsmart roytsmart force-pushed the feature/signal-charge-diffusion branch from 6f14ac3 to 58915e2 Compare June 2, 2026 21:30
roytsmart and others added 4 commits June 6, 2026 22:20
…or refactor.

Sensor pipeline:
- Rename the sensor methods to `collect` -> `expose` -> `measure`
  (was `bin_rays` -> `expose` -> `readout`), update the `_sequential.py` caller.
- `expose` centers the wavelength bin edges and is shared by any per-pixel
  photon image; tidy the empty-pixel mean-cosine division.

Review fixes (internal consistency of the breaking changes):
- `fit_eqe` now passes the cosine `direction=1` instead of a 3d vector to the
  (now cosine-based) standalone `quantum_efficiency_effective`. This was masked
  by the joblib cache, so a cold cache would have broken all three device
  materials; verified by running the fit uncached.
- Update the stale docstring examples that still used the old API
  (`quantum_efficiency_effective(rays=...)`, `width_charge_diffusion(rays=...)`,
  and a vector `direction` to the standalone QE); confirmed by a full doc build.
- Revert the `RayVectorArray.intensity` default back to `1` and fix a cosmetic
  typo in the new `RayVectorArray.n` property.

Add a `test_n` exercising the `RayVectorArray.n` complex-index property.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@roytsmart roytsmart force-pushed the feature/signal-charge-diffusion branch from 88ff3fc to 7057014 Compare June 7, 2026 18:47
@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.01961% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.30%. Comparing base (44177ca) to head (151a216).

Files with missing lines Patch % Lines
optika/sensors/materials/_materials.py 97.64% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #162      +/-   ##
==========================================
- Coverage   99.32%   99.30%   -0.03%     
==========================================
  Files         116      116              
  Lines        5797     5908     +111     
==========================================
+ Hits         5758     5867     +109     
- Misses         39       41       +2     
Flag Coverage Δ
unittests 99.30% <99.01%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

roytsmart and others added 9 commits June 7, 2026 13:00
- Document the `n` property (complex index of refraction: real part is the
  index of refraction, imaginary part is the extinction coefficient
  k = alpha * lambda / 4 pi).
- Run black on the sensor material modules to satisfy the formatting check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rename its first argument `photons_absorbed` -> `photons_transmitted` and
  model the transmitted->absorbed step (the fraction `1 - e^(-alpha t)` that is
  absorbed before escaping out the back), so it is now drop-in comparable with
  the Monte-Carlo `electrons_measured()`: at 5.9 keV the two agree to ~0.003%
  on the mean and ~0.6% on the std.
- Add the matching `thickness_substrate` parameter and align the shared
  parameter docstrings/summary with `electrons_measured()`.
- Fix two latent bugs: the docstring example called `electrons_measured`
  with the old `photons_absorbed=` keyword, and the broadcast shape accidentally
  referenced the module-level `absorbance` function.
- Update the MC summary line, which still said "photons absorbed" despite the
  argument now being `photons_transmitted`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Mark the defensive `ValueError` branches with `# pragma: nocover`, matching
  the existing convention (e.g. `_sequential.py`): the `expose` ambiguous
  wavelength-axis check and the `absorbance` method dispatch.
- Mark the `@numba.njit` `_electrons_measured_numba` kernel `# pragma: nocover`;
  JIT-compiled code cannot be traced by coverage.py.
- Extend `test_electrons_measured` with an `axis_xy` parametrize and a per-pixel
  photon grid so the charge-diffusion branch of `electrons_measured` is
  exercised. `_ramanathan_2020.py` is now at 100% line coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Parametrize `test_absorbance` over `method` ("Beer-Lambert" and "exact") so
  the exact (`layer_absorbance`) branch is exercised.
- Add a `transmittance` method test mirroring the other method tests, covering
  its `direction`/`normal` None-default handling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…otons_transmitted`.

The Monte-Carlo kernel now samples the absorption depth from an exponential
truncated to the light-sensitive region, so every input photon is treated as
absorbed. The escape fraction is applied as Poisson thinning in `signal()`
(exactly equivalent). `electrons_measured_approx()` reverts to splitting the
absorbed photons into partial/complete charge collection, and `signal()`
computes absorbance from the start rather than transmittance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…within the substrate.

The exact `electrons_measured` kernel samples the absorption depth from an
exponential truncated to the substrate `[0, D)`, so the approximate model must
do the same. The partial-collection fraction is now the implant fraction
conditioned on absorption within the substrate,
`(1 - exp(-alpha*W)) / (1 - exp(-alpha*D))`, which restores agreement between
`electrons_measured_approx` and `electrons_measured`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `signal()` `method` parameter docstring described nonexistent `exact`/
`approx` methods; it now documents the actual `monte-carlo` and `expected`
options, and notes that the per-pixel `expected` method applies no charge
diffusion.

Adds `test_electrons_measured_diffusion`, which injects photons into a single
pixel and checks that the spatial spread of the diffused charge matches the
analytic width from `optika.sensors.charge_diffusion`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…` method.

The per-pixel charge-collection model needs the cosine of the refracted angle
inside the light-sensitive region, which depends on the ambient index of
refraction. Rather than threading an ambient-index argument through
`expose`/`signal`, `collect` now refracts each ray with its own `rays.n` via the
new `AbstractSensorMaterial.direction_refracted` method and flux-weights the
resulting (complex) substrate-side cosine per pixel. `material.signal` consumes
this already-refracted cosine and forwards it to `signal` with equal
ambient/substrate indices so the internal Snell step is a no-op while the
effective absorption still uses the correct substrate index; its `n` parameter
is removed. `AbstractBackIlluminatedSiliconSensorMaterial.electrons_measured` is
refactored to reuse `direction_refracted`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant