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

Split magnet set object #173

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*.yaml
!config.yaml
*.lic
!tests/files_for_tests/magnet_set.cub5

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
42 changes: 39 additions & 3 deletions parastell/cubit_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def import_step_cubit(filename, import_dir):
"""Imports STEP file into Coreform Cubit.

Arguments:
filename (str): name of STEP input file, excluding '.step' extension.
filename (str): name of STEP input file.
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
import_dir (str): directory from which to import STEP file.

Returns:
Expand All @@ -55,7 +55,7 @@ def export_step_cubit(filename, export_dir=""):
"""Export CAD solid as a STEP file via Coreform Cubit.

Arguments:
filename (str): name of STEP output file, excluding '.step' extension.
filename (str): name of STEP output file.
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
export_dir (str): directory to which to export the STEP output file
(defaults to empty string).
"""
Expand All @@ -65,11 +65,28 @@ def export_step_cubit(filename, export_dir=""):
cubit.cmd(f'export step "{export_path}" overwrite')


def import_cub5_cubit(filename, import_dir):
"""Imports cub5 file with Coreform Cubit with default import settings.
Arguments:
filename (str): name of cub5 input file.
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
import_dir (str): directory from which to import cub5 file.
Returns:
vol_id (int): Cubit volume ID of imported CAD solid.
"""
init_cubit()
import_path = Path(import_dir) / Path(filename).with_suffix(".cub5")
cubit.cmd(
f'import cubit "{import_path}" nofreesurfaces attributes_on separate_bodies'
)
vol_id = cubit.get_last_id("volume")
return vol_id


def export_cub5(filename, export_dir=""):
"""Export cub5 representation of model (native Cubit format).

Arguments:
filename (str): name of cub5 output file, excluding '.cub5' extension.
filename (str): name of cub5 output file.
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
export_dir (str): directory to which to export the cub5 output file
(defaults to empty string).
"""
Expand Down Expand Up @@ -192,3 +209,22 @@ def export_dagmc_cubit_native(
# exports
if delete_upon_export:
cubit.cmd(f"delete mesh volume all propagate")


def cubit_importer(filename, import_dir=""):
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
"""Attempts to open a geometry file with the appropriate cubit_io function,
based on file extension
Arguments:
filename (path): name of the file to import, including the suffix
import_dir (str): directory from which to import the file.
Returns:
vol_id (int): Cubit volume ID of imported CAD solid.
"""
importers = {
".step": import_step_cubit,
".stp": import_step_cubit,
".cub5": import_cub5_cubit,
}
filename = Path(filename)
vol_id = importers[filename.suffix](filename, import_dir)
Copy link
Collaborator

Choose a reason for hiding this comment

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

To make this compatible with the corresponding import functions, see the below suggestion.

Suggested change
vol_id = importers[filename.suffix](filename, import_dir)
vol_id = importers[filename.suffix](filename.stem, import_dir)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think with the comment above regarding the behavior of .with_suffix() this should be fine as is?

return vol_id
152 changes: 88 additions & 64 deletions parastell/magnet_coils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,92 @@


class MagnetSet(object):
"""An object representing a set of modular stellarator magnet coils.
"""A minimum viable class which can be used to build a DAGMC model with
parastell utilizing pre-defined geometry

Arguments:
geom_filename (path): Path to the predefined magnet geometry
logger (object): logger object (optional, defaults to None). If no
logger is supplied, a default logger will be instantiated.

Optional Attributes
mat_tag (str): DAGMC material tag to use for magnets in DAGMC
neutronics model (defaults to 'magnets').
"""

def __init__(self, geom_filename, logger=None, **kwargs):
self.logger = logger
geom_path = Path(geom_filename).resolve()
self.geom_filename = geom_path.name
self.export_dir = geom_path.parent
self.mat_tag = "magnets"
for name in kwargs.keys() & ("mat_tag"):
self.__setattr__(name, kwargs[name])

@property
def logger(self):
return self._logger

@logger.setter
def logger(self, logger_object):
self._logger = log.check_init(logger_object)

def import_geom_cubit(self):
"""Import geom file for magnet set into Coreform Cubit."""
first_vol_id = 1
if cubit_io.initialized:
first_vol_id += cubit.get_last_id("volume")

# TODO cubit importer
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved
last_vol_id = cubit_io.cubit_importer(
self.geom_filename, self.export_dir
)

self.volume_ids = list(range(first_vol_id, last_vol_id + 1))
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure that the volume ids are contiguous? I think there is a cubit command to ensure this.


