Skip to content

Commit

Permalink
Add new component for "Reactor" and the associated Event.REACT (#44)
Browse files Browse the repository at this point in the history
Add  Reactor component and associated REACT event

The use of this component is to represent reaction mixtures in an LSC-PM
device where the absorbed photons are used to drive a photochemical reaction.

This is essentially identical to Absorber but associated with a different event
to easily distinguish the different photon fates at the simulation end.

* Add Reactor to package imports

* Add test for Reactor
  • Loading branch information
dcambie committed Nov 18, 2020
1 parent 4e9c5f1 commit 663e90c
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 3 deletions.
2 changes: 1 addition & 1 deletion pvtrace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@


# material
from .material.component import Scatterer, Absorber, Luminophore
from .material.component import Scatterer, Absorber, Luminophore, Reactor
from .material.distribution import Distribution
from .material.material import Material
from .material.surface import (
Expand Down
7 changes: 5 additions & 2 deletions pvtrace/algorithm/photon_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pvtrace.scene.node import Node
from pvtrace.light.ray import Ray
from pvtrace.light.event import Event
from pvtrace.material.component import Scatterer, Luminophore
from pvtrace.material.component import Scatterer, Luminophore, Reactor
from pvtrace.geometry.utils import (
distance_between,
close_to_zero,
Expand Down Expand Up @@ -184,7 +184,10 @@ def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"):
history.append((ray, event))
continue
else:
history.append((ray, Event.ABSORB))
if isinstance(component, Reactor):
history.append((ray, Event.REACT))
else:
history.append((ray, Event.ABSORB))
break
else:
ray = ray.propagate(full_distance)
Expand Down
1 change: 1 addition & 0 deletions pvtrace/light/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ class Event(Enum):
EMIT = 5
EXIT = 6
KILL = 7
REACT = 8
41 changes: 41 additions & 0 deletions pvtrace/material/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,47 @@ def is_radiative(self, ray):
return False


class Reactor(Absorber):
"""Describes a reaction mixture: photon absorbed cause photochemical transformation.
Examples
--------
Create `Reactor` with isotropic and constant probability of scattering::
Reactor(1.0)
"""

def __init__(self, coefficient, x=None, name="Reactor", hist=False):
""" coefficient: float, list, tuple or numpy.ndarray
Specifies the absorption coefficient per unit length. Constant values
can be supplied or a spectrum per nanometer per unit length.
If using a list of tuple you should also specify the wavelengths using
the `x` keyword.
If using a numpy array use `column_stack` to supply a single array with
a wavelength and coefficient values::
x: list, tuple of numpy.ndarray (optional)
Wavelength values in nanometers. Required when specifying a the
`coefficient` with an list or tuple.
name: str
A user-defined identifier string
hist: Bool
Specifies how the coefficient spectrum is sampled. If `True` the values
are treated as a histogram. If `False` the values are linearly
interpolated.
"""

super(Reactor, self).__init__(
coefficient,
x=x,
hist=hist,
name=name,
)


class Luminophore(Scatterer):
""" Describes molecule, nanocrystal or material which absorbs and emits light.
Expand Down
52 changes: 52 additions & 0 deletions tests/test_refractored_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ def make_embedded_lossy_scene(n1=1.5):
scene = Scene(world)
return scene, world, box

def make_embedded_lossy_scene_w_reactor(n1=1.5):
world = Node(
name="world (air)",
geometry=Sphere(
radius=10.0,
material=Material(refractive_index=1.0)
)
)
box = Node(
name="box (reactor)",
geometry=Box(
(1.0, 1.0, 1.0),
material=Material(
refractive_index=n1,
components=[
Reactor(coefficient=10.0)
]
),
),
parent=world
)
scene = Scene(world)
return scene, world, box

def make_embedded_lumophore_scene(n1=1.5):
world = Node(
name="world (air)",
Expand Down Expand Up @@ -207,6 +231,34 @@ def test_follow_lossy_embedded_scene_1():
assert np.allclose(expected_point, point, atol=EPS_ZERO)


def test_follow_lossy_embedded_scene_w_reactor():
ray = Ray(
position=(0.0, 0.0, -1.0),
direction=(0.0, 0.0, 1.0),
wavelength=555.0,
is_alive=True
)
scene, world, box = make_embedded_lossy_scene_w_reactor()
np.random.seed(0)
path = photon_tracer.follow(scene, ray)
path, events = zip(*path)
positions = [x.position for x in path]
expected_positions = [
(0.00, 0.00, -1.00), # Starting
(0.00, 0.00, -0.50), # Hit box
(0.00, 0.00, -0.3744069237034118), # Absorbed and reacted
]
expected_events = [
Event.GENERATE,
Event.TRANSMIT,
Event.REACT,
]
for expected_point, point, expected_event, event in zip(
expected_positions, positions, expected_events, events):
assert expected_event == event
assert np.allclose(expected_point, point, atol=EPS_ZERO)


def test_follow_embedded_lumophore_scene_1():

ray = Ray(
Expand Down

0 comments on commit 663e90c

Please sign in to comment.