Skip to content

Commit

Permalink
Assign materials to regions
Browse files Browse the repository at this point in the history
  • Loading branch information
lequ0n committed Nov 13, 2023
1 parent ea62ca7 commit 95c6562
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 54 deletions.
22 changes: 22 additions & 0 deletions converter/fluka/cards/assignmat_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from dataclasses import dataclass, field
from converter.fluka.cards.card import Card

@dataclass
class AssignMatCard(Card):
"""
Class representing description of material assignment in FLUKA input.
Card consists of:
- codewd (str): 'ASSIGNMA'
- what (list[str]): list of parameters (see FLUKA manual for details)
sdum is not used.
"""

codewd: str = "ASSIGNMA"
region_name: str = ""
material_name: str = "BLCKHOLE"

def __post_init__(self) -> None:
"""Set material and region on appropriate positions in what list."""
super().__post_init__()
self.what[0] = self.material_name
self.what[1] = self.region_name
2 changes: 1 addition & 1 deletion converter/fluka/cards/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __post_init__(self):

def __str__(self) -> str:
"""Return card as string."""
return self._format_line()
return self._format_line().rstrip()

def _format_line(self) -> str:
"""Return formatted line from given parameters."""
Expand Down
18 changes: 8 additions & 10 deletions converter/fluka/helper_parsers/region_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,27 @@ class FlukaRegion:
figures_operators: list[list[tuple[BoolOperation, str]]] = field(default_factory=lambda: [])


def parse_regions(zones_json: dict, figures: list[FlukaFigure]) -> (list[FlukaRegion], list[FlukaFigure]):
def parse_regions(zones_json: dict, figures: list[FlukaFigure]) -> (dict[str, FlukaRegion], FlukaRegion, list[FlukaFigure]):
"""
Parse zones from JSON to Fluka regions.
Returns list of regions and list of additional figures generated by parsing world zone.
"""
# Naming is different in Fluka - Fluka zones consist of figures joined by subtractions and intersections
# Fluka regions consist of zones joined by unions
regions = []
# uuid -> FlukaRegion
regions = {}
zone_name = "region{}"
for idx, zone in enumerate(zones_json["zones"]):
regions.append(
FlukaRegion(
name=zone_name.format(idx),
figures_operators=parse_csg_operations(zone["unionOperations"], figures),
)
regions[zone["uuid"]] = FlukaRegion(
name=zone_name.format(idx),
figures_operators=parse_csg_operations(zone["unionOperations"], figures)
)

if "worldZone" in zones_json:
world_region, boundary_region, world_figure, world_boundary = parse_world_zone(zones_json, figures)
regions.append(world_region)
regions.append(boundary_region)
regions[zones_json["worldZone"]["uuid"]] = world_region

return regions, [world_figure, world_boundary]
return regions, boundary_region, [world_figure, world_boundary]


def parse_world_zone(zones_json: dict, figures: list[FlukaFigure]) -> (FlukaRegion, FlukaFigure, FlukaFigure):
Expand Down
7 changes: 4 additions & 3 deletions converter/fluka/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Input:
compounds: list[Card] = field(default_factory=lambda: [])
figures: list[SolidFigure] = field(default_factory=lambda: [])
regions: list = field(default_factory=lambda: [])
assignmats: list[Card] = field(default_factory=lambda: [])

template: str = """TITLE
proton beam simulation
Expand All @@ -35,9 +36,7 @@ class Input:
GEOEND
{MATERIALS}
{COMPOUNDS}
ASSIGNMA BLCKHOLE Z_BBODY
ASSIGNMA AIR Z_AIR
ASSIGNMA WATER Z_TARGET
{ASSIGNMATS}
* scoring NEUTRON on mesh z
USRBIN 0.0 NEUTRON -21 0.5 0.5 5.0n_z
USRBIN -0.5 -0.5 0.0 1 1 500&
Expand Down Expand Up @@ -73,11 +72,13 @@ def __str__(self):
"""Return fluka input file as string"""
materials_str = "\n".join([str(material) for material in self.materials]).strip()
compounds_str = "\n".join([str(compound) for compound in self.compounds]).strip()
assignmats_str = "\n".join([str(assignmat) for assignmat in self.assignmats]).strip()
return self.template.format(
BEAM=Card(codewd="BEAM", what=[str(-self.energy_GeV)], sdum="PROTON"),
START=Card(codewd="START", what=[str(self.number_of_particles)]),
FIGURES=FiguresCard(data=self.figures),
REGIONS=RegionsCard(data=self.regions),
MATERIALS=materials_str,
COMPOUNDS=compounds_str,
ASSIGNMATS=assignmats_str,
)
27 changes: 9 additions & 18 deletions converter/fluka/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def parse_materials_compounds(self, materials: list, zones: list) -> None:
density = material["density"]
self.__add_new_material_compound(uuid, icru, density)
for zone in zones:
if "density" in zone["materialPropertiesOverrides"]:
if "density" in zone.get("materialPropertiesOverrides", []):
uuid = zone["materialUuid"]
density = zone["materialPropertiesOverrides"]["density"]
icru = zone["customMaterial"]["icru"]
Expand Down Expand Up @@ -71,17 +71,12 @@ def __add_new_material_compound(self, uuid, icru, density) -> None:
else:
self.compounds[uuid] = self.predefined_compounds[icru]
else:
raise NotImplementedError(
"Only predefined materials and compounds are supported."
)
raise NotImplementedError("Only predefined materials and compounds are supported.")

def __load_predifined_materials(self) -> None:
"""Load predefined materials and compounds from file"""
with open(
Path("__file__").resolve().parent
/ "converter"
/ "fluka"
/ "predefined_materials.json",
Path("__file__").resolve().parent / "converter" / "fluka" / "predefined_materials.json",
"r",
) as file:
data = json.load(file)
Expand Down Expand Up @@ -115,19 +110,15 @@ def __generate_name(self, length: int = 8) -> str:

def get_custom_materials(self) -> list:
"""Return list of modified materials."""
return [
material
for material in self.materials.values()
if isinstance(material, CustomMaterial)
]
return [material for material in self.materials.values() if isinstance(material, CustomMaterial)]

def get_custom_compounds(self) -> list:
"""Return list of modified compounds."""
return [
compound
for compound in self.compounds.values()
if isinstance(compound, CustomCompound)
]
return [compound for compound in self.compounds.values() if isinstance(compound, CustomCompound)]

def get_parsed_materials_and_compounds(self) -> dict:
"""Return dict of parsed materials and compounds."""
return self.materials | self.compounds


@dataclass
Expand Down
30 changes: 24 additions & 6 deletions converter/fluka/parser.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from converter.common import Parser
from converter.fluka.helper_parsers.figure_parser import parse_figures
from converter.fluka.helper_parsers.region_parser import parse_regions
from converter.fluka.helper_parsers.region_parser import parse_regions, FlukaRegion
from converter.fluka.input import Input
from converter.fluka.material import MaterialsCompoundsConfig
from converter.fluka.cards.assignmat_card import AssignMatCard


class FlukaParser(Parser):
Expand All @@ -25,16 +26,20 @@ def parse_configs(self, json: dict) -> None:
self.input.number_of_particles = json["beam"]["numberOfParticles"]

self.materials_compounds_config.parse_materials_compounds(
materials=json["materialManager"].get("materials"),
zones=json["zoneManager"].get("zones"),
materials=json["materialManager"]["materials"],
zones=json["zoneManager"]["zones"] + [json["zoneManager"]["worldZone"]],
)
self.input.materials = self.materials_compounds_config.get_custom_materials()
self.input.compounds = self.materials_compounds_config.get_custom_compounds()
self.input.figures = parse_figures(json["figureManager"].get("figures"))
self.input.regions, world_figures = parse_regions(
json["zoneManager"], self.input.figures
)
regions, boundary_region, world_figures = parse_regions(json["zoneManager"], self.input.figures)
self.input.regions = list(regions.values()) + [boundary_region]
self.input.figures.extend(world_figures)
self.__assign_materials_and_compounds(
regions=regions,
zones=json["zoneManager"]["zones"] + [json["zoneManager"]["worldZone"]],
boundary=boundary_region,
)

def get_configs_json(self) -> dict:
"""
Expand All @@ -45,3 +50,16 @@ def get_configs_json(self) -> dict:
configs_json["fl_sim.inp"] = str(self.input)

return configs_json

def __assign_materials_and_compounds(self, regions: dict, zones: list, boundary: FlukaRegion) -> None:
"""Assign materials and compounds to regions."""
assignmat_cards = []
materials = self.materials_compounds_config.get_parsed_materials_and_compounds()
for zone in zones:
assignmat_cards.append(
AssignMatCard(
region_name=regions[zone["uuid"]].name, material_name=materials[zone["materialUuid"]].get_name()
)
)
assignmat_cards.append(AssignMatCard(region_name=boundary.name))
self.input.assignmats = assignmat_cards
13 changes: 8 additions & 5 deletions tests/fluka/expected_fluka_output/fl_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* default physics settings for hadron therapy
DEFAULTS HADROTHE
* beam source
BEAM -0.07 PROTON
BEAM -0.07 PROTON
* beam source position
BEAMPOS 0.0 0.0 -100.0
* geometry description starts here
Expand Down Expand Up @@ -59,9 +59,12 @@
GEOEND
MATERIAL 82.0 11.36 207.0KAFNAFQO
COMPOUND -1.01 WATER QAHFTRXC
ASSIGNMA BLCKHOLE Z_BBODY
ASSIGNMA AIR Z_AIR
ASSIGNMA WATER Z_TARGET
ASSIGNMA AIR region0
ASSIGNMA QAHFTRXC region1
ASSIGNMA KAFNAFQO region2
ASSIGNMA AIR region3
ASSIGNMA WATER world
ASSIGNMA BLCKHOLE boundary
* scoring NEUTRON on mesh z
USRBIN 0.0 NEUTRON -21 0.5 0.5 5.0n_z
USRBIN -0.5 -0.5 0.0 1 1 500&
Expand Down Expand Up @@ -89,6 +92,6 @@
* random number generator settings
RANDOMIZ 137
* number of particles to simulate
START 10000.0
START 10000.0
STOP
"""
20 changes: 10 additions & 10 deletions tests/fluka/test_fluka_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@

def test_beam_line() -> None:
"""Test if BEAM line is formatted properly."""
assert str(Card("BEAM", ["-0.15"], "PROTON")) == "BEAM -0.15 PROTON "
assert str(Card("BEAM", ["200.0", "0.2", "1.5", "1.2", "0.7", "1.0"], "PROTON")) == "BEAM 200.0 0.2 1.5 1.2 0.7 1.0PROTON "
assert str(Card("BEAM", ["10.0", "0.2", "0.0", "-2.36", "-1.18", "1.0"], "PROTON")) == "BEAM 10.0 0.2 0.0 -2.36 -1.18 1.0PROTON "
assert str(Card("BEAM", ["-661.7E-06", "0.0", "1.0E4", "0.0", "0.0", "1.0"], "PHOTON")) == "BEAM -0.0006617 0.0 10000.0 0.0 0.0 1.0PHOTON "
assert str(Card("BEAM", ["-2.0", "0.0", "3.0", "0.0", "0.0", "1.0"], "MUON-")) == "BEAM -2.0 0.0 3.0 0.0 0.0 1.0MUON- "
assert str(Card("BEAM", ["120000000000000.0", "3.02", "-30.4234"], "PROTON")) == "BEAM 1.200E+14 3.02 -30.4234 PROTON "
assert str(Card("BEAM", ["-0.15"], "PROTON")) == "BEAM -0.15 PROTON"
assert str(Card("BEAM", ["200.0", "0.2", "1.5", "1.2", "0.7", "1.0"], "PROTON")) == "BEAM 200.0 0.2 1.5 1.2 0.7 1.0PROTON"
assert str(Card("BEAM", ["10.0", "0.2", "0.0", "-2.36", "-1.18", "1.0"], "PROTON")) == "BEAM 10.0 0.2 0.0 -2.36 -1.18 1.0PROTON"
assert str(Card("BEAM", ["-661.7E-06", "0.0", "1.0E4", "0.0", "0.0", "1.0"], "PHOTON")) == "BEAM -0.0006617 0.0 10000.0 0.0 0.0 1.0PHOTON"
assert str(Card("BEAM", ["-2.0", "0.0", "3.0", "0.0", "0.0", "1.0"], "MUON-")) == "BEAM -2.0 0.0 3.0 0.0 0.0 1.0MUON-"
assert str(Card("BEAM", ["120000000000000.0", "3.02", "-30.4234"], "PROTON")) == "BEAM 1.200E+14 3.02 -30.4234 PROTON"


def test_start_line() -> None:
"""Test if START line is formatted properly."""
assert str(Card("START", ["10000"])) == "START 10000.0 "
assert str(Card("START", ["5000"])) == "START 5000.0 "
assert str(Card("START", ["200000"])) == "START 200000.0 "
assert str(Card("START", ["1234567"])) == "START 1234567.0 "
assert str(Card("START", ["10000"])) == "START 10000.0"
assert str(Card("START", ["5000"])) == "START 5000.0"
assert str(Card("START", ["200000"])) == "START 200000.0"
assert str(Card("START", ["1234567"])) == "START 1234567.0"

3 changes: 2 additions & 1 deletion tests/fluka/test_region_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ def test_parse_regions(zones_json, project_fluka_json):
"""Test if regions are parsed correctly"""
figures = parse_figures(project_fluka_json["figureManager"].get('figures'))

regions, _ = parse_regions(zones_json, figures)
regions, boundary_region, _ = parse_regions(zones_json, figures)
regions = list(regions.values()) + [boundary_region]

assert regions[0].name == "region0"
assert regions[0].figures_operators == [[(BoolOperation.INTERSECTION, "fig0"), (BoolOperation.SUBTRACTION, "fig1"),
Expand Down

0 comments on commit 95c6562

Please sign in to comment.