Source code for paramak.assemblies.spherical_tokamak

from typing import Optional, Sequence, Tuple, Union

import cadquery as cq
from .assembly import Assembly

from ..utils import (
    get_plasma_index,
    get_plasma_value,
    sum_up_to_gap_before_plasma,
    sum_up_to_plasma,
    sum_before_after_plasma,
    LayerType,
)
from ..workplanes.blanket_from_plasma import blanket_from_plasma
from ..workplanes.center_column_shield_cylinder import center_column_shield_cylinder
from ..workplanes.plasma_simplified import plasma_simplified


def create_blanket_layers_after_plasma(
    radial_build, vertical_build, minor_radius, major_radius, triangularity, elongation, rotation_angle, center_column
):
    layers = []
    cumulative_thickness_rb = 0
    cumulative_thickness_uvb = 0
    cumulative_thickness_lvb = 0

    plasma_index_radial = get_plasma_index(radial_build)
    plasma_index_vertical = get_plasma_index(vertical_build)

    for i, item in enumerate(radial_build[plasma_index_radial + 1 :]):
        upper_thicknees = vertical_build[plasma_index_vertical + 1 + i][1]
        lower_thicknees = vertical_build[plasma_index_vertical - 1 - i][1]
        radial_thickness = item[1]

        if item[0] == LayerType.GAP:
            cumulative_thickness_rb += radial_thickness
            cumulative_thickness_uvb += upper_thicknees
            cumulative_thickness_lvb += lower_thicknees
            continue

        layer = blanket_from_plasma(
            minor_radius=minor_radius,
            major_radius=major_radius,
            triangularity=triangularity,
            elongation=elongation,
            thickness=[
                lower_thicknees,
                radial_thickness,
                upper_thicknees,
            ],
            offset_from_plasma=[
                cumulative_thickness_lvb,
                cumulative_thickness_rb,
                cumulative_thickness_uvb,
            ],
            start_angle=-90,
            stop_angle=90,
            rotation_angle=rotation_angle,
            color=(0.5, 0.5, 0.5),
            name=f"layer_{plasma_index_radial+i+1}",
            allow_overlapping_shape=True,
            connect_to_center=True,
        )
        layer = layer.cut(center_column)
        cumulative_thickness_rb += radial_thickness
        cumulative_thickness_uvb += upper_thicknees
        cumulative_thickness_lvb += lower_thicknees
        layers.append(layer)

    return layers


def create_center_column_shield_cylinders(radial_build, vertical_build, rotation_angle):
    cylinders = []
    total_sum = 0
    layer_count = 0

    before, _ = sum_before_after_plasma(vertical_build)
    center_column_shield_height = sum([item[1] for item in vertical_build])

    for index, item in enumerate(radial_build):
        if item[0] == LayerType.PLASMA:
            break
        if item[0] == LayerType.GAP and radial_build[index + 1][0] == LayerType.PLASMA:
            break
        if item[0] == LayerType.GAP:
            total_sum += item[1]
            continue

        layer_count += 1
        # print('inner_radius', total_sum, 'item thickness', item[1], 'layer_count', layer_count)
        cylinder = center_column_shield_cylinder(
            inner_radius=total_sum,
            thickness=item[1],
            name=f"layer_{layer_count}",
            rotation_angle=rotation_angle,
            height=center_column_shield_height,
            reference_point=("lower", -before),
        )
        cylinders.append(cylinder)
        total_sum += item[1]

    return cylinders