def mesh_magnets(self, min_size=20.0, max_size=50.0, max_gradient=1.5):
"""Creates tetrahedral mesh of magnet volumes via Coreform Cubit.

Arguments:
min_size (float): minimum size of mesh elements (defaults to 20.0).
max_size (float): maximum size of mesh elements (defaults to 50.0).
max_gradient (float): maximum transition in mesh element size
(defaults to 1.5).
"""
self._logger.info("Generating tetrahedral mesh of magnet coils...")

if not hasattr(self, "volume_ids"):
self.import_geom_cubit()

volume_ids_str = " ".join(str(id) for id in self.volume_ids)
cubit.cmd(f"volume {volume_ids_str} scheme tetmesh")
cubit.cmd(
f"volume {volume_ids_str} sizing function type skeleton min_size "
f"{min_size} max_size {max_size} max_gradient {max_gradient} "
"min_num_layers_3d 1 min_num_layers_2d 1 min_num_layers_1d 1"
)
cubit.cmd(f"mesh volume {volume_ids_str}")

def export_mesh(self, mesh_filename="magnet_mesh", export_dir=""):
"""Creates tetrahedral mesh of magnet volumes and exports H5M format
via Coreform Cubit and MOAB.

Arguments:
mesh_filename (str): name of H5M output file, excluding '.h5m'
extension (optional, defaults to 'magnet_mesh').
export_dir (str): directory to which to export the H5M output file
(optional, defaults to empty string).
"""
self._logger.info("Exporting mesh H5M file for magnet coils...")

cubit_io.export_mesh_cubit(
filename=mesh_filename, export_dir=export_dir
)


class BuildableMagnetSet(MagnetSet):
Copy link
Member

Choose a reason for hiding this comment

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

Not convinced by this new name... maybe FilamentMagnetSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both versions may have filament data associated with them, as it may be desired to build the magnet surface for radial distance finding. BuildableMagnetSet is not my favorite either, but I've had a hard time coming up with another distinguishing feature. Maybe CadQueryMagnetSet, or ParastellMagnetSet, or MagnetSetFromFilaments? Could have 3 objects in total as well, MagnetSet and MagnetSetFromFilaments and MagnetSetFromGeometry, which inherit from MagnetSet?

"""An object representing a set of modular stellarator magnet coils, and
can use filament data to build 3D step files with cadquery.

Arguments:
coils_file (str): path to coil filament data file.
Expand Down Expand Up @@ -104,14 +189,6 @@ def toroidal_extent(self, angle):
self._logger.error(e.args[0])
raise e

@property
def logger(self):
return self._logger

@logger.setter
def logger(self, logger_object):
self._logger = log.check_init(logger_object)

def _instantiate_coils(self):
"""Extracts filament coordinate data from input data file and
instantiates MagnetCoil class objects.
Expand Down Expand Up @@ -238,18 +315,6 @@ def build_magnet_coils(self):

self._cut_magnets()

def import_step_cubit(self):
"""Import STEP file for magnet set into Coreform Cubit."""
first_vol_id = 1
if cubit_io.initialized:
first_vol_id += cubit.get_last_id("volume")

last_vol_id = cubit_io.import_step_cubit(
self.step_filename, self.export_dir
)

self.volume_ids = list(range(first_vol_id, last_vol_id + 1))

def export_step(self, step_filename="magnet_set", export_dir=""):
"""Export CAD solids as a STEP file via CadQuery.

Expand All @@ -262,56 +327,15 @@ def export_step(self, step_filename="magnet_set", export_dir=""):
self._logger.info("Exporting STEP file for magnet coils...")

self.export_dir = export_dir
self.step_filename = step_filename
self.geom_filename = Path(step_filename).with_suffix(".step")

export_path = Path(self.export_dir) / Path(
self.step_filename
).with_suffix(".step")
export_path = Path(self.export_dir) / self.geom_filename

coil_set = cq.Compound.makeCompound(
[coil.solid for coil in self.magnet_coils]
)
cq.exporters.export(coil_set, str(export_path))

def mesh_magnets(self, min_size=20.0, max_size=50.0, max_gradient=1.5):
"""Creates tetrahedral mesh of magnet volumes via Coreform Cubit.

Arguments:
min_size (float): minimum size of mesh elements (defaults to 20.0).
max_size (float): maximum size of mesh elements (defaults to 50.0).
max_gradient (float): maximum transition in mesh element size
(defaults to 1.5).
"""
self._logger.info("Generating tetrahedral mesh of magnet coils...")

if not hasattr(self, "volume_ids"):
self.import_step_cubit()

volume_ids_str = " ".join(str(id) for id in self.volume_ids)
cubit.cmd(f"volume {volume_ids_str} scheme tetmesh")
cubit.cmd(
f"volume {volume_ids_str} sizing function type skeleton min_size "
f"{min_size} max_size {max_size} max_gradient {max_gradient} "
"min_num_layers_3d 1 min_num_layers_2d 1 min_num_layers_1d 1"
)
cubit.cmd(f"mesh volume {volume_ids_str}")

def export_mesh(self, mesh_filename="magnet_mesh", export_dir=""):
"""Creates tetrahedral mesh of magnet volumes and exports H5M format
via Coreform Cubit and MOAB.

