Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds utility for building the 'magnet surface' and shooting rays at it #142

Merged
merged 5 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Examples/radial_build_distance_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from parastell.radial_distance_utils import *

coils_file = "coils_wistelld.txt"
circ_cross_section = ["circle", 25]
toroidal_extent = 90.0
vmec_file = "plasma_wistelld.nc"
vmec = read_vmec.VMECData(vmec_file)

magnet_set = magnet_coils.MagnetSet(
coils_file, circ_cross_section, toroidal_extent
)

reordered_filaments = get_reordered_filaments(magnet_set)

build_magnet_surface(reordered_filaments)

toroidal_angles = np.linspace(0, 90, 72)
poloidal_angles = np.linspace(0, 360, 96)
wall_s = 1.08

radial_build_dict = {}

radial_build = ivb.RadialBuild(
toroidal_angles, poloidal_angles, wall_s, radial_build_dict
)
build = ivb.InVesselBuild(vmec, radial_build, split_chamber=False)
build.populate_surfaces()
build.calculate_loci()
ribs = build.Surfaces["chamber"].Ribs
radial_distances = measure_radial_distance(ribs)
np.savetxt("radial_distances.csv", radial_distances, delimiter=",")
156 changes: 156 additions & 0 deletions parastell/radial_distance_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import numpy as np
from . import magnet_coils
from . import invessel_build as ivb
import pystell.read_vmec as read_vmec

import cubit


def calc_z_radius(point):
"""
Calculate the distance from the z axis.

Arguments:
point (iterable of [x,y,z] float): point to find distance for
Returns:
(float): distance to z axis.
"""
return (point[0] ** 2 + point[1] ** 2) ** 0.5


def get_start_index(filament):
"""
Find the index at which the filament crosses the xy plane on the OB
side of the filament

Arguments:
filament (list of list of float): List of points defining the filament

Returns:
max_z_index (int): index at which the filament crosses the xy plane
"""
max_z_index = None
max_z_radius = 0
for index, (point, next_point) in enumerate(
zip(filament[0:-1], filament[1:])
):
if point[2] / next_point[2] < 0:
z_radius = calc_z_radius(point)
if max_z_radius < z_radius:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the max z point isn't where the filament crosses the x-y plane?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is a poor variable name. It's called max_z_radius because its should end up being the point where it crosses the x-y plane with the larger distance to the z axis (has the larger radius if the center is the z axis) of the two crossings, not anything to do with the actual z value of the points. The if statement above checks for a sign change in z between the two points, and only checks for what the z radius is if there is a sign change (crosses the xy plane). I'll rename these to something hopefully a bit clearer.

max_z_index = index
max_z_radius = z_radius
return max_z_index


def sort_filaments_toroidally(filaments):
"""
Reorder filaments in order of increasing toroidal angle

Arguments:
filaments (list of list of list of float): List of filaments, which are
lists of points defining each filament.
Returns:
filaments (list of list of list of float): filaments in order of
increasing toroidal angle.
"""
com_list = np.zeros((len(filaments), 3))

for idx, fil in enumerate(filaments):
com_list[idx] = np.average(fil, axis=0)

phi_arr = np.arctan2(com_list[:, 1], com_list[:, 0])

filaments = np.array([x for _, x in sorted(zip(phi_arr, filaments))])

return filaments


def reorder_filaments(filaments):
"""
Reorder the filaments so they start near the outboard xy plane crossing,
and begin by increasing z value.

Arguments:
filaments (list of list of list of float): List of filaments, which are
lists of points defining each filament.
Returns:
filaments (list of list of list of float): Reordered list of filaments,
suitable for building the magnet surface.
"""
for filament_index, filament in enumerate(filaments):
# start the filament at the outboard
max_z_index = get_start_index(filament)
reordered_filament = np.concatenate(
[filament[max_z_index:], filament[1:max_z_index]]
)

# make sure z is increasing initially
if reordered_filament[0, 2] > reordered_filament[1, 2]:
reordered_filament = np.flip(reordered_filament, axis=0)

# ensure filament is a closed loop
reordered_filament = np.concatenate(
[reordered_filament, [reordered_filament[0]]]
)

filaments[filament_index] = reordered_filament

filaments = sort_filaments_toroidally(filaments)

return filaments


def get_reordered_filaments(magnet_set):
"""
Convenience function to get the reordered filament data from a magnet
"""
magnet_set._extract_filaments()
magnet_set._set_average_radial_distance()
magnet_set._set_filtered_filaments()

filtered_filaments = magnet_set.filtered_filaments
filaments = reorder_filaments(filtered_filaments)

return filaments


def build_magnet_surface(reordered_filaments):
for fil1, fil2 in zip(reordered_filaments[0:-1], reordered_filaments[1:]):
for index, _ in enumerate(fil1):
x1 = fil1[index, 0]
x2 = fil2[index, 0]
y1 = fil1[index, 1]
y2 = fil2[index, 1]
z1 = fil1[index, 2]
z2 = fil2[index, 2]
cubit.cmd(
f"create curve location {x1} {y1} {z1} location {x2} {y2} {z2}"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this?

Suggested change
x1 = fil1[index, 0]
x2 = fil2[index, 0]
y1 = fil1[index, 1]
y2 = fil2[index, 1]
z1 = fil1[index, 2]
z2 = fil2[index, 2]
cubit.cmd(
f"create curve location {x1} {y1} {z1} location {x2} {y2} {z2}"
)
loc1 = ' '.join(fil1[index,0:2])
loc2 = ' '.join(fil2[index,0:2])
cubit.cmd(
f"create curve location {loc1} location {loc2}"
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to use list comprehension to make a list of str first but this will work. join() doesn't like to operate on iterables of things that are not str seems like


loops = np.array(cubit.get_entities("curve"))
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
loops = np.reshape(
loops, (len(reordered_filaments) - 1, len(reordered_filaments[0]))
)
for loop in loops:
for line in loop[0:-1]:
cubit.cmd(f"create surface skin curve {line} {line + 1}")


def measure_radial_distance(ribs):
distances = []
for rib in ribs:
distance_subset = []
for point, direction in zip(rib.rib_loci, rib._normals()):
cubit.cmd(f"create vertex {point[0]} {point[1]} {point[2]}")
vertex_id = cubit.get_last_id("vertex")
cubit.cmd(
f"create curve location at vertex {vertex_id} "
f"location fire ray location at vertex {vertex_id} "
f"direction {direction[0]} {direction[1]} {direction[2]} at "
"surface all maximum hits 1"
)
curve_id = cubit.get_last_id("curve")
distance = cubit.get_curve_length(curve_id)
distance_subset.append(distance)
distances.append(distance_subset)
return np.array(distances)