Skip to content

Commit 21c2e21

Browse files
Merge branch 'main' into doc
2 parents 2578db4 + 418e576 commit 21c2e21

3 files changed

Lines changed: 85 additions & 31 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ classifiers = [
3535
"Programming Language :: Python :: 3.12",
3636
"Programming Language :: Python :: 3.13",
3737
]
38-
requires-python = ">=3.8"
39-
# TODO: add MOAB once on PyPI
38+
requires-python = ">=3.9"
39+
# TODO: add PyMOAB once on PyPI
4040
dependencies = ["numpy"]
4141

4242
# Optional Dependencies
@@ -69,4 +69,4 @@ docs = [
6969
[tool.setuptools_scm]
7070

7171
[tool.setuptools]
72-
package-dir = {"" = "src"}
72+
package-dir = {"" = "src"}

src/pydagmc/dagnav.py

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def volumes_by_material(self) -> Dict[str, list[Volume]]:
8686
A dictionary where keys are material names (str) and values
8787
are lists of Volume objects.
8888
"""
89-
material_map: DefaultDict[str, list[Volume]] = defaultdict(list)
89+
material_map: defaultdict[str, list[Volume]] = defaultdict(list)
9090
for volume in self.volumes:
9191
if volume.material is None:
9292
continue
@@ -347,7 +347,7 @@ def _check_category_and_dimension(self):
347347
raise ValueError(f"{identifier} has no category or geom_dimension tags assigned.")
348348

349349
def __eq__(self, other):
350-
return type(other) == type(self) and \
350+
return type(other) is type(self) and \
351351
self.model == other.model and \
352352
self.handle == other.handle
353353

@@ -403,6 +403,36 @@ def category(self, category: str):
403403
"""Set the DAGMC set's category."""
404404
self._tag_set_data(self.model.category_tag, category)
405405

406+
@property
407+
def groups(self) -> list[Group]:
408+
"""Get list of groups containing this DAGMC set."""
409+
return [group for group in self.model.groups if self in group]
410+
411+
def _metadata_group(self, prefix: str) -> list[Group]:
412+
"""Get single group containing this DAGMC set, with matching prefix."""
413+
for group in self.model.groups:
414+
if self in group and prefix in group.name:
415+
return group
416+
return None
417+
418+
def _metadata_group_name(self, prefix: str) -> Optional[str]:
419+
group = self._metadata_group(prefix)
420+
if group is not None:
421+
return group.name.removeprefix(prefix)
422+
return None
423+
424+
def _set_metadata_group(self, prefix: str, name: str):
425+
group = self._metadata_group(prefix)
426+
427+
if group is not None:
428+
# remove surface from existing group
429+
group.remove_set(self)
430+
431+
# create a new group or get an existing group
432+
if name is not None:
433+
group = Group.create(self.model, name = prefix + name)
434+
group.add_set(self)
435+
406436
@abstractmethod
407437
def _get_triangle_sets(self):
408438
"""Retrieve all (surface) sets under this set that contain triangle elements.
@@ -534,8 +564,9 @@ def create(cls, model: Model, global_id: Optional[int] = None) -> GeometrySet:
534564

535565
class Surface(GeometrySet):
536566

537-
_category = 'Surface'
538-
_geom_dimension = 2
567+
_category: str = 'Surface'
568+
_geom_dimension: int = 2
569+
_boundary_prefix: str = "boundary:"
539570

540571
def __init__(self, model: Model, handle: np.uint64):
541572
super().__init__(model, handle)
@@ -584,6 +615,19 @@ def reverse_volume(self) -> Optional[Volume]:
584615
def reverse_volume(self, volume: Volume):
585616
self.senses = [self.forward_volume, volume]
586617

618+
@property
619+
def boundary_group(self) -> Optional[Group]:
620+
return self._metadata_group(self._boundary_prefix)
621+
622+
@property
623+
def boundary(self) -> Optional[str]:
624+
"""Name of the boundary assigned to this surface."""
625+
return self._metadata_group_name(self._boundary_prefix)
626+
627+
@boundary.setter
628+
def boundary(self, name: Optional[str]):
629+
self._set_metadata_group(self._boundary_prefix, name)
630+
587631
@property
588632
def volumes(self) -> list[Volume]:
589633
"""Get the parent volumes of this surface.
@@ -613,42 +657,24 @@ class Volume(GeometrySet):
613657

614658
_category: str = 'Volume'
615659
_geom_dimension: int = 3
660+
_material_prefix: str = 'mat:'
616661

617662
def __init__(self, model: Model, handle: np.uint64):
618663
super().__init__(model, handle)
619664
self._check_category_and_dimension()
620665

621666
@property
622-
def groups(self) -> list[Group]:
623-
"""Get list of groups containing this volume."""
624-
return [group for group in self.model.groups if self in group]
625-
626-
@property
627-
def _material_group(self):
628-
for group in self.groups:
629-
if "mat:" in group.name:
630-
return group
631-
return None
667+
def material_group(self) -> Optional[Group]:
668+
return self._metadata_group(self._material_prefix)
632669

633670
@property
634671
def material(self) -> Optional[str]:
635672
"""Name of the material assigned to this volume."""
636-
group = self._material_group
637-
if group is not None:
638-
return group.name[4:]
639-
return None
673+
return self._metadata_group_name(self._material_prefix)
640674

641675
@material.setter
642676
def material(self, name: str):
643-
group = self._material_group
644-
645-
if group is not None:
646-
# Remove volume from existing group
647-
group.remove_set(self)
648-
649-
# create a new group or get an existing group
650-
group = Group.create(self.model, name=f"mat:{name}")
651-
group.add_set(self)
677+
self._set_metadata_group(self._material_prefix, name)
652678

653679
@property
654680
def surfaces(self) -> list[Surface]:

test/test_dagnav.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,12 @@ def test_initial_volume_properties_and_groups(fuel_pin_model, fuel_pin_volumes):
301301
# Check initial material via volume property and group membership
302302
assert vol1.material == 'fuel'
303303
assert vol1 in model.groups_by_name['mat:fuel']
304+
assert vol1.material_group == model.groups_by_name['mat:fuel']
304305
assert vol2.material == 'fuel'
305306
assert vol2 in model.groups_by_name['mat:fuel']
306307
assert vol3.material == '41'
307308
assert vol3 in model.groups_by_name['mat:41']
309+
assert vol3.material_group == model.groups_by_name['mat:41']
308310
assert vol4.material == 'Graveyard'
309311
assert vol4 in model.groups_by_name['mat:Graveyard']
310312

@@ -917,7 +919,7 @@ def test_surface_sense_value_error_on_wrong_length():
917919

918920
# Create dummy volumes for valid input
919921
vol1 = model.create_volume(global_id=1)
920-
922+
921923
# Empty list
922924
with pytest.raises(ValueError, match="Senses should be a list of two volumes."):
923925
surf.senses = []
@@ -948,6 +950,32 @@ def test_surface_create_invalid_filename():
948950
model.create_surface(filename='my_model.step')
949951

950952

953+
def test_surface_boundary():
954+
"""Test the boundary property of Surface."""
955+
model = pydagmc.Model()
956+
surf = model.create_surface(global_id=1)
957+
958+
# Initially, boundary should be None
959+
assert surf.boundary is None
960+
961+
# Set a valid boundary condition
962+
surf.boundary = 'Reflecting'
963+
assert surf.boundary == 'Reflecting'
964+
assert surf.boundary_group == model.groups_by_name['boundary:Reflecting']
965+
assert [1] == sorted(model.groups_by_name['boundary:Reflecting'].surface_ids)
966+
967+
# Change the boundary condition
968+
surf.boundary = 'Vacuum'
969+
assert surf.boundary == 'Vacuum'
970+
assert surf.boundary_group == model.groups_by_name['boundary:Vacuum']
971+
assert [1] == sorted(model.groups_by_name['boundary:Vacuum'].surface_ids)
972+
assert [] == sorted(model.groups_by_name['boundary:Reflecting'].surface_ids)
973+
974+
# Remove the boundary condition by setting it to None
975+
surf.boundary = None
976+
assert surf.boundary is None
977+
978+
951979
def test_geometryset_category_runtime_error(request):
952980
"""Test category returns None when tag is missing."""
953981
model = pydagmc.Model()

0 commit comments

Comments
 (0)