def _generate_rays(self, template, ray_count): origin_vectors = self._sphere_sampler(ray_count) directions = self._vector_sampler(ray_count) rays = [] for n in range(ray_count): # calculate surface point origin = origin_vectors[n].copy() origin.length = self._radius origin = Point3D(*origin) # calculate surface normal normal = -origin_vectors[n] # transform sampling direction from surface space direction = directions[n].transform( rotate_basis(normal, normal.orthogonal())) # USE WITH HEMISPHERECOSINESAMPLER # cosine weighted distribution, projected area weight is # implicit in distribution, so set weight appropriately rays.append((template.copy(origin, direction), 0.5)) return rays
def voxel_matches_polygon(self, coordinate_list): for voxel_coords in coordinate_list: voxel_coords = np.asarray(voxel_coords) rmax = voxel_coords[:, 0].max() rmin = voxel_coords[:, 0].min() zmax = voxel_coords[:, 1].max() zmin = voxel_coords[:, 1].min() router = 1.5 * rmax rinner = 0.5 * rmin zupper = 1.5 * zmax if zmax > 0 else 0.5 * zmax zlower = 0.5 * zmin if zmin > 0 else 1.5 * zmin test_rs = np.linspace(rinner, router, int(100 * (router - rinner))) test_zs = np.linspace(zlower, zupper, int(100 * (zupper - zlower))) voxel_vertex_points = [Point2D(*v) for v in voxel_coords] voxel = AxisymmetricVoxel(voxel_vertex_points, parent=None, primitive_type='csg') polygon = Polygon(voxel_coords, closed=True) test_verts = list(itertools.product(test_rs, test_zs)) try: inside_poly = polygon.contains_points(test_verts) except AttributeError: # Polygon.contains_points was only introduced in Matplotlib 2.2. # Before that we need to convert to Path inside_poly = Path( polygon.get_verts()).contains_points(test_verts) inside_csg = [ any( child.contains(Point3D(r, 0, z)) for child in voxel.children) for (r, z) in test_verts ] self.assertSequenceEqual(inside_csg, inside_poly.tolist())
def plot_brdf(light_angle): light_position = Point3D(np.sin(np.deg2rad(light_angle)), 0, np.cos(np.deg2rad(light_angle))) light_direction = origin.vector_to(light_position).normalise() phis = np.linspace(0, 360, 200) num_phis = len(phis) thetas = np.linspace(0, 90, 100) num_thetas = len(thetas) values = np.zeros((num_thetas, num_phis)) for i in range(num_thetas): for j in range(num_phis): theta = np.deg2rad(thetas[i]) phi = np.deg2rad(phis[j]) outgoing = Vector3D( np.cos(phi) * np.sin(theta), np.sin(phi) * np.sin(theta), np.cos(theta)) values[i, j] = aluminium.bsdf(light_direction, outgoing, 500.0) fig, ax = plt.subplots(subplot_kw=dict(projection='polar')) cs = ax.contourf(np.deg2rad(phis), thetas, values, extend="both") cs.cmap.set_under('k') plt.title("Light angle: {} degrees".format(light_angle))
def _check_barrel_surface(self, lens, azimuths, barrel_z): """ Checks barrel surface of a lens by calculating ray-lens intersection. Hit point position and angle of incidence are compared to predicted ones. :param lens: Spherical lens object to test plane surface of. :param azimuths: Azimuth angles to test lens surface at. :param barrel_z: """ lens_radius = lens.diameter / 2 for z in barrel_z: for ta in azimuths: # get x-y coordinates of the surface point from azimuth x = lens_radius * cos(ta) y = lens_radius * sin(ta) # get origin by surface point offset and calculate ray direction surface_point = Point3D(x, y, z) direction = Vector3D(-x, -y, 0) origin = Point3D(1.1 * x, 1.1 * y, z) # calculate ray-lens intersection intersection = lens.hit(CoreRay(origin, direction)) hit_point = intersection.hit_point.transform( intersection.primitive_to_world) # distance of expected surface point and the ray hit point distance = hit_point.vector_to(surface_point).length self.assertAlmostEqual( distance, 0, self.tolerance_distance, msg= "Ray-curved surface hit point and predicted surface point difference" " is larger than tolerance.") # angle of incidence on the sphere surface should be perpendicular cos_angle_incidence = intersection.normal.dot( intersection.ray.direction.normalise()) self.assertAlmostEqual( fabs(cos_angle_incidence), 1, self.tolerance_angle, msg="Angle of incidence differs from perpendicular.")
def generate_annulus_mesh_segments(lower_corner, upper_corner, theta_width, theta_offset, world): material = UnityVolumeEmitter() # Set of points in x-z plane p1a = Point3D(lower_corner.x, 0, lower_corner.y) # corresponds to lower corner is x-z plane p2a = Point3D(lower_corner.x, 0, upper_corner.y) p3a = Point3D(upper_corner.x, 0, upper_corner.y) # corresponds to upper corner in x-z plane p4a = Point3D(upper_corner.x, 0, lower_corner.y) # Set of points rotated away from x-z plane p1b = p1a.transform(rotate_z(theta_width)) p2b = p2a.transform(rotate_z(theta_width)) p3b = p3a.transform(rotate_z(theta_width)) p4b = p4a.transform(rotate_z(theta_width)) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [ [1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (rotated out of x-z plane) [5, 1, 2], [5, 2, 6], # top face (x-y plane) [3, 0, 4], [3, 4, 7], # bottom face (x-y plane) [4, 0, 5], [1, 5, 0], # inner face (y-z plane) [2, 3, 7], [2, 7, 6] ] # outer face (y-z plane) return Mesh(vertices=vertices, triangles=triangles, smoothing=False, transform=rotate_z(theta_offset), material=material, parent=world)
def test_planoconcave(self): """ Test planoconcave lens for a range of curvatures and center thickness combinations. """ # combinations of lens parameters to test diameter = 75 curvatures = np.array([0.5, 0.75, 1, 1.5, 5]) * diameter threshold_ratios = np.array([1, 1.05, 2, 3, 5, 10]) radius = diameter / 2 radius2 = radius**2 for curvature in curvatures: for threshold_ratio in threshold_ratios: # center thickness calculated from the minimum threshold front_thickness = curvature - sqrt(curvature**2 - radius2) center_thickness = threshold_ratio * front_thickness lens = PlanoConcave(diameter, center_thickness, curvature) # calculate coordinates of center of curvature of the lens front surface center_front = Point3D(0, 0, center_thickness + curvature) # check lens front and back surface by inside and outside envelopes azimuths = np.linspace(0, 2 * pi, self.n_testpoints, endpoint=False) min_altitude = self.pad_constant max_altitude = asin(radius / curvature) - self.pad_constant altitude = np.linspace(max_altitude, min_altitude, self.n_testpoints, endpoint=True) radii = curvature * np.sin(altitude) self._check_spherical_surface(curvature, lens, center_front, True, False, azimuths, radii) radii = np.linspace(radius - self.pad_constant, self.pad_constant, self.n_testpoints) self._check_plane_surface(lens, azimuths, radii) # check lens barrel surface by inside and outside envelope min_z = 0 max_z = center_thickness + front_thickness if max_z - min_z <= 3 * self.pad_constant: barrel_z = np.array(((min_z + max_z) / 2), ndmin=1) else: barrel_z = np.linspace(min_z + self.pad_constant, max_z - self.pad_constant, self.n_testpoints, endpoint=True) self._check_barrel_surface(lens, azimuths, barrel_z)
def csg_aperture(self, value): if value is True: width = max(self.dx, self.dy) face = Box(Point3D(-width, -width, -self.dz / 2), Point3D(width, width, self.dz / 2)) slit = Box(lower=Point3D(-self.dx / 2, -self.dy / 2, -self.dz / 2 - self.dz * 0.1), upper=Point3D(self.dx / 2, self.dy / 2, self.dz / 2 + self.dz * 0.1)) self._csg_aperture = Subtract(face, slit, parent=self, material=AbsorbingSurface(), name=self.name + ' - CSG Aperture') else: if isinstance(self._csg_aperture, Primitive): self._csg_aperture.parent = None self._csg_aperture = None
def _mayavi_source_from_raysect_object(self): lower = self._raysect_object.lower upper = self._raysect_object.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [[1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6]] # right face (y-z) mesh = Mesh(vertices, triangles) self._raysect_mesh = subdivide(mesh)
def load_dms_output(config, world, plasma, spec, fibgeom): from raysect.optical.observer import FibreOptic, PowerPipeline0D, SpectralRadiancePipeline0D from raysect.optical import translate, rotate, rotate_basis from raysect.core import Vector3D, Point3D power_arr = np.zeros(fibgeom.numfibres) spectra_arr = np.zeros((spec.pixels, fibgeom.numfibres)) for i, f in enumerate(power_arr): print("Analysing fibre: ", int(i + 1)) fibgeom.set_fibre(number=int(i + 1)) start_point = Point3D(fibgeom.origin[0], fibgeom.origin[1], fibgeom.origin[2]) forward_vector = Vector3D(fibgeom.xhat(), fibgeom.yhat(), fibgeom.zhat()).normalise() up_vector = Vector3D(0, 0, 1.0) if config['dms']['power_pipeline']: power = PowerPipeline0D() fibre = FibreOptic([power], acceptance_angle=1, radius=0.001, spectral_bins=spec.pixels, spectral_rays=1, pixel_samples=5, transform=translate(*start_point) * rotate_basis(forward_vector, up_vector), parent=world) fibre.min_wavelength = spec.wlower fibre.max_wavelength = spec.wupper fibre.observe() power_arr[i] = power.value.mean else: power_arr[i] = None if config['dms']['radiance_pipeline']: spectra = SpectralRadiancePipeline0D(display_progress=False) fibre = FibreOptic([spectra], acceptance_angle=1, radius=0.001, spectral_bins=spec.pixels, spectral_rays=1, pixel_samples=5, transform=translate(*start_point) * rotate_basis(forward_vector, up_vector), parent=world) fibre.min_wavelength = spec.wlower fibre.max_wavelength = spec.wupper fibre.observe() spectra_arr[:, i] = spectra.samples.mean else: spectra_arr[:, i] = None return power_arr, spectra_arr
def visualise_scenegraph(camera, focal_distance=1, zoom=1): if not isinstance(camera, Observer): raise TypeError("The vtk visualisation function takes a Raysect Observer object as its argument.") world = camera.root if not isinstance(world, World): raise TypeError("The vtk visualisation function requires the Raysect Observer object to be connected to a valid scene-graph.") # Add the actors to the renderer renderer = vtk.vtkRenderer() renderer.SetBackground(0, 0, 0) for child in world.children: vtk_element = map_raysect_element_to_vtk(child) if isinstance(vtk_element, VTKAssembly): renderer.AddActor(vtk_element.assembly) else: renderer.AddActor(vtk_element.actor) axes = vtk.vtkAxesActor() renderer.AddActor(axes) renWin = vtk.vtkRenderWindow() renWin.AddRenderer(renderer) renWin.SetSize(512, 512) iren = vtk.vtkRenderWindowInteractor() iren.SetRenderWindow(renWin) iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera()) iren.Initialize() camera_origin = Point3D(0, 0, 0).transform(camera.transform) camera_direction = Vector3D(0, 0, 1).transform(camera.transform) up_direction = Vector3D(0, 1, 0).transform(camera.transform) focal_point = camera_origin + camera_direction * focal_distance vtk_camera = vtk.vtkCamera() vtk_camera.SetPosition(camera_origin.x, camera_origin.y, camera_origin.z) vtk_camera.SetFocalPoint(focal_point.x, focal_point.y, focal_point.z) vtk_camera.SetViewUp(up_direction.x, up_direction.y, up_direction.z) vtk_camera.ComputeViewPlaneNormal() vtk_camera.SetDistance(focal_distance) vtk_camera.Zoom(zoom) renderer.SetActiveCamera(vtk_camera) renderer.SetBackground(1.0, 0.9688, 0.8594) renderer.SetBackground(vtk_colors.GetColor3d("SlateGray")) # Start the event loop. iren.Start()
def __init__(self, slit_id, centre_point, basis_x, dx, basis_y, dy, dz=0.001, parent=None, csg_aperture=False): self._centre_point = centre_point self._basis_x = basis_x.normalise() self.dx = dx self._basis_y = basis_y.normalise() self.dy = dy self.dz = dz # NOTE - target primitive and aperture surface cannot be co-incident otherwise numerics will cause Raysect # to be blind to one of the two surfaces. slit_normal = basis_x.cross(basis_y) transform = translate(centre_point.x, centre_point.y, centre_point.z) * rotate_basis( slit_normal, basis_y) super().__init__(parent=parent, transform=transform, name=slit_id) self.target = Box(lower=Point3D(-dx / 2 * 1.01, -dy / 2 * 1.01, -dz / 2), upper=Point3D(dx / 2 * 1.01, dy / 2 * 1.01, dz / 2), transform=None, material=NullMaterial(), parent=self, name=slit_id + ' - target') self._csg_aperture = None self.csg_aperture = csg_aperture
def fibre_distance_world(self, world): from cherab.tools.observers.intersections import find_wall_intersection from raysect.core import Vector3D, Point3D start_point = Point3D(self.origin[0] + 1.0 * self.xhat(), self.origin[1] + 1.0 * self.yhat(), self.origin[2] + 1.0 * self.zhat()) forward_vector = Vector3D(self.xhat(), self.yhat(), self.zhat()) hit_point, primitive = find_wall_intersection(world, start_point, forward_vector, delta=1E-3) return abs((self.origin[0] - hit_point[0]) / self.xhat())
def box_to_mesh(box): if not isinstance(box, Box): raise TypeError("The _box_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(box))) lower = box.lower upper = box.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [[1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6]] # right face (y-z) mesh = Mesh(vertices, triangles) mesh = subdivide(mesh) vertices = np.array(mesh.data.vertices) triangles = np.array(mesh.data.triangles) if box.parent: to_world = box.to_root() else: to_world = box.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def mesh_to_mesh(mesh): vertices = mesh.data.vertices.copy() triangles = mesh.data.triangles.copy()[:, 0:3] if mesh.parent: to_world = mesh.to_root() else: to_world = mesh.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def get_pini_alignment(pulse, oct8_pini): import idlbridge as idl global _idl_was_setup if not _idl_was_setup: _setup_idl() _idl_was_setup = True # Note: array index starts at zero, so actual pini index equals pini number - 1/. oct8_pini -= 1 idl.execute("ret = get_cherab_pinialignment(pulse={})".format(pulse)) ret = idl.get("ret") # Pull out the origin points from the IDL structure, convert to Point3D origin = Point3D(ret['origin'][oct8_pini][0] / 1000, ret['origin'][oct8_pini][1] / 1000, ret['origin'][oct8_pini][2] / 1000) # Pull out the direction vector from the IDL structure, convert to Vector3D direction = Vector3D(ret['vector'][oct8_pini][0], ret['vector'][oct8_pini][1], ret['vector'][oct8_pini][2]) # TODO - note divergence numbers are different between Carine and Corentin. div_u = ret['divu'][oct8_pini] / (2 * PI) * 360 div_v = ret['divv'][oct8_pini] / (2 * PI) * 360 divergence = (div_u, div_v) # Minimal 1/e width (at the source) of the beam (scalar in meters) initial_width = 0.001 # Approximate with 1mm as an effective point source. pini_length = PINI_LENGTHS[oct8_pini] pini_geometry = (origin, direction, divergence, initial_width, pini_length) return pini_geometry
def voxel_matches_polygon(self, coordinate_list): for voxel_coords in coordinate_list: voxel_coords = np.asarray(voxel_coords) rmax = voxel_coords[:, 0].max() rmin = voxel_coords[:, 0].min() zmax = voxel_coords[:, 1].max() zmin = voxel_coords[:, 1].min() router = 1.5 * rmax rinner = 0.5 * rmin zupper = 1.5 * zmax if zmax > 0 else 0.5 * zmax zlower = 0.5 * zmin if zmin > 0 else 1.5 * zmin test_rs = np.linspace(rinner, router, int(50 * (router - rinner))) test_zs = np.linspace(zlower, zupper, int(50 * (zupper - zlower))) # Test for 0 area: not supported by mesh representation x, y = voxel_coords.T area = 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) if area == 0: continue voxel = AxisymmetricVoxel(voxel_coords, primitive_type='mesh') polygon = Polygon(voxel_coords, closed=True).get_path() test_verts = list(itertools.product(test_rs, test_zs)) inside_poly = polygon.contains_points(test_verts) inside_voxel = [any(child.contains(Point3D(r, 0, z)) for child in voxel.children) for (r, z) in test_verts] # Due to numerical precision, some points may be inside the # Matplotlib polygon but not the Mesh. Check in this case that the # "failing" points are just very close to the edge of the polygon fails = np.nonzero(np.not_equal(inside_voxel, inside_poly))[0] for fail in fails: if inside_voxel[fail] and not inside_poly[fail]: # Polygon should be made slightly bigger inside_poly[fail] = polygon.contains_point(test_verts[fail], radius=-0.01) elif inside_poly[fail] and not inside_voxel[fail]: # Polygon should be made slightly smaller inside_poly[fail] = polygon.contains_point(test_verts[fail], radius=0.01) self.assertSequenceEqual(inside_voxel, inside_poly.tolist(), "Failed for vertices {}".format(voxel_coords))
def box_to_mesh(box): if not isinstance(box, Box): raise TypeError( "The _box_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(box))) lower = box.lower upper = box.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [ [1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6] ] # right face (y-z) return Mesh(vertices=vertices, triangles=triangles, smoothing=False, transform=box.transform, material=box.material, name=box.name)
def cone_to_mesh(cone, vertical_divisions=10, cylindrical_divisions=36, base_radial_divisions=5): if not isinstance(cone, Cone): raise TypeError("The _cone_to_mesh() function takes a Raysect Cone primitive as an argument, " "wrong type '{}' given.".format(type(cone))) radius = cone.radius height = cone.height # first make the cone body working_cylindrical_divisions = cylindrical_divisions cone_body_vertices = [] for i in range(vertical_divisions): working_radius = radius * (1 - (i / vertical_divisions)) working_height = height * (i / vertical_divisions) theta_step = 360 / working_cylindrical_divisions for j in range(working_cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) cone_body_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), working_height]) working_cylindrical_divisions -= int(cylindrical_divisions / (vertical_divisions + 1)) if working_cylindrical_divisions < 5: working_cylindrical_divisions = 5 # Finally, add centre point cone_body_vertices.append([0, 0, height]) # Triangulate the vertices vertices_2d = np.array(cone_body_vertices)[:, 0:2] cone_body_triangles = Delaunay(vertices_2d).simplices.tolist() # Now make the cone base working_cylindrical_divisions = cylindrical_divisions cone_base_vertices = [] for i in range(base_radial_divisions): working_radius = radius * (1 - (i / base_radial_divisions)) theta_step = 360 / working_cylindrical_divisions for j in range(working_cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) cone_base_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), 0]) working_cylindrical_divisions -= int(cylindrical_divisions / (base_radial_divisions + 1)) if working_cylindrical_divisions < 5: working_cylindrical_divisions = 5 # Finally, add centre point cone_base_vertices.append([0, 0, 0]) # Triangulate the vertices vertices_2d = np.array(cone_base_vertices)[:, 0:2] cone_base_triangles = np.flip(Delaunay(vertices_2d).simplices + len(cone_body_vertices), 1).tolist() # Combine the resulting triangles together vertices = cone_body_vertices + cone_base_vertices triangles = cone_body_triangles + cone_base_triangles vertices = np.array(vertices) triangles = np.array(triangles) if cone.parent: to_world = cone.to_root() else: to_world = cone.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def cylinder_to_mesh(cylinder, vertical_divisions=10, cylindrical_divisions=36, radial_divisions=5): if not isinstance(cylinder, Cylinder): raise TypeError("The _cylinder_to_mesh() function takes a Raysect Cylinder primitive as an argument, " "wrong type '{}' given.".format(type(cylinder))) radius = cylinder.radius height = cylinder.height # first make the main cylinder theta_step = 360 / cylindrical_divisions vertices = [] for i in range(vertical_divisions): z = (i / (vertical_divisions - 1)) * height for j in range(cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) vertices.append([radius * np.cos(theta_rad), radius * np.sin(theta_rad), z]) triangles = [] for i in range(vertical_divisions - 1): row_start = cylindrical_divisions * i next_row_start = cylindrical_divisions * (i + 1) for j in range(cylindrical_divisions): v1 = row_start + j if j != cylindrical_divisions - 1: v2 = row_start + j + 1 v3 = next_row_start + j + 1 else: v2 = row_start v3 = next_row_start v4 = next_row_start + j triangles.append([v1, v2, v3]) triangles.append([v3, v4, v1]) def _make_cap_triangles(n_cylindrical_segments, n_radial_segments, radius, z_height): working_cylindrical_segments = n_cylindrical_segments cap_vertices = [] for i in range(n_radial_segments): working_radius = radius * (1 - (i / n_radial_segments)) theta_step = 360 / working_cylindrical_segments for j in range(working_cylindrical_segments): theta_rad = np.deg2rad(j * theta_step) cap_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), z_height]) working_cylindrical_segments -= int(n_cylindrical_segments/(n_radial_segments+1)) if working_cylindrical_segments < 5: working_cylindrical_segments = 5 # Finally, add centre point cap_vertices.append([0, 0, z_height]) vertices_2d = np.array(cap_vertices)[:, 0:2] triangles = Delaunay(vertices_2d).simplices return cap_vertices, triangles # Make the upper and lower end caps lower_cap_vertices, lower_cap_triangles = _make_cap_triangles(cylindrical_divisions, radial_divisions, radius, 0) lower_cap_triangles = np.flip(lower_cap_triangles, 1) lower_cap_triangles += len(vertices) lower_cap_triangles = lower_cap_triangles.tolist() vertices += lower_cap_vertices triangles += lower_cap_triangles upper_cap_vertices, upper_cap_triangles = _make_cap_triangles(cylindrical_divisions, radial_divisions, radius, height) upper_cap_triangles += len(vertices) upper_cap_triangles = upper_cap_triangles.tolist() vertices += upper_cap_vertices triangles += upper_cap_triangles vertices = np.array(vertices) triangles = np.array(triangles) if cylinder.parent: to_world = cylinder.to_root() else: to_world = cylinder.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
########################## # add machine components # config_file = get_resource("ST40-IVC1", "configuration", 'st40_ivc1_config') load_wall_configuration(config_file, world) eq002 = get_resource("ST40-IVC1", "equilibrium", "eq_006_2T_export") fiesta = Fiesta(eq002) b_field = fiesta.b_field lcfs = fiesta.get_midplane_lcfs()[1] seed_points = [ Point3D(0.75, 0, 0), Point3D(lcfs + 0.001, 0, 0), Point3D(lcfs + 0.01, 0, 0), Point3D(lcfs + 0.02, 0, -0.01) ] field_tracer = FieldlineTracer(b_field, method=RK2(step_size=0.0001)) end_point, _, trajectory1 = field_tracer.trace(world, seed_points[0], save_trajectory=True, max_length=15) end_point, _, trajectory2 = field_tracer.trace(world, seed_points[1], save_trajectory=True, max_length=15) end_point, _, trajectory3 = field_tracer.trace(world, seed_points[2], save_trajectory=True, max_length=15) end_point, _, trajectory4 = field_tracer.trace(world, seed_points[3], save_trajectory=True, max_length=15) # mlab.plot3d([0, 0.005, 0.001], [0, 0, 0], [0, 0, 0], tube_radius=0.0005, color=(1, 0, 0)) # mlab.plot3d([0, 0, 0], [0, 0.005, 0.001], [0, 0, 0], tube_radius=0.0005, color=(0, 1, 0))
############################## # calculate heatflux mapping # mesh_powers = {} null_intersections = 0 lost_power = 0 power_per_fieldline = TOTAL_POWER / NUM_OF_FIELDLINES t_start = time.time() for i in range(NUM_OF_FIELDLINES): seed_radius = q_to_s_func(random()) + lcfs_radius seed_angle = random() * 45 seed_point = Point3D(seed_radius * np.cos(np.deg2rad(seed_angle)), seed_radius * np.sin(np.deg2rad(seed_angle)), 0) end_point, intersection, _ = field_tracer.trace(world, seed_point, max_length=15) if intersection is not None: try: powers = mesh_powers[intersection.primitive.name] except KeyError: mesh = intersection.primitive powers = np.zeros((mesh.data.triangles.shape[0])) mesh_powers[intersection.primitive.name] = powers tri_id = intersection.primitive_coords[0] powers[tri_id] += power_per_fieldline
def map_power(self, power, angle_period, field_tracer, world, num_of_fieldlines=50000, max_tracing_length=15, phi_offset=0, debug_output=False, debug_count=10000, write_output=True): """ Map the power from this surface onto the wall tiles. :param float power: The total power that will be mapped onto the tiles using the specified distribution. :param float angle_period: the spatial period for the interface surface in degrees, e.g. 45 degrees. Exploiting cylindrical symmetry will enable a significant speed up of the calculations. :param FieldlineTracer field_tracer: a pre-configured field line tracer object. :param World world; A world scenegraph to be mapped. This scenegraph must contain all the desired meshes for power tracking calculations. :param int num_of_fieldlines: the number of fieldlines to launch in the mapping process. Defaults to 50000 fieldlines. :param float max_tracing_length: the maximum length for tracing fieldlines. :param float phi_offset: the angular range offset for collision point mapping. Important for cases where the periodic divertor tiles straddle phi=0. :param bool debug_output: toggle to print extra debug information output, such as the meshes collided with and the amount of lost power. """ if not (isinstance(power, (float, int)) and power > 0): raise TypeError("The interface power must be a float/int > 0.") if not (isinstance(angle_period, (float, int)) and 0 < angle_period <= 360): raise TypeError( "The angle period must be a float/int between (0, 360].") if 360 % angle_period: raise ValueError( "The angle period must be divisible into 360 degrees an integer number of times." ) if not (isinstance(num_of_fieldlines, int) and num_of_fieldlines > 0): raise TypeError( "The number of fieldlines to trace must be an integer > 0.") # reduce power when exploiting cylindrical symmetry reduction_factor = 360 / angle_period total_power = power / reduction_factor power_per_fieldline = total_power / num_of_fieldlines meshes = {} mesh_powers = {} mesh_hitpoints = {} mesh_seedpoints = {} null_intersections = 0 lost_power = 0 t_start = time.time() for i in range(num_of_fieldlines): seed_point_2d = self._generate_sample_point() seed_angle = random() * angle_period seed_point = Point3D( seed_point_2d.x * np.cos(np.deg2rad(seed_angle)), seed_point_2d.x * np.sin(np.deg2rad(seed_angle)), seed_point_2d.y) end_point, intersection, _ = field_tracer.trace( world, seed_point, max_length=max_tracing_length) # log the collision information for power tallies if intersection is not None: # catch primitive for later if we haven't encountered it before try: meshes[intersection.primitive.name] except KeyError: meshes[ intersection.primitive.name] = intersection.primitive # extract power array for intersected mesh and save results try: powers = mesh_powers[intersection.primitive.name] except KeyError: mesh = intersection.primitive powers = np.zeros((mesh.data.triangles.shape[0])) mesh_powers[intersection.primitive.name] = powers tri_id = intersection.primitive_coords[0] powers[tri_id] += power_per_fieldline # save hit points for saving to a separate vtk file try: hitpoints = mesh_hitpoints[intersection.primitive.name] except KeyError: hitpoints = [] mesh_hitpoints[intersection.primitive.name] = hitpoints # save seed points for separate analysis try: seedpoints = mesh_seedpoints[intersection.primitive.name] except KeyError: seedpoints = [] mesh_seedpoints[intersection.primitive.name] = seedpoints # map the hit point back to the starting sector (angular period) hit_point = intersection.hit_point.transform( intersection.primitive_to_world) phi = np.rad2deg(atan2(hit_point.y, hit_point.x)) - phi_offset phase_phi = phi - phi % angle_period mapped_point = hit_point.transform(rotate_z(-phase_phi)) hitpoints.append( (mapped_point.x, mapped_point.y, mapped_point.z)) seedpoints.append(seed_point) else: null_intersections += 1 lost_power += power_per_fieldline if not i % debug_count and debug_output: print("Tracing fieldline {}.".format(i)) t_end = time.time() if debug_output: print("Meshes collided with:") for mesh_name, mesh_values in mesh_powers.items(): power_fraction = mesh_values.sum() / total_power * 100 print('{} - {:.4G}%'.format(mesh_name, power_fraction)) print("Fraction of lost power - {:.4G}%".format(lost_power / total_power)) print() print("execution time: {}".format(t_end - t_start)) print() if write_output: for mesh_name in mesh_powers.keys(): mesh_primitive = meshes[mesh_name] powers = mesh_powers[mesh_name] hitpoints = np.array(mesh_hitpoints[mesh_name]) output_filename = mesh_name + ".vtk" self._write_mesh_power_vtk(output_filename, powers, mesh_primitive) point_filename = mesh_name + ".vtp" self._write_mesh_points_vtk(point_filename, hitpoints, power_per_fieldline) return mesh_powers, mesh_hitpoints, mesh_seedpoints
s1_from_s2_signed_distance = np.empty(n_s1_triangles) s1_from_s2_signed_distance[:] = 1E999 s2_intersects = np.zeros(n_s2_triangles) s2s1_distance = np.empty(n_s2_triangles) s2s1_distance[:] = 1E999 s2_from_s1_signed_distance = np.empty(n_s2_triangles) s2_from_s1_signed_distance[:] = 1E999 s2_pair = np.zeros(n_s1_triangles) for s1_tri_id in range(n_s1_triangles): v1, v2, v3 = s1_triangles[s1_tri_id] u1x, u1y, u1z = s1_vertices[v1] u1 = Point3D(u1x, u1y, u1z) u2x, u2y, u2z = s1_vertices[v2] u2 = Point3D(u2x, u2y, u2z) u3x, u3y, u3z = s1_vertices[v3] u3 = Point3D(u3x, u3y, u3z) uc = Point3D((u1x + u2x + u3x) / 3, (u1y + u2y + u3y) / 3, (u1z + u2z + u3z) / 3) n_pi1 = u1.vector_to(u2).cross(u1.vector_to(u3)).normalise() # normal vector of plane 1 for s2_tri_id in range(n_s2_triangles): v1, v2, v3 = s2_triangles[s2_tri_id] v1x, v1y, v1z = s2_vertices[v1] v1 = Point3D(v1x, v1y, v1z) v2x, v2y, v2z = s2_vertices[v2] v2 = Point3D(v2x, v2y, v2z) v3x, v3y, v3z = s2_vertices[v3]
import numpy as np import matplotlib.pyplot as plt from raysect.core import Point3D, Vector3D, rotate_basis, translate, Ray as CoreRay from raysect.core.math.sampler import DiskSampler3D, RectangleSampler3D, TargettedHemisphereSampler from raysect.optical import World from raysect.primitive import Box, Cylinder, Subtract from raysect.optical.material import AbsorbingSurface, NullMaterial R_2_PI = 1 / (2 * np.pi) world = World() # Setup pinhole target_plane = Box(Point3D(-10, -10, -0.000001), Point3D(10, 10, 0.000001)) hole = Cylinder(0.001, 0.001, transform=translate(0, 0, -0.0005)) pinhole = Subtract(target_plane, hole, parent=world, material=AbsorbingSurface()) target = Cylinder(0.0012, 0.001, transform=translate(0, 0, -0.0011), parent=world, material=NullMaterial()) def analytic_etendue(area_det, area_slit, distance, alpha, gamma): return area_det * area_slit * np.cos(alpha / 360 * (2 * np.pi)) * np.cos(
def mask_corners(element): """ Support detectors with rounded corners, by producing a mask to cover the corners. The mask is produced by placing thin rectangles of side element.curvature_radius at each corner, and then cylinders of radius element.curvature_radius centred on the inner vertex of those rectangles. Then each corner of the mask is the part of the rectangle not covered by the cylinder. The curvature radius should be given in units of metres. """ # Make the mask very (but not infinitely) thin, so that raysect # can actually detect that it's there. We'll work in the local # coordinate system of the element, with dx=width, dy=height, # dz=depth. dz = 1e-6 rc = element.curvature_radius # Shorthand try: dx = element.x_width dy = element.y_width except AttributeError: dx = element.dx dy = element.dy # Create a box and a cylinder of the appropriate size. # Then position copies of these at each corner. box_template = Box(Point3D(0, 0, 0), Point3D(rc, rc, dz)) cylinder_template = Cylinder(rc, dz) top_left_box = box_template.instance( transform=translate(-dx / 2, dy / 2 - rc, 0), ) top_left_cylinder = cylinder_template.instance( transform=translate(-dx / 2 + rc, dy / 2 - rc, 0), ) top_left_mask = Subtract(top_left_box, Intersect(top_left_box, top_left_cylinder)) top_right_box = box_template.instance( transform=translate(dx / 2 - rc, dy / 2 - rc, 0), ) top_right_cylinder = cylinder_template.instance( transform=translate(dx / 2 - rc, dy / 2 - rc, 0), ) top_right_mask = Subtract(top_right_box, Intersect(top_right_box, top_right_cylinder)) bottom_right_box = box_template.instance( transform=translate(dx / 2 - rc, -dy / 2, 0), ) bottom_right_cylinder = cylinder_template.instance( transform=translate(dx / 2 - rc, -dy / 2 + rc, 0), ) bottom_right_mask = Subtract(bottom_right_box, Intersect(bottom_right_box, bottom_right_cylinder)) bottom_left_box = box_template.instance( transform=translate(-dx / 2, -dy / 2, 0), ) bottom_left_cylinder = cylinder_template.instance( transform=translate(-dx / 2 + rc, -dy / 2 + rc, 0), ) bottom_left_mask = Subtract(bottom_left_box, Intersect(bottom_left_box, bottom_left_cylinder)) # The foil mask is the sum of all 4 of these corner shapes mask = functools.reduce(Union, (top_left_mask, top_right_mask, bottom_right_mask, bottom_left_mask)) mask.material = AbsorbingSurface() mask.transform = translate(0, 0, dz) mask.name = element.name + ' - rounded edges mask' mask.parent = element
def centre_point(self): return Point3D(0, 0, 0).transform(self.to_root())
def load_ks5_sightlines(pulse, spectrometer, parent=None, fibre_names=None, min_wavelength = 526, max_wavelength = 532, spectral_bins = 500): if not pulse >= 76666: raise ValueError("Only shots >= 76666 are supported at this time.") if spectrometer not in ["ks5c", "ks5d"]: raise ValueError("Only spectrometers ['ks5c', 'ks5d'] are supported at this time.") import idlbridge as idl idl.execute('searchpath = !PATH') global _idl_was_setup if not _idl_was_setup: _setup_idl() _idl_was_setup = True idl.execute("ret = get_ks5_alignment(pulse={}, spec='{}')".format(pulse, spectrometer)) # Pull out data cg_align = idl.get("ret") # Process fibres in the order that CXSfit uses. cxsfit_order = [i[0] for i in sorted(enumerate(cg_align['cxsfit_track']), key=lambda x:x[1], reverse=True)] sightline_group = LineOfSightGroup(parent=parent, name=spectrometer) for icg in cxsfit_order: fibre_name = str(cg_align['fibre_name'][icg]) # Some fibre names are blank, meaning ignore them. if not fibre_name.strip(): continue if fibre_names and fibre_name not in fibre_names: print('Skipped', fibre_name) continue # Extract the fibres origin and direction xi = cg_align['origin_cart']['x'][icg]/1000 yi = cg_align['origin_cart']['y'][icg]/1000 zi = cg_align['origin_cart']['z'][icg]/1000 pini6 = 5 xj = cg_align['pos_activevol_cart']['x'][pini6][icg]/1000 yj = cg_align['pos_activevol_cart']['y'][pini6][icg]/1000 zj = cg_align['pos_activevol_cart']['z'][pini6][icg]/1000 los_origin = Point3D(xi, yi, zi) los_vec = Vector3D(xj-xi, yj-yi, zj-zi).normalise() sight_line = SpectroscopicSightLine(los_origin, los_vec, name=fibre_name, parent=sightline_group) sight_line.min_wavelength = min_wavelength sight_line.max_wavelength = max_wavelength sight_line.spectral_bins = spectral_bins sightline_group.add_sight_line(sight_line) return sightline_group
def sphere_to_mesh(sphere, subdivision_count=2): if not isinstance(sphere, Sphere): raise TypeError("The _sphere_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(sphere))) # Calculate vertices and faces using the icosohedren method # We compute a regular icosohedren with 12 vertices and 20 faces. # Vertices given by all perturbations of: # (0, ±1, ±ϕ), (±1, ±ϕ, 0), (±ϕ, 0, ±1), where ϕ = golden ratio golden_ratio = 1.61803398875 radius = sphere.radius v1 = Vector3D(-1.0, golden_ratio, 0.0).normalise() * radius v2 = Vector3D(1.0, golden_ratio, 0.0).normalise() * radius v3 = Vector3D(-1.0, -golden_ratio, 0.0).normalise() * radius v4 = Vector3D(1.0, -golden_ratio, 0.0).normalise() * radius v5 = Vector3D(0.0, -1.0, golden_ratio).normalise() * radius v6 = Vector3D(0.0, 1.0, golden_ratio).normalise() * radius v7 = Vector3D(0.0, -1.0, -golden_ratio).normalise() * radius v8 = Vector3D(0.0, 1.0, -golden_ratio).normalise() * radius v9 = Vector3D(golden_ratio, 0.0, -1.0).normalise() * radius v10 = Vector3D(golden_ratio, 0.0, 1.0).normalise() * radius v11 = Vector3D(-golden_ratio, 0.0, -1.0).normalise() * radius v12 = Vector3D(-golden_ratio, 0.0, 1.0).normalise() * radius vertices = [ [v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z], [v4.x, v4.y, v4.z], [v5.x, v5.y, v5.z], [v6.x, v6.y, v6.z], [v7.x, v7.y, v7.z], [v8.x, v8.y, v8.z], [v9.x, v9.y, v9.z], [v10.x, v10.y, v10.z], [v11.x, v11.y, v11.z], [v12.x, v12.y, v12.z], ] triangles = [ [0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1] ] # Optional - subdivision of icosohedren to increase resolution num_vertices = 12 num_triangles = 20 for i in range(subdivision_count): for j in range(num_triangles): triangle = triangles[j] # extract current triangle vertices v0_id = triangle[0] v1_id = triangle[1] v2_id = triangle[2] v0 = Vector3D(vertices[v0_id][0], vertices[v0_id][1], vertices[v0_id][2]) v1 = Vector3D(vertices[v1_id][0], vertices[v1_id][1], vertices[v1_id][2]) v2 = Vector3D(vertices[v2_id][0], vertices[v2_id][1], vertices[v2_id][2]) # subdivide with three new vertices v3 = (v0 + v1).normalise() * radius v3_id = num_vertices v4 = (v1 + v2).normalise() * radius v4_id = num_vertices + 1 v5 = (v2 + v0).normalise() * radius v5_id = num_vertices + 2 vertices.append([v3.x, v3.y, v3.z]) vertices.append([v4.x, v4.y, v4.z]) vertices.append([v5.x, v5.y, v5.z]) # ... and three new faces triangles[j] = [v0_id, v3_id, v5_id] # replace the first face triangles.append([v3_id, v1_id, v4_id]) triangles.append([v4_id, v2_id, v5_id]) triangles.append([v3_id, v4_id, v5_id]) num_vertices += 3 num_triangles += 3 vertices = np.array(vertices) triangles = np.array(triangles) if sphere.parent: to_world = sphere.to_root() else: to_world = sphere.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
import numpy as np import matplotlib.pyplot as plt from mayavi import mlab from raysect.core import World, Point3D, Vector3D from cherab.core.math import ConstantVector3D from vita.modules.cherab import FieldlineTracer, Euler # the world scene-graph world = World() b_field = ConstantVector3D(Vector3D(0, 1.5, 0)) field_tracer = FieldlineTracer(b_field, method=Euler(step_size=0.001)) start_point = Point3D(0, 0, 0) end_point, trajectory = field_tracer.trace(world, start_point, save_trajectory=True, max_steps=1000) num_segments = len(trajectory) x = np.zeros(num_segments) y = np.zeros(num_segments) z = np.zeros(num_segments) for ith_position, position in enumerate(trajectory): x[ith_position] = position.x y[ith_position] = position.y z[ith_position] = position.z mlab.plot3d(x, y, z, tube_radius=0.0005, color=(1, 0, 0))
d_ion_species, inside_outside=plasma.inside_outside) d_delta_recom.add_emitter_to_world(world, plasma) d_epsilon_excit = ExcitationLine(d_epsilon, plasma.electron_distribution, d_atom_species, inside_outside=plasma.inside_outside) d_epsilon_excit.add_emitter_to_world(world, plasma) d_epsilon_recom = RecombinationLine(d_epsilon, plasma.electron_distribution, d_ion_species, inside_outside=plasma.inside_outside) d_epsilon_recom.add_emitter_to_world(world, plasma) start_point = Point3D(1.669, 0, -1.6502) forward_vector = Vector3D(1 - 1.669, 0, -2 + 1.6502).normalise() up_vector = Vector3D(0, 0, 1.0) spectra = SpectralPipeline0D() fibre = FibreOptic([spectra], acceptance_angle=1, radius=0.001, spectral_bins=8000, spectral_rays=1, pixel_samples=5, transform=translate(*start_point) * rotate_basis(forward_vector, up_vector), parent=world) fibre.min_wavelength = 350.0