[docs] def spherical_tokamak_from_plasma( radial_build: Sequence[Tuple[LayerType, float]], elongation: float = 2.0, triangularity: float = 0.55, rotation_angle: float = 180.0, extra_cut_shapes: Sequence[cq.Workplane] = [], extra_intersect_shapes: Sequence[cq.Workplane] = [], colors: dict = {}, ) -> Assembly: """Creates a spherical tokamak fusion reactor from a radial build and plasma parameters. Args: radial_build: sequence of tuples containing the radial build of the reactor. Each tuple should contain a LayerType and a float elongation (float, optional): _description_. Defaults to 2.0. triangularity (float, optional): _description_. Defaults to 0.55. rotation_angle (Optional[str], optional): _description_. Defaults to 180.0. extra_cut_shapes (Sequence, optional): _description_. Defaults to []. colors (dict, optional): the colors to assign to the assembly parts. Defaults to {}. Each dictionary entry should be a key that matches the assembly part name (e.g. 'plasma', or 'layer_1') and a tuple of 3 or 4 floats between 0 and 1 representing the RGB or RGBA values. Returns: _type_: _description_ """ inner_equatorial_point = sum_up_to_plasma(radial_build) plasma_radial_thickness = get_plasma_value(radial_build) outer_equatorial_point = inner_equatorial_point + plasma_radial_thickness # sets major radius and minor radius from equatorial_points to allow a # radial build. This helps avoid the plasma overlapping the center # column and other components major_radius = (outer_equatorial_point + inner_equatorial_point) / 2 minor_radius = major_radius - inner_equatorial_point # make vertical build from outer radial build pi = get_plasma_index(radial_build) upper_vertical_build = radial_build[pi:] plasma_height = 2 * minor_radius * elongation # slice opperation reverses the list and removes the last value to avoid two plasmas vertical_build = upper_vertical_build[::-1][:-1] + [(LayerType.PLASMA, plasma_height)] + upper_vertical_build[1:] return spherical_tokamak( radial_build=radial_build, vertical_build=vertical_build, triangularity=triangularity, rotation_angle=rotation_angle, extra_cut_shapes=extra_cut_shapes, extra_intersect_shapes=extra_intersect_shapes, colors=colors, )
[docs] def spherical_tokamak( radial_build: Sequence[Tuple[LayerType, float]], vertical_build: Sequence[Tuple[str, float]], triangularity: float = 0.55, rotation_angle: Optional[str] = 180.0, extra_cut_shapes: Sequence[cq.Workplane] = [], extra_intersect_shapes: Sequence[cq.Workplane] = [], colors: dict = {}, ) -> Assembly: """ Creates a spherical tokamak fusion reactor from a radial build and vertical build. Args: radial_build: sequence of tuples containing the radial build of the reactor. Each tuple should contain a LayerType and a float elongation (float, optional): _description_. Defaults to 2.0. triangularity (float, optional): _description_. Defaults to 0.55. rotation_angle (Optional[str], optional): _description_. Defaults to 180.0. extra_cut_shapes (Sequence, optional): _description_. Defaults to []. colors (dict, optional): the colors to assign to the assembly parts. Defaults to {}. Each dictionary entry should be a key that matches the assembly part name (e.g. 'plasma', or 'layer_1') and a tuple of 3 or 4 floats between 0 and 1 representing the RGB or RGBA values. Returns: _type_: _description_ """ inner_equatorial_point = sum_up_to_plasma(radial_build) plasma_radial_thickness = get_plasma_value(radial_build) plasma_vertical_thickness = get_plasma_value(vertical_build) outer_equatorial_point = inner_equatorial_point + plasma_radial_thickness # sets major radius and minor radius from equatorial_points to allow a # radial build. This helps avoid the plasma overlapping the center # column and other components major_radius = (outer_equatorial_point + inner_equatorial_point) / 2 minor_radius = major_radius - inner_equatorial_point # vertical build elongation = (plasma_vertical_thickness / 2) / minor_radius blanket_rear_wall_end_height = sum([item[1] for item in vertical_build]) plasma = plasma_simplified( major_radius=major_radius, minor_radius=minor_radius, elongation=elongation, triangularity=triangularity, rotation_angle=rotation_angle, ) inner_radial_build = create_center_column_shield_cylinders( radial_build=radial_build, vertical_build=vertical_build, rotation_angle=rotation_angle, ) blanket_cutting_cylinder = center_column_shield_cylinder( inner_radius=0, thickness=sum_up_to_gap_before_plasma(radial_build), rotation_angle=360, height=2 * blanket_rear_wall_end_height, ) blanket_layers = create_blanket_layers_after_plasma( radial_build=radial_build, vertical_build=vertical_build, minor_radius=minor_radius, major_radius=major_radius, triangularity=triangularity, elongation=elongation, rotation_angle=rotation_angle, center_column=blanket_cutting_cylinder, ) my_assembly = Assembly() for i, entry in enumerate(extra_cut_shapes): if isinstance(entry, cq.Workplane): name = f"add_extra_cut_shape_{i+1}" my_assembly.add(entry, name=name, color=cq.Color(*colors.get(name, (0.5,0.5,0.5)))) else: raise ValueError(f"extra_cut_shapes should only contain cadquery Workplanes, not {type(entry)}") # builds up the intersect shapes intersect_shapes_to_cut = [] if len(extra_intersect_shapes) > 0: all_shapes = [] for shape in inner_radial_build + blanket_layers: all_shapes.append(shape) # makes a union of the the radial build to use as a base for the intersect shapes reactor_compound = inner_radial_build[0] for i, entry in enumerate(inner_radial_build[1:] + blanket_layers): reactor_compound = reactor_compound.union(entry) # adds the extra intersect shapes to the assembly for i, entry in enumerate(extra_intersect_shapes): reactor_entry_intersection = entry.intersect(reactor_compound) intersect_shapes_to_cut.append(reactor_entry_intersection) name = f"extra_intersect_shapes_{i+1}" my_assembly.add(reactor_entry_intersection, name=name, color=cq.Color(*colors.get(name, (0.5,0.5,0.5)))) # builds just the core if there are no extra parts if len(extra_cut_shapes) == 0 and len(intersect_shapes_to_cut) == 0: for i, entry in enumerate(inner_radial_build+blanket_layers): name = f"layer_{i+1}" my_assembly.add(entry, name=name, color=cq.Color(*colors.get(name, (0.5,0.5,0.5)))) else: shapes_and_components = [] for i, entry in enumerate(inner_radial_build + blanket_layers): for cutter in extra_cut_shapes + extra_intersect_shapes: entry = entry.cut(cutter) # TODO use something like this to return a list of material tags for the solids in order, as some solids get split into multiple # for subentry in entry.objects: # print(i, subentry) shapes_and_components.append(entry) for i, entry in enumerate(shapes_and_components): # TODO track the names of shapes, even when extra shapes are made due to splitting name=f"layer_{i+1}" my_assembly.add(entry, name=name, color=cq.Color(*colors.get(name, (0.5,0.5,0.5)))) my_assembly.add(plasma, name="plasma", color=cq.Color(*colors.get("plasma", (0.5,0.5,0.5)))) my_assembly.elongation = elongation my_assembly.triangularity = triangularity my_assembly.major_radius = major_radius my_assembly.minor_radius = minor_radius return my_assembly