Arguments:
mesh_filename (str): name of H5M output file, excluding '.h5m'
extension (optional, defaults to 'magnet_mesh').
export_dir (str): directory to which to export the H5M output file
(optional, defaults to empty string).
"""
self._logger.info("Exporting mesh H5M file for magnet coils...")

cubit_io.export_mesh_cubit(
filename=mesh_filename, export_dir=export_dir
)

def sort_coils_toroidally(self):
"""Reorders list of coils by toroidal angle on range [-pi, pi].

Expand Down
12 changes: 10 additions & 2 deletions parastell/parastell.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def construct_magnets(
mat_tag (str): DAGMC material tag to use for magnets in DAGMC
neutronics model (defaults to 'magnets').
"""
self.magnet_set = mc.MagnetSet(
self.magnet_set = mc.BuildableMagnetSet(
coils_file,
width,
thickness,
Expand All @@ -226,6 +226,14 @@ def construct_magnets(
self.magnet_set.populate_magnet_coils()
self.magnet_set.build_magnet_coils()

def load_magnets_from_geometry(self, geom_filename):
"""Load predefined magnet geometry for use when building dagmc model

Arguments:
geom_filename (path): Path to the predefined magnet geometry
"""
self.magnet_set = mc.MagnetSet(geom_filename)

def export_magnets(
self,
step_filename="magnet_set",
Expand Down Expand Up @@ -377,7 +385,7 @@ def build_cubit_model(self, skip_imprint=False, legacy_faceting=True):
self.invessel_build.import_step_cubit()

if self.magnet_set:
self.magnet_set.import_step_cubit()
self.magnet_set.import_geom_cubit()

if skip_imprint:
self.invessel_build.merge_layer_surfaces()
Expand Down
Binary file added tests/files_for_tests/magnet_set.cub5
Binary file not shown.
53 changes: 52 additions & 1 deletion tests/test_magnet_coils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import numpy as np

import parastell.magnet_coils as magnet_coils
import cubit
from parastell import cubit_io


def remove_files():
Expand All @@ -29,13 +31,22 @@ def coil_set():
toroidal_extent = 90.0
sample_mod = 10

coil_set_obj = magnet_coils.MagnetSet(
coil_set_obj = magnet_coils.BuildableMagnetSet(
coils_file, width, thickness, toroidal_extent, sample_mod=sample_mod
)

return coil_set_obj


@pytest.fixture
def coil_set_from_geom():

geom_file = Path("files_for_tests") / "magnet_set.step"
coil_set_from_geom_obj = magnet_coils.MagnetSet(geom_file)

return coil_set_from_geom_obj


def test_magnet_construction(coil_set):

width_exp = 40.0
Expand Down Expand Up @@ -67,6 +78,11 @@ def test_magnet_construction(coil_set):

def test_magnet_exports(coil_set):

if cubit_io.initialized:
cubit.cmd("new")
else:
cubit_io.init_cubit()
Comment on lines +81 to +84
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this necessary? Not sure how/if pytest isolates these tests


volume_ids_exp = list(range(1, 2))

remove_files()
Expand All @@ -83,3 +99,38 @@ def test_magnet_exports(coil_set):
assert Path("magnet_mesh.h5m").exists()

remove_files()


def test_magnets_from_geom_cubit_import(coil_set_from_geom):
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved

if cubit_io.initialized:
cubit.cmd("new")
else:
cubit_io.init_cubit()
Comment on lines +106 to +109
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question as above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I added these upon realizing that there was leftover stuff from previous tests messing up these tests


volume_ids_exp = list(range(1, 2))

coil_set_from_geom.import_geom_cubit()

assert coil_set_from_geom.volume_ids == volume_ids_exp

cubit.cmd("new")

coil_set_from_geom.geom_filename = "magnet_set.cub5"

coil_set_from_geom.import_geom_cubit()

assert coil_set_from_geom.volume_ids == volume_ids_exp


def test_magnets_from_geom_exports(coil_set_from_geom):
Edgar-21 marked this conversation as resolved.
Show resolved Hide resolved

if cubit_io.initialized:
cubit.cmd("new")
else:
cubit_io.init_cubit()
Comment on lines +136 to +139
Copy link
Collaborator

Choose a reason for hiding this comment

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

See above


coil_set_from_geom.mesh_magnets()

coil_set_from_geom.export_mesh()
assert Path("magnet_mesh.h5m").exists()
Loading