From 3a893abc0ce4a7d18f0015cdc99bcf24d7e00754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Cambi=C3=A9?= Date: Tue, 10 Nov 2020 10:17:58 +0100 Subject: [PATCH 1/3] Add new component for "Reactor" and the associated Event REACT 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. --- pvtrace/algorithm/photon_tracer.py | 7 +++-- pvtrace/light/event.py | 1 + pvtrace/material/component.py | 41 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/pvtrace/algorithm/photon_tracer.py b/pvtrace/algorithm/photon_tracer.py index c672c0e..139be2f 100644 --- a/pvtrace/algorithm/photon_tracer.py +++ b/pvtrace/algorithm/photon_tracer.py @@ -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, @@ -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) diff --git a/pvtrace/light/event.py b/pvtrace/light/event.py index 10095a3..78933df 100644 --- a/pvtrace/light/event.py +++ b/pvtrace/light/event.py @@ -13,3 +13,4 @@ class Event(Enum): EMIT = 5 EXIT = 6 KILL = 7 + REACT = 8 diff --git a/pvtrace/material/component.py b/pvtrace/material/component.py index aa65c62..5c2f419 100644 --- a/pvtrace/material/component.py +++ b/pvtrace/material/component.py @@ -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. From 168b0e039f8085c32bd184a1e7652ef9a1f9e494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Cambi=C3=A9?= Date: Tue, 10 Nov 2020 10:48:13 +0100 Subject: [PATCH 2/3] Add Reactor to package imports --- pvtrace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvtrace/__init__.py b/pvtrace/__init__.py index ec0953b..78b4eb3 100644 --- a/pvtrace/__init__.py +++ b/pvtrace/__init__.py @@ -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 ( From b7f0c51685177da2f505bc6686fd0ee968ee6353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Cambi=C3=A9?= Date: Tue, 10 Nov 2020 11:03:57 +0100 Subject: [PATCH 3/3] Add test for Reactor --- tests/test_refractored_tracer.py | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/test_refractored_tracer.py b/tests/test_refractored_tracer.py index e3dc215..24a108c 100644 --- a/tests/test_refractored_tracer.py +++ b/tests/test_refractored_tracer.py @@ -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)", @@ -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(