def generate_annulus_mesh_segments(lower_corner, upper_corner, number_segments, world, material=None): """ Generates an annulus from many smaller mesh segments. Used for calculating sensitivity matrices for poloidal inversion grids. :param Point2D lower_corner: the lower corner of the poloidal 2D cell. :param Point2D upper_corner: the upper corner of the poloidal 2D cell. :param int number_segments: The number of angular mesh segments used to build the annulus. :param World world: The scene-graph to which the annulus will be attached. :return: Node holding all the annulus segment primitives. :rtype: Node """ material = material or UnityVolumeEmitter() annulus_node = Node(parent=world) theta_adjusted = 360 / number_segments # 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_adjusted)) p2b = p2a.transform(rotate_z(theta_adjusted)) p3b = p3a.transform(rotate_z(theta_adjusted)) p4b = p4a.transform(rotate_z(theta_adjusted)) 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) base_segment = Mesh(vertices=vertices, triangles=triangles, smoothing=False) # Construct annulus by duplicating and rotating base segment. for i in range(number_segments): theta_rotation = theta_adjusted * i segment = Mesh(instance=base_segment, transform=rotate_z(theta_rotation), material=material, parent=annulus_node) return annulus_node
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 vectorfunction3d(x, y, z): r = sqrt(x**2 + y**2) phi = np.arctan2(y, x) b_mag = 1 / r b_vec = Vector3D(r * np.cos(phi), r * np.sin(phi), 0) b_vec = b_vec.transform(rotate_z(90)) b_vec = b_vec.normalise() * b_mag return b_vec
centre_column = get_resource("ST40-IVC2", "mesh", "centre_column") centre_column = import_ply(centre_column, scaling=0.001, parent=world, name="centre_column") meshes["centre_column"] = centre_column poloidal_coil_lower_45 = get_resource("ST40-IVC2", "mesh", "poloidal_coil_lower_45") poloidal_coil_lower_45 = import_ply(poloidal_coil_lower_45, scaling=0.001, name="poloidal_coil_lower_45") meshes["poloidal_coil_lower_45"] = poloidal_coil_lower_45 for i in range(8): poloidal_coil_lower_45.instance(parent=world, transform=rotate_z(i * 45), name="poloidal_coil_lower_45") poloidal_coil_upper_45 = get_resource("ST40-IVC2", "mesh", "poloidal_coil_upper_45") poloidal_coil_upper_45 = import_ply(poloidal_coil_upper_45, scaling=0.001, name="poloidal_coil_upper_45") meshes["poloidal_coil_upper_45"] = poloidal_coil_upper_45 for i in range(8): poloidal_coil_upper_45.instance(parent=world, transform=rotate_z(i * 45), name="poloidal_coil_upper_45") limiter_lower_45 = get_resource("ST40-IVC2", "mesh", "limiter_lower_45") limiter_lower_45 = import_ply(limiter_lower_45,
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
def load_wall_configuration(config_file, parent): with open(config_file, 'r') as fh: vita_input = json.load(fh) machine_id = vita_input["machine-settings"]["machine-id"] try: wall_components = vita_input["machine-settings"]["wall-components"] except KeyError: raise IOError( "Invalid vita config JSON - no 'wall-components' specification.") if not isinstance(wall_components, list): raise IOError( "Invalid vita config JSON - expected a list of wall component specifications." ) for component in wall_components: if not isinstance(component, dict): raise IOError( "Invalid vita config JSON - expected a dictionary specifying the component." ) resource_id = component["mesh-resource-id"] try: scaling = component["scaling"] except KeyError: scaling = None try: period = component["period"] if 360 % period: raise IOError( "Invalid vita config JSON - the angle period must be divisible into 360 " "degrees an integer number of times.") except KeyError: period = 360 try: rotation_offset = component["rotation_offset"] except KeyError: rotation_offset = 0 # TODO - add proper material handling material = AbsorbingSurface() mesh_instances = int(360 / period) mesh_file = get_resource(machine_id, 'mesh', resource_id) path, filename = os.path.split(mesh_file) name, ext = os.path.splitext(filename) importer = _import_functions[ext] mesh = importer(mesh_file, scaling=scaling, name=name, transform=rotate_z(rotation_offset)) for i in range(mesh_instances): mesh.instance(material=material, parent=parent, transform=rotate_z(i * period), name=name)
def load_kb1_camera(parent=None): camera_id = 'KB1' # Transforms, read from KB1 CAD model for INDIVIDUAL_BOLOMETER_ASSEMBLY # Note that the rotation angle is positive when Axis is the Z axis, and # negative when Axis is the -Z axis camera_transforms = [ translate(-1.73116, 2.59086, 3.31650) * rotate_z(123.75), translate(-3.05613, 0.60790, 3.31650) * rotate_z(168.75), translate(1.73116, -2.59086, 3.31650) * rotate_z(-56.25), translate(3.05613, -0.60790, 3.31650) * rotate_z(-11.25), ] # Transform for INDIVIDUAL_BOLOMETER_ASSEMBLY/SINGLE_BOLOMETER_ASSEMBLY/FOIL 1 # in CAD model foil_camera_transform = translate(0, 0, 18.70e-3) # Foils point downwards towards the plasma foil_orientation_transform = rotate_basis(Vector3D(0, 0, -1), Vector3D(0, 1, 0)) # Dimensions read from edge to edge (and adjacent vertices defining rounded corners) on # INDIVIDUAL_BOLOMETER_ASSEMBLY/SINGLE_BOLOMETER_ASSEMBLY/FOIL SUPPORT 1, # edges (and vertices) closest to the foil foil_width = 11e-3 foil_height = 11e-3 foil_curvature_radius = 1e-3 # KB1 does not really have a slit, per-se. The vessel functions as the # aperture. To ensure a sufficiently displaced bounding sphere for the # TargettedPixel, we'll put a dummy slit at the exit of the port through # which the camera views. Note that with the camera transform defined above, # the y axis is in the toroidal direction and the x axis in the inward # radial direction. # # The foil is not centred on the centre of the port. To measure the # displacement, the centre of the port was read from the CAD model for # KB1-1, then the vector from the foil centre to the centre of the port exit # for this channel was calculated in the foil's local coordinate system. foil_slit_transform = translate(-0.05025, 0, 1.38658) slit_width = 0.25 # slightly larger than widest point of port (~225 mm) slit_height = 0.09 # sligtly larger than length of port (~73.84 mm) num_slits = len(camera_transforms) num_foils = len(camera_transforms) bolometer_camera = BolometerCamera(name=camera_id, parent=parent) slit_objects = {} for i in range(num_slits): slit_id = '{}_Slit_#{}'.format(camera_id, i + 1) slit_transform = (camera_transforms[i] * foil_orientation_transform * foil_slit_transform * foil_camera_transform) centre_point = Point3D(0, 0, 0).transform(slit_transform) basis_x = Vector3D(1, 0, 0).transform(slit_transform) basis_y = Vector3D(0, 1, 0).transform(slit_transform) dx = slit_width dy = slit_height slit_objects[slit_id] = BolometerSlit(slit_id, centre_point, basis_x, dx, basis_y, dy, csg_aperture=True, parent=bolometer_camera) for i in range(num_foils): foil_id = '{}_CH{}_Foil'.format(camera_id, i + 1) slit_id = '{}_Slit_#{}'.format(camera_id, i + 1) foil_transform = (camera_transforms[i] * foil_orientation_transform * foil_camera_transform) centre_point = Point3D(0, 0, 0).transform(foil_transform) basis_x = Vector3D(1, 0, 0).transform(foil_transform) basis_y = Vector3D(0, 1, 0).transform(foil_transform) dx = foil_width dy = foil_height rc = foil_curvature_radius foil = BolometerFoil(foil_id, centre_point, basis_x, dx, basis_y, dy, slit_objects[slit_id], curvature_radius=rc, parent=bolometer_camera) bolometer_camera.add_foil_detector(foil) return bolometer_camera
cylinder_source1.Update() transform_filter = vtk.vtkTransformPolyDataFilter() transform_filter.SetInputConnection(cylinder_source1.GetOutputPort()) transform_filter.SetTransform(convert_to_vtk_transform(rotate_x(90))) transform_filter.Update() tris1 = vtk.vtkTriangleFilter() tris1.SetInputData(transform_filter.GetOutput()) cylinder_source2 = vtk.vtkCylinderSource() cylinder_source2.SetRadius(1) cylinder_source2.SetHeight(4.2) cylinder_source2.SetResolution(50) cylinder_source2.Update() transform_filter = vtk.vtkTransformPolyDataFilter() transform_filter.SetInputConnection(cylinder_source2.GetOutputPort()) transform_filter.SetTransform(convert_to_vtk_transform(rotate_z(90))) transform_filter.Update() tris2 = vtk.vtkTriangleFilter() tris2.SetInputData(transform_filter.GetOutput()) booleanOperation1 = vtk.vtkBooleanOperationPolyDataFilter() booleanOperation1.SetOperationToUnion() booleanOperation1.SetInputConnection(0, tris1.GetOutputPort()) booleanOperation1.SetInputConnection(1, tris2.GetOutputPort()) booleanOperation1.Update() bool_tris = vtk.vtkTriangleFilter() bool_tris.SetInputData(booleanOperation1.GetOutput()) cylinder_source3 = vtk.vtkCylinderSource() cylinder_source3.SetRadius(1) cylinder_source3.SetHeight(4.2)