User Guide¶
This package provides reusable optical material property definitions for fast integration with Geant4 geometries via pyg4ometry. It includes:
Ready-to-use functions to attach optical properties (wavelength-dependent and constant) to materials and optical surfaces.
A pluggable store mechanism to override or extend material properties without forking the package.
Utilities to read spectra from data files and interpolate them with physical units using pint.
See concrete geometry integrations and usage examples in:
LEGEND-200: legend-exp/legend-pygeom-l200
LEGEND-1000: legend-exp/legend-pygeom-l1000
These repositories demonstrate how this package is used to populate material properties in full detector geometries.
High-level API: attaching optical properties¶
Most materials expose convenience functions with the prefix
pyg4_<material>_attach_*. These functions add properties to a Geant4 material
(or optical surface) using pint-aware helpers that ensure correct units and
sorting. Typical property names include:
RINDEX: refractive index (dimensionless)ABSLENGTH: absorption length (length)RAYLEIGH: Rayleigh scattering length (length)REFLECTIVITY,EFFICIENCY: surface/optical parameters (dimensionless)WLSABSLENGTH,WLSCOMPONENT,WLSTIMECONSTANT: wavelength-shifting propertiesSCINTILLATIONCOMPONENT*,SCINTILLATIONTIMECONSTANT*,RESOLUTIONSCALE: scintillation models
Examples of high-level attachers:
Liquid argon (LAr):
pygeomoptics.lar.pyg4_lar_attach_rindex(),pygeomoptics.lar.pyg4_lar_attach_attenuation(),pygeomoptics.lar.pyg4_lar_attach_scintillation()PEN:
pygeomoptics.pen.pyg4_pen_attach_rindex(),pygeomoptics.pen.pyg4_pen_attach_attenuation(),pygeomoptics.pen.pyg4_pen_attach_wls(),pygeomoptics.pen.pyg4_pen_attach_scintillation()TPB:
pygeomoptics.tpb.pyg4_tpb_attach_rindex(),pygeomoptics.tpb.pyg4_tpb_attach_wls()Fibers:
pygeomoptics.fibers.pyg4_fiber_core_attach_rindex(),pygeomoptics.fibers.pyg4_fiber_core_attach_absorption(),pygeomoptics.fibers.pyg4_fiber_core_attach_wls(),pygeomoptics.fibers.pyg4_fiber_core_attach_scintillation()(plus cladding 1/2 rindex attachers)Reflectors:
pygeomoptics.tetratex.pyg4_tetratex_attach_reflectivity(),pygeomoptics.tyvek.pyg4_tyvek_attach_reflectivity(),pygeomoptics.vm2000.pyg4_vm2000_attach_wls()(see module for more: rindex, reflectivity, border params)Substrates/metals:
pygeomoptics.copper.pyg4_copper_attach_reflectivity(),pygeomoptics.germanium.pyg4_germanium_attach_reflectivity()Semiconductors/glass:
pygeomoptics.silicon.pyg4_silicon_attach_complex_rindex(),pygeomoptics.silica.pyg4_silica_attach_rindex()Others:
pygeomoptics.nylon,pygeomoptics.ultem,pygeomoptics.water,pygeomoptics.pmts
Minimal usage outline (pseudo-code):
# Minimal outline with pyg4ometry (exact construction details may vary):
import pint
import pyg4ometry.geant4 as g4
from pygeomoptics.lar import (
pyg4_lar_attach_rindex,
pyg4_lar_attach_attenuation,
pyg4_lar_attach_scintillation,
)
u = pint.get_application_registry().get()
reg = g4.Registry()
# Create your material in pyg4ometry here (example only; adjust to your setup)
lar_mat = g4.Material(name="LAr", density=1.396, registry=reg) # density in g/cm**3
# Attach optical properties
pyg4_lar_attach_rindex(lar_mat, reg)
pyg4_lar_attach_attenuation(lar_mat, reg, lar_temperature=88.8 * u.K)
pyg4_lar_attach_scintillation(lar_mat, reg)
Under the hood, the helpers use pygeomoptics.pyg4utils to patch
pyg4ometry with pint-aware methods:
Material.addVecPropertyPint(name, energy, values)Material.addConstPropertyPint(name, value)
These ensure proper unit handling and ascending-energy ordering.
Modifying properties without forking (pluggable store)¶
Many property functions are decorated with
pygeomoptics.store.register_pluggable(). This makes them dynamically
replaceable at runtime.
Common operations:
from pygeomoptics import store
from pygeomoptics.pen import pen_refractive_index
# Replace a property implementation
def my_pen_rindex() -> float:
return 1.52 # new constant refractive index
pen_refractive_index.replace_implementation(my_pen_rindex)
# Query which functions were replaced
assert "pen_refractive_index" in store.get_replaced()
# Reset just this function
pen_refractive_index.reset_implementation()
# Or reset everything back to defaults
store.reset_all_to_original()
Note: Any imported function that is decorated with @store.register_pluggable
can be replaced. The wrapper preserves the original function and exposes:
wrap.replace_implementation(new_impl)wrap.reset_implementation()wrap.is_original()wrap.original_impl()
Adding a new material¶
To integrate a new material in the same style:
Provide data-backed property functions
Use
pygeomoptics.utils.readdatafile()to read spectra with units from a data file included in a Python package (defaultpygeomoptics.data).Use
pygeomoptics.utils.InterpolatingGraphfor interpolation with correct unit handling.Decorate property functions you want to be overridable with
@store.register_pluggable.
Example:
import numpy as np
import pint
from pygeomoptics import store
from pygeomoptics.utils import readdatafile, InterpolatingGraph
u = pint.get_application_registry()
@store.register_pluggable
def mymat_refractive_index() -> float:
return 1.37
@store.register_pluggable
def mymat_absorption() -> tuple[u.Quantity, u.Quantity]:
λ, L = readdatafile("mymat_absorption.dat") # "# unit1 unit2" header expected
return λ, L
Implement pyg4 attachers
Sample wavelengths with
pygeomoptics.pyg4utils.pyg4_sample_λ()where needed.Convert wavelength to energy inside a pint context and attach via
addVecPropertyPintandaddConstPropertyPint.
Example:
import numpy as np
from pygeomoptics.pyg4utils import pyg4_sample_λ
import pint
u = pint.get_application_registry()
def pyg4_mymat_attach_rindex(mat, reg):
λ = np.array([650.0, 115.0]) * u.nm
r = [mymat_refractive_index()] * 2
with u.context("sp"):
mat.addVecPropertyPint("RINDEX", λ.to("eV"), r)
def pyg4_mymat_attach_absorption(mat, reg):
λ, L = mymat_absorption()
with u.context("sp"):
mat.addVecPropertyPint("ABSLENGTH", λ.to("eV"), L)
Optional: WLS and scintillation
Follow patterns in
pen.py,tpb.py,fibers.py, andlar.py:WLS:
WLSABSLENGTH,WLSCOMPONENT,WLSTIMECONSTANT, optionalWLSMEANNUMBERPHOTONSScintillation: define a
pygeomoptics.scintillate.ScintConfigand usepygeomoptics.pyg4utils.pyg4_def_scint_by_particle_type().
Include data files
Place spectral data in an importable package (e.g., the
datadirectory in the packagemypkgis importable asmypkg.data)Format: first line header with units (
# unit1 unit2), then pairs of numbers; comments allowed after#(after the header line).
CLI helper¶
A small CLI (defined in pygeomoptics.cli) can generate
G4GeneralParticleSource emission spectra:
legend-pygeom-optics g4gps lar_emission out.mac
legend-pygeom-optics g4gps pen_emission out.mac
legend-pygeom-optics g4gps fiber_emission out.mac
This uses the same emission spectra as the attachers.
Practical examples¶
pygeomtools(central material definitions): legend-exp/legend-pygeom-toolsLEGEND-200: legend-exp/legend-pygeom-l200
LEGEND-1000: legend-exp/legend-pygeom-l1000
The three repositories illustrate how materials are defined with their attachers, and how to manage optical surfaces and properties consistently across a large detector model.
Tips and best practices¶
Always use pint quantities and the provided attach helpers to avoid unit mistakes.
When interpolating spectra, use
InterpolatingGraphto handle extrapolation bounds predictably and similar to Geant4.For wavelength/energy conversions, use a pint context, e.g.
with u.context("sp").Prefer overriding pluggable functions instead of patching code. This keeps your runs reproducible and centralized.
Keep emission spectra zeroed at sampling boundaries to avoid artifacts (see
pen.py,fibers.py,lar.pypatterns).