def test_find_container_embedded_scene(): from pvtrace.algorithm.photon_tracer import find_container scene, world, box = make_embedded_scene() ray = Ray( position=(0.0, 0.0, -1.0), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) container = find_container(ray, nodes) assert container == world ray = Ray( position=(0.0, 0.0, -0.4), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) container = find_container(ray, nodes) assert container == box ray = Ray( position=(0.0, 0.0, 0.6), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) status = find_container(ray, nodes) assert status == world
def test_ray_status_embedded_scene(): from pvtrace.algorithm.photon_tracer import ray_status 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_scene() intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) container, to_node, surface_node = ray_status(ray, points, nodes) assert all([ container == world, to_node == box, surface_node == box ] ) ray = Ray( position=(0.0, 0.0, -0.4), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) container, to_node, surface_node = ray_status(ray, points, nodes) assert all([ container == box, to_node == world, surface_node == box ] ) ray = Ray( position=(0.0, 0.0, 0.6), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) container, to_node, surface_node = ray_status(ray, points, nodes) assert all([ container == world, to_node == None, surface_node == world ] )
def emit(self, num_rays=None) -> Iterator[Ray]: """ Returns a ray with wavelength, position and divergence sampled from the delegates. Parameters ---------- num_rays : int of None The maximum number of rays this light source will generate. If set to None then the light will generate until manually terminated. """ count = 0 while True: count += 1 if num_rays is not None and count > num_rays: break nanometers = self.wavelength_delegate() position = self.position_delegate() direction = (0.0, 0.0, 1.0) divergence = self.divergence_delegate() if not np.allclose((0.0, 0.0), divergence): (theta, phi) = divergence x = np.sin(phi) * np.cos(theta) y = np.sin(phi) * np.sin(theta) z = np.cos(phi) direction = (x, y, z) ray = Ray(wavelength=nanometers, position=position, direction=direction, is_alive=True) yield ray
def test_follow_lossy_embedded_scene_1(): 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() np.random.seed(0) path = follow(ray, scene) path, decisions = 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.50), # Reflection (0.00, 0.00, -0.3744069237034118), # Absorbed ] expected_decisions = [ Decision.EMIT, Decision.TRAVEL, Decision.TRANSIT, Decision.ABSORB, ] for expected_point, point, expected_decision, decision in zip( expected_positions, positions, expected_decisions, decisions): assert expected_decision == decision assert np.allclose(expected_point, point, atol=EPS_ZERO)
def probability(self, ray: Ray) -> float: """ Returns the probability that the interaction will occur. """ context = self.context normal = np.array(context.normal) n1 = context.n1 n2 = context.n2 # Be flexible with how the normal is defined ray_ = ray.representation(context.normal_node.root, context.normal_node) if np.dot(normal, ray_.direction) < 0.0: normal = flip(normal) angle = angle_between(normal, np.array(ray_.direction)) logger.debug("Incident angle {:.2f}".format(np.degrees(angle))) if angle < 0.0 or angle > 0.5 * np.pi: raise ValueError("The incident angle must be between 0 and pi/2.") # Catch TIR case if n2 < n1 and angle > np.arcsin(n2/n1): return 1.0 c = np.cos(angle) s = np.sin(angle) k = np.sqrt(1 - (n1/n2 * s)**2) Rs1 = n1 * c - n2 * k Rs2 = n1 * c + n2 * k Rs = (Rs1/Rs2)**2 Rp1 = n1 * k - n2 * c Rp2 = n1 * k + n2 * c Rp = (Rp1/Rp2)**2 return 0.5 * (Rs + Rp)
def test_follow_embedded_scene_2(): 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_scene(n1=100.0) np.random.seed(0) path = follow(ray, scene) path, decisions = 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.50), # Reflection (0.00, 0.00, -10.0), # Hit world node (0.00, 0.00, -10.0) # Ray killed ] expected_decisions = [ Decision.EMIT, Decision.TRAVEL, Decision.RETURN, Decision.TRAVEL, Decision.KILL ] for expected_point, point, expected_decision, decision in zip(expected_positions, positions, expected_decisions, decisions): assert expected_decision == decision assert np.allclose(expected_point, point, atol=EPS_ZERO)
def transform(self, ray: Ray, context: dict) -> Ray: """ Transform ray according to the physics of the interaction. An absorption event occurred at the path length along rays trajectory. """ _check_required_keys(set(["distance"]), context) distance = context["distance"] new_ray = ray.propagate(distance) return new_ray
def transform(self, ray: Ray) -> Ray: """ Transform ray according to the physics of the interaction. An absorption event occurred at the path length along rays trajectory. """ if not isinstance(self._path_length, float): raise AppError("Path length has not yet be calculated.") if self._interaction_material is not None: raise AppError("Cannot reuse mechanisms. Interaction material already calculated.") material = self.context.container.geometry.material self._interaction_material = material.get_interaction_material(ray.wavelength) new_ray = ray.propagate(self._path_length) return new_ray
def test_normal_reflection(self): n1 = 1.0 n2 = 1.5 normal = (0.0, 0.0, 1.0) angle = 0.0 ray = Ray(position=(0.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0), wavelength=None) fresnel = FresnelReflection() assert np.isclose(fresnel.reflectivity(angle, n1, n2), 0.04) new_ray = fresnel.transform(ray, {"normal": normal}) assert np.allclose(flip(ray.direction), new_ray.direction)
def transform(self, ray: Ray) -> Ray: """ Transform ray according to the physics of the interaction. """ context = self.context normal = np.array(context.normal) ray_ = ray.representation(context.normal_node.root, context.normal_node) vec = np.array(ray_.direction) d = np.dot(normal, vec) reflected_direction = vec - 2 * d * normal new_ray_ = replace(ray_, direction=tuple(reflected_direction.tolist())) new_ray = new_ray_.representation(context.normal_node, context.normal_node.root) return new_ray # back to world node
def test_touching_scene_intersections(): print("test_touching_scene_intersections") ray = Ray( position=(0.0, 0.0, -1.0), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) scene, world, box1, box2, box3 = make_touching_scene() intersections = scene.intersections(ray.position, ray.direction) points, nodes = zip(*[(x.point, x.hit) for x in intersections]) assert nodes == (box1, box1, box2, box2, box3, box3, world)
def test_antinormal_reflection(self): """ FresnelReflection takes the smallest angle between the ray direction and the normal. Thus the flipped normal will also work. """ n1 = 1.0 n2 = 1.5 normal = (0.0, 0.0, -1.0) angle = 0.0 ray = Ray(position=(0.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0), wavelength=None) fresnel = FresnelReflection() assert np.isclose(fresnel.reflectivity(angle, n1, n2), 0.04) new_ray = fresnel.transform(ray, {"normal": normal}) assert np.allclose(flip(ray.direction), new_ray.direction)
def test_normal_reflection(self): n1 = 1.0 n2 = 1.5 normal = (0.0, 0.0, 1.0) angle = 0.0 ray = Ray(position=(0.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0), wavelength=None) fresnel = FresnelRefraction() new_ray = fresnel.transform(ray, { "n1": n1, "n2": n2, "normal": normal }) assert np.allclose(ray.direction, new_ray.direction)
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_follow_touching_scene(): ray = Ray( position=(0.0, 0.0, -1.0), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True ) scene, world, box1, box2, box3 = make_touching_scene() np.random.seed(0) path = follow(ray, scene) path, decisions = zip(*path) positions = [x.position for x in path] print(decisions) expected_positions = [ (0.00, 0.00, -1.00), # Starting (0.00, 0.00, -0.50), # Hit box (0.00, 0.00, -0.50), # Refraction into box (0.00, 0.00, 0.50), # Hit box (0.00, 0.00, 0.50), # Refraction (0.00, 0.00, 1.50), # Hit box (0.00, 0.00, 1.50), # Refraction (0.00, 0.00, 2.50), # Hit box (0.00, 0.00, 2.50), # Refraction (0.00, 0.00, 10.0), # Hit world node (0.00, 0.00, 10.0) # Kill ] expected_decisions = [ Decision.EMIT, Decision.TRAVEL, Decision.TRANSIT, Decision.TRAVEL, Decision.TRANSIT, Decision.TRAVEL, Decision.TRANSIT, Decision.TRAVEL, Decision.TRANSIT, Decision.TRAVEL, Decision.KILL ] for expected_point, point, expected_decision, decision in zip( expected_positions, positions, expected_decisions, decisions): assert np.allclose(expected_point, point, atol=EPS_ZERO) assert expected_decision == decision
def transform(self, ray: Ray) -> Ray: """ Transform ray according to the physics of the interaction. """ context = self.context n1 = context.n1 n2 = context.n2 ray_ = ray.representation(context.normal_node.root, context.normal_node) normal = np.array(context.normal) vector = np.array(ray_.direction) n = n1/n2 dot = np.dot(vector, normal) c = np.sqrt(1 - n**2 * (1 - dot**2)) sign = 1 if dot < 0.0: sign = -1 refracted_direction = n * vector + sign*(c - sign*n*dot) * normal new_ray_ = replace(ray_, direction=tuple(refracted_direction.tolist())) new_ray = new_ray_.representation(context.normal_node, context.normal_node.root) return new_ray
def emit(self, num_rays=None) -> Iterator[Ray]: """ Returns a ray with wavelength, position and divergence sampled from the delegates. Parameters ---------- num_rays : int of None The maximum number of rays this light source will generate. If set to None then the light will generate until manually terminated. """ if num_rays is None or num_rays == 0: return count = 0 while True: count += 1 if num_rays is not None and count > num_rays: break ray = Ray(wavelength=self.wavelength(), position=self.position(), direction=self.direction(), is_alive=True, source=self) yield ray
def test_follow_embedded_lumophore_scene_1(): 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_lumophore_scene() np.random.seed(0) path = follow(ray, scene) path, decisions = zip(*path) positions = [x.position for x in path] # First two are before box expected_positions = [ (0.00, 0.00, -1.00), # Starting (0.00, 0.00, -0.50), # Refraction into box ] assert len(expected_positions) < len(positions[:-1]) print("Expected: {}".format(expected_positions)) assert all([ np.allclose(expected, actual, atol=EPS_ZERO) for (expected, actual) in zip(expected_positions, positions[0:2]) ])