From 8325a6aa111885e5e4b96de228d93ad08b06d914 Mon Sep 17 00:00:00 2001 From: mamintatarin Date: Sat, 6 Dec 2025 21:55:52 +0300 Subject: [PATCH 1/2] break condition & time tracing --- examples/001 Quick Start.ipynb | 4 +- examples/003 Lights.ipynb | 4 +- examples/006 Coatings.ipynb | 5 +-- pvtrace/algorithm/photon_tracer.py | 71 +++++++++++++++++++++++++----- pvtrace/light/ray.py | 43 ++++++++++++++++-- 5 files changed, 106 insertions(+), 21 deletions(-) diff --git a/examples/001 Quick Start.ipynb b/examples/001 Quick Start.ipynb index ac936fb..8942adb 100644 --- a/examples/001 Quick Start.ipynb +++ b/examples/001 Quick Start.ipynb @@ -220,7 +220,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -234,7 +234,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.7.8" } }, "nbformat": 4, diff --git a/examples/003 Lights.ipynb b/examples/003 Lights.ipynb index 2afbdf3..7a526d2 100644 --- a/examples/003 Lights.ipynb +++ b/examples/003 Lights.ipynb @@ -300,7 +300,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -314,7 +314,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.8" } }, "nbformat": 4, diff --git a/examples/006 Coatings.ipynb b/examples/006 Coatings.ipynb index b317f52..1467b72 100644 --- a/examples/006 Coatings.ipynb +++ b/examples/006 Coatings.ipynb @@ -18,7 +18,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -298,7 +297,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -312,7 +311,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.8" } }, "nbformat": 4, diff --git a/pvtrace/algorithm/photon_tracer.py b/pvtrace/algorithm/photon_tracer.py index 139be2f..952cf60 100644 --- a/pvtrace/algorithm/photon_tracer.py +++ b/pvtrace/algorithm/photon_tracer.py @@ -4,7 +4,7 @@ import collections import traceback import numpy as np -from typing import Optional, Tuple, Sequence +from typing import Optional, Tuple, Sequence, Callable from dataclasses import dataclass, replace from pvtrace.scene.scene import Scene from pvtrace.scene.node import Node @@ -109,7 +109,9 @@ def next_hit(scene, ray): return hit_node, (container, adjacent), point, distance -def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"): +SPEED_OF_LIGHT = 1 + +def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT", break_condition: Optional[Callable] = None): """ The main ray-tracing function. Provide a scene and a ray and get a full photon path history and list of events. @@ -135,6 +137,24 @@ def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"): `'full'` option samples the full emission spectrum allowing the emitted ray to take any value. + break_condition: Optional[Callable] + Custom decision function that iterrupts ray tracing earlier by adding user-defined rules. + This could reduce computation time or serve any other goal. + + Recieves parameters: + ray: Ray + A current Ray object that will be tracked to the next step + hit_node : Node + The node corresponding to the geometry object that was hit. + interface : tuple of Node + Two node: the `container` and the `adjacent` which correspond to the + materials either side of the interface. + point: tuple of float + The intersection point. + distance: float + Distance to the intersection point. + + Returns boolean. If True, then ray tracing will be interrupted with Event.KILL Returns ------- @@ -154,8 +174,9 @@ def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"): history = [(ray, Event.GENERATE)] while True: count += 1 + if count > maxsteps or ray.travelled > maxpathlength: - history.append([ray, Event.KILL]) + history.append((ray, Event.KILL)) break info = next_hit(scene, ray) @@ -168,35 +189,61 @@ def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"): break material = container.geometry.material + refractive_index = material.refractive_index absorbed, at_distance = material.is_absorbed(ray, full_distance) if absorbed: ray = ray.propagate(at_distance) + ray = ray.add_time(refractive_index*at_distance/SPEED_OF_LIGHT) + time = ray.time component = material.component(ray.wavelength) if component.is_radiative(ray): ray = component.emit( ray.representation(scene.root, container), method=emit_method ) ray = ray.representation(container, scene.root) + ray = ray.set_time(time) if isinstance(component, Luminophore): event = Event.EMIT elif isinstance(component, Scatterer): event = Event.SCATTER - history.append((ray, event)) + else: + raise ValueError('Only Luminophore and Scatterer allowed for radiative component') + + if break_condition is not None and break_condition(ray, *info, event): + history.append((ray, Event.KILL)) + break + else: + history.append((ray, event)) + continue else: if isinstance(component, Reactor): - history.append((ray, Event.REACT)) + event = Event.REACT else: - history.append((ray, Event.ABSORB)) + event = Event.ABSORB + + if break_condition is not None and break_condition(ray, *info, event): + history.append((ray, Event.KILL)) + break + else: + history.append((ray, event)) + break else: ray = ray.propagate(full_distance) + ray = ray.add_time(refractive_index*full_distance/SPEED_OF_LIGHT) surface = hit.geometry.material.surface ray = ray.representation(scene.root, hit) if surface.is_reflected(ray, hit.geometry, container, adjacent): ray = surface.reflect(ray, hit.geometry, container, adjacent) ray = ray.representation(hit, scene.root) - history.append((ray, Event.REFLECT)) + + if break_condition is not None and break_condition(ray, *info, Event.REFLECT): + history.append((ray, Event.KILL)) + break + else: + history.append((ray, Event.REFLECT)) + # print("REFLECT", ray) continue else: @@ -205,7 +252,11 @@ def follow(scene, ray, maxsteps=1000, maxpathlength=np.inf, emit_method="kT"): # raise ValueError("Ray did not refract.") ray = ref_ray ray = ray.representation(hit, scene.root) - history.append((ray, Event.TRANSMIT)) - # print("TRANSMIT", ray) + + if break_condition is not None and break_condition(ray, *info, Event.TRANSMIT): + history.append((ray, Event.KILL)) + break + else: + history.append((ray, Event.TRANSMIT)) continue - return history + return history \ No newline at end of file diff --git a/pvtrace/light/ray.py b/pvtrace/light/ray.py index 469a94f..3992f4d 100644 --- a/pvtrace/light/ray.py +++ b/pvtrace/light/ray.py @@ -10,7 +10,7 @@ @dataclass(frozen=True) class Ray: - """ A ray of light. Has the physical attributes of position, direction and + """ A ray of light. Has the physical attributes of position, direction, time and wavelength. Attributes @@ -27,6 +27,8 @@ class Ray: Total propagation distance. This gets updated when when calling `propagate`. source: float Identifier of the light source of luminophore that emitted the ray. + time: float + Total time since Event.GENERATE """ position: tuple @@ -35,15 +37,48 @@ class Ray: is_alive: bool = True travelled: float = 0.0 source: Optional[str] = None + time: float = 0.0 def __repr__(self): position = "(" + ", ".join(["{:.2f}".format(x) for x in self.position]) + ")" direction = "(" + ", ".join(["{:.2f}".format(x) for x in self.direction]) + ")" wavelength = "{:.2f}".format(self.wavelength) + time = "{:.2f}".format(self.time) is_alive = "True" if self.is_alive else "False" - args = (position, direction, wavelength, is_alive) - return "Ray(pos={}, dir={}, nm={}, alive={})".format(*args) - + args = (position, direction, wavelength, is_alive, time) + return "Ray(pos={}, dir={}, nm={}, alive={}, time={})".format(*args) + + def add_time(self, val): + """ Returns a new ray with clock value incremented by 'val' + + Parameters + ---------- + val : float + time to add + """ + if not self.is_alive: + raise ValueError("Ray is not alive.") + new_time = self.time + val + new_ray = replace( + self, time=new_time + ) + return new_ray + + def set_time(self, val): + """ Returns a new ray with clock value set to 'val' + + Parameters + ---------- + val : float + time to set + """ + if not self.is_alive: + raise ValueError("Ray is not alive.") + new_ray = replace( + self, time=val + ) + return new_ray + def propagate(self, distance: float) -> Ray: """ Returns a new ray which has been moved the specified distance along its direction. From ce0caad1a78e4c68c70c330b649b6622582c0165 Mon Sep 17 00:00:00 2001 From: mamintatarin Date: Sun, 7 Dec 2025 20:28:00 +0300 Subject: [PATCH 2/2] returned some files back --- examples/001 Quick Start.ipynb | 4 ++-- examples/003 Lights.ipynb | 4 ++-- examples/006 Coatings.ipynb | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/001 Quick Start.ipynb b/examples/001 Quick Start.ipynb index 8942adb..ac936fb 100644 --- a/examples/001 Quick Start.ipynb +++ b/examples/001 Quick Start.ipynb @@ -220,7 +220,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -234,7 +234,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.6" } }, "nbformat": 4, diff --git a/examples/003 Lights.ipynb b/examples/003 Lights.ipynb index 7a526d2..2afbdf3 100644 --- a/examples/003 Lights.ipynb +++ b/examples/003 Lights.ipynb @@ -300,7 +300,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -314,7 +314,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/examples/006 Coatings.ipynb b/examples/006 Coatings.ipynb index 1467b72..b317f52 100644 --- a/examples/006 Coatings.ipynb +++ b/examples/006 Coatings.ipynb @@ -18,6 +18,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -297,7 +298,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -311,7 +312,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.2" } }, "nbformat": 4,