def test_trace_with_material_object(self): """ Root node and test object has a material attached. """ np.random.seed(1) # No reflections # np.random.seed(2) # Reflection at last inteface root = Node(name="Root", geometry=Sphere(radius=10.0, material=Dielectric.make_constant((400, 800), 1.0))) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0, material=Dielectric.make_constant((400, 800), 1.5))) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(4.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(4.0, 0.0, 0.0)), # Refracted into A replace(initial_ray, position=(6.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Refracted out of A replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): a, b = pair print("Testing {} {}".format(a.position, b.position)) assert np.allclose(a.position, b.position)
def test_trace_with_geometric_objects_1(self): """ Only one of the geometric objects has a transformation applied. """ root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0)) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(-1.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(1.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(4.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): a, b = pair print("Testing {} {}".format(a.position, b.position)) assert np.allclose(a.position, b.position)
def test_basic_scene(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((2.0, 0.0, 0.0)) s = Scene(root=a) r = MeshcatRenderer(zmq_url='tcp://127.0.0.1:6000') r.render(s) time.sleep(0.5) r.remove(s)
def test_basic_scene(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((2.0, 0.0, 0.0)) s = Scene(root=a) r = MeshcatRenderer() r.render(s) time.sleep(0.5) r.remove(s)
def test_intersection(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) # In frame of a everything is shifed 1 along x assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
def test_intersection_with_rotation_around_z(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) # This rotation make an z translation in b becomes a -y translation in a a.rotate(np.pi/2, axis=(0.0, 0.0, 1.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert np.allclose(points, ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0)))
def test_intersection_with_rotation_around_x(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) # Rotation around x therefore no displace in x b.rotate(np.pi/2, (1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
def test_intersection_with_rotation_around_x(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) # Rotation around x therefore no displace in x b.rotate(np.pi / 2, (1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
def test_intersection_with_rotation_around_z(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) # This rotation make an z translation in b becomes a -y translation in a a.rotate(np.pi / 2, axis=(0.0, 0.0, 1.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert np.allclose(points, ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0)))
def test_intersection_coordinate_system(self): root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((1.0, 0.0, 0.0)) scene = Scene(root) initial_ray = Ray( position=(-2.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0), wavelength=None, is_alive=True, ) scene_intersections = scene.intersections(initial_ray.position, initial_ray.direction) a_intersections = tuple(map(lambda x: x.to(root), scene_intersections)) assert scene_intersections == a_intersections
def test_intersection_coordinate_system(self): root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((1.0, 0.0, 0.0)) scene = Scene(root) initial_ray = Ray( position=(-2.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0), wavelength=None, is_alive=True, ) scene_intersections = scene.intersections( initial_ray.position, initial_ray.direction ) a_intersections = tuple(map(lambda x: x.to(root), scene_intersections)) assert scene_intersections == a_intersections
def test_intersection_with_translation(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) aloc = (-2.0, 0.0, 0.0) avec = (1.0, 0.0, 0.0) bloc = b.point_to_node(aloc, b) bvec = b.vector_to_node(avec, b) intersections = b.intersections(bloc, bvec) points = tuple(x.point for x in intersections) assert np.allclose(points, ((-1.0, 0.0, 0.0), (1.0, 0.0, 0.0))) # In local frame of b sphere is at origin intersections = a.intersections(aloc, avec) points = np.array(tuple(x.to(a).point for x in intersections)) expected = np.array(((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))) # In frame of a everything is shifed 1 along x assert np.allclose(points, expected)
def make_touching_scene(n1=1.5, n2=1.5, n3=1.5): world = Node( name="world (air)", geometry=Sphere( radius=10.0, material=Dielectric.air() ) ) box1 = Node( name="box one (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n1 ) ), parent=world ) box2 = Node( name="box two (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n2 ) ), parent=world ) box2.translate((0.0, 0.0, 1.0)) box3 = Node( name="box three (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n3 ) ), parent=world ) box3.translate((0.0, 0.0, 2.0)) scene = Scene(world) return scene, world, box1, box2, box3
def test_coodinate_system_conversions(self): a = Node(name='a') b = Node(name='b', parent=a) c = Node(name='c', parent=b) d = Node(name='d', parent=a) b.translate((1, 1, 1)) c.translate((0, 1, 1)) d.translate((-1, -1, -1)) theta = 0.5 * np.pi b.rotate(theta, (0, 0, 1)) c.rotate(theta, (1, 0, 0)) d.rotate(theta, (0, 1, 0)) # Points in node d to a require just travelling up the nodes. assert np.allclose(d.point_to_node((0, 0, 0), a), (-1, -1, -1)) assert np.allclose(d.point_to_node((1, 1, 1), a), (0, 0, -2)) # Directions in node d to a require just travelling up the nodes. assert np.allclose(d.vector_to_node((1, 0, 0), a), (0, 0, -1)) assert np.allclose(d.vector_to_node((0, 1, 0), a), (0, 1, 0)) assert np.allclose(d.vector_to_node((0, 0, 1), a), (1, 0, 0)) # Points in node d to c requires going up and down nodes assert np.allclose(c.point_to_node((0, 0, 0), d), (-3, 2, 1)) assert np.allclose(c.point_to_node((1, 1, 1), d), (-4, 3, 2)) # Directions in node d to c require going up and down nodes assert np.allclose(c.vector_to_node((1, 0, 0), d), (0, 1, 0)) assert np.allclose(c.vector_to_node((0, 1, 0), d), (-1, 0, 0)) assert np.allclose(c.vector_to_node((0, 0, 1), d), (0, 0, 1))
def test_coodinate_system_conversions(self): a = Node(name='a') b = Node(name='b', parent=a) c = Node(name='c', parent=b) d = Node(name='d', parent=a) b.translate((1,1,1)) c.translate((0,1,1)) d.translate((-1, -1, -1)) theta = 0.5 * np.pi b.rotate(theta, (0, 0, 1)) c.rotate(theta, (1, 0, 0)) d.rotate(theta, (0, 1, 0)) # Points in node d to a require just travelling up the nodes. assert np.allclose(d.point_to_node((0, 0, 0), a), (-1, -1, -1)) assert np.allclose(d.point_to_node((1, 1, 1), a), (0, 0, -2)) # Directions in node d to a require just travelling up the nodes. assert np.allclose(d.vector_to_node((1, 0, 0), a), (0, 0, -1)) assert np.allclose(d.vector_to_node((0, 1, 0), a), (0, 1, 0)) assert np.allclose(d.vector_to_node((0, 0, 1), a), (1, 0, 0)) # Points in node d to c requires going up and down nodes assert np.allclose(c.point_to_node((0, 0, 0), d), (-3, 2, 1)) assert np.allclose(c.point_to_node((1, 1, 1), d), (-4, 3, 2)) # Directions in node d to c require going up and down nodes assert np.allclose(c.vector_to_node((1, 0, 0), d), (0, 1, 0)) assert np.allclose(c.vector_to_node((0, 1, 0), d), (-1, 0, 0)) assert np.allclose(c.vector_to_node((0, 0, 1), d), (0, 0, 1))
def test_trace_raises_trace_error(self): """ Tests interface between objects in which only one of them has a material. This should raise a RuntimeError because it cannot be traced in a physically correct way. """ np.random.seed(2) root = Node(name="Root", geometry=Sphere(radius=10.0)) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0, material=Dielectric.make_constant((400, 800), 1.5))) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) did_raise = False try: tracer.follow(initial_ray) except TraceError as err: did_raise = True assert did_raise
def test_trace_with_translated_geometric_object(self): """ Single translated geometric objects. """ root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-2.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(4.0, 0.0, 0.0)), # First intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Second intersection replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): assert pair[0] == pair[1]
), parent=world ) # Make a light source. This is a laser emitting light along the whole length of the # cylinder. We need to translate and rotate the light source to get it to fire along # the axis. We use the position delegate to generate photons along the same length # as the cylinder. light = Node( name="light (555nm laser)", light=Light(position_delegate=lambda : (np.random.uniform(-2.5, 2.5), 0.0, 0.0)), parent=world ) cylinder.rotate(np.radians(90), (0.0, 1.0, 0.0)) light.translate((0.0, 0.0, -1.0)) # Setup renderer and the scene for tracing rend = MeshcatRenderer() scene = Scene(world) tracer = PhotonTracer(scene) rend.render(scene) # Trace 100 photons and visualise for ray in light.emit(100): path = tracer.follow(ray) print(path) rend.add_ray_path(path) # You can kill the simulation any time by pressing ctrl-c. # Do something with the data. Read the pvtrace documentation on how to process data
from pvtrace.scene.renderer import MeshcatRenderer from pvtrace.geometry.sphere import Sphere from pvtrace.material.dielectric import Dielectric from pvtrace.light.light import Light from pvtrace.algorithm import photon_tracer import time import functools import numpy as np # Add nodes to the scene graph world = Node(name="world (air)", geometry=Sphere(radius=10.0, material=Dielectric.air())) sphere = Node(name="sphere (glass)", geometry=Sphere(radius=1.0, material=Dielectric.glass()), parent=world) sphere.translate((0, 0, 2)) # Add source of photons light = Node(name="Light (555nm)", light=Light(divergence_delegate=functools.partial( Light.cone_divergence, np.radians(20)))) # Use meshcat to render the scene (optional) viewer = MeshcatRenderer(open_browser=True) scene = Scene(world) for ray in light.emit(100): # Do something with the photon trace information... info = photon_tracer.follow(ray, scene) rays, events = zip(*info) # Add rays to the renderer (optional) viewer.add_ray_path(rays)
world = Node(name="world (air)", geometry=Sphere(radius=10.0, material=Dielectric.air())) # A small cylinder shape made from glass cylinder = Node(name="cylinder (glass)", geometry=Cylinder(length=1.0, radius=1.0, material=Dielectric.glass()), parent=world) # A light source with 60-deg divergence light = Node(name="light (555nm laser)", light=Light(divergence_delegate=functools.partial( Light.cone_divergence, np.radians(60))), parent=world) light.translate((0.0, 0.0, -1.0)) # Make a renderer object and scene for tracing viewer = MeshcatRenderer(open_browser=True) scene = Scene(world) viewer.render(scene) # Generate some rays from the light source and trace them through the scene for ray in light.emit(10): info = photon_tracer.follow(ray, scene) rays, events = zip(*info) viewer.add_ray_path(rays) # Wait for Ctrl-C to terminate the script; keep the window open print("Ctrl-C to close") while True:
def _make_scene(self): """ Creates the scene based on configuration values. """ # Make world node (l, w, d) = self.size world = Node( name="World", geometry=Box((l * 100, w * 100, d * 100), material=Material(refractive_index=self.n0)), ) # Create components (Absorbers, Luminophores and Scatteres) if len(self._user_components) == 0: self._user_components = self._make_default_components() components = [] for component_data in self._user_components: cls = component_data.pop("cls") coefficient = component_data.pop("coefficient") component = cls(coefficient, **component_data) components.append(component) # Create LSC node lsc = Node( name="LSC", geometry=Box( (l, w, d), material=Material( refractive_index=self.n1, components=components, surface=Surface(delegate=OptionalMirrorAndSolarCell(self)), ), ), parent=world, ) if self._air_gap_mirror_info["want_air_gap_mirror"]: sheet_thickness = 0.25 * d # make it appear thinner than the LSC air_gap_mirror = Node( name="Air Gap Mirror", geometry=Box( (l, w, sheet_thickness), # same surface air but very thin material=Material( refractive_index=self.n0, components=[], surface=Surface(delegate=AirGapMirror(self)), ), ), parent=world, ) # Move adjacent to bottom surface with a small air gap air_gap_mirror.translate((0.0, 0.0, -(0.5 * d + sheet_thickness))) # Use user light if any have been given, otherwise use default values. if len(self._user_lights) == 0: self._user_lights = self._make_default_lights() # Create light nodes for light_data in self._user_lights: name = light_data["name"] light = Light( name=name, direction=light_data["direction"], wavelength=light_data["wavelength"], position=light_data["position"], ) light_node = Node(name=name, light=light, parent=world) light_node.location = light_data["location"] if light_data["rotation"]: light_node.rotate(*light_data["rotation"]) self._scene = Scene(world)