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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason we've removed the specification that the '.step' extension should be excluded? The function still needs that to be the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Path(filename).with_suffix(".step")

replaces whatever suffix is there with .step or adds it if no suffix is present

>>> from pathlib import Path
>>> test = Path('test.py')
>>> test
PosixPath('test.py')
>>> test.with_suffix('.step')
PosixPath('test.step')
>>> 

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh I didn't know that about with_suffix. Neat

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above comment.

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be best to specify that the '.cub5' extension should be excluded from the filename

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above comments for STEP file extensions

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=""):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it might be best to modify this function name to start with a verb to follow our established convention/best practices, such as import_cubit or something similar.

"""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)

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
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 to-do still relevant?

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))

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):
"""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):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Call remove_files before and after function calls. In this case, step_import.log and maybe some other log file for the CUB5 import will be created.


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


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):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Call remove_files before and after function calls


if cubit_io.initialized:
cubit.cmd("new")
else:
cubit_io.init_cubit()
Comment on lines +128 to +131
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