Source code for mapstp.materials
"""Code to load materials map.
The map associates material number to its MCNP specification text.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, TextIO
from collections import defaultdict
from dataclasses import dataclass, field
from logging import getLogger
from pathlib import Path
import numpy as np
from mapstp.utils._re import CARD_PATTERN, MATERIAL_PATTERN
if TYPE_CHECKING:
import sqlite3 as sq
from collections.abc import Callable, Generator, Iterable
import pandas as pd
MaterialsDict = dict[int, str]
"""Mapping material number -> material MCNP text."""
logger = getLogger()
@dataclass
class _Loader:
stream: TextIO
material_no: int = field(default=-1, init=False)
materials_dict: dict[int, list[str]] = field(
default_factory=lambda: defaultdict(list),
init=False,
)
def __post_init__(self: _Loader) -> None:
for line in self.stream:
self._process_line(line)
@property
def _in_material_card(self: _Loader) -> bool:
return self.material_no > 0
def _process_line(self: _Loader, line: str) -> None:
if self._in_material_card:
match = CARD_PATTERN.search(line)
if not match:
self._append(line)
return
if match.lastgroup != "comment" and not self._check_if_material_line(line):
self.material_no = -1
else:
self._check_if_material_line(line)
def _append(self: _Loader, line: str) -> None:
if self.material_no > 0:
self.materials_dict[self.material_no].append(line)
def _check_if_material_line(self: _Loader, line: str) -> bool:
match = MATERIAL_PATTERN.search(line)
if match:
self.material_no = int(match["material"])
if self.material_no <= 0:
msg = f"Wrong material number {self.material_no} found"
raise ValueError(msg)
if self.material_no in self.materials_dict:
msg = f"Material number {self.material_no} is duplicated"
raise ValueError(msg)
self._append(line)
return True
return False # skipping other cards and prepending text
[docs]
def load_materials_map_from_stream(stream: TextIO) -> MaterialsDict:
"""Read materials from opened MCNP file.
Args:
stream: stream to read from
Returns:
MaterialsDict: mapping material number -> material text
"""
loader = _Loader(stream)
def _restore_material_text(lines: Iterable[str]) -> str:
result = "".join(lines)
if not result.endswith("\n"):
result += "\n"
return result
return {k: _restore_material_text(v) for k, v in loader.materials_dict.items()}
[docs]
def load_materials_map(materials: str | Path) -> MaterialsDict:
"""Read materials from MCNP file.
Args:
materials: name of MCNP file, containing materials to read
Returns:
MaterialsDict: mapping material number -> material text
"""
path = Path(materials)
with path.open(encoding="cp1251") as stream:
return load_materials_map_from_stream(stream)
[docs]
def drop_material_cards(lines: Iterable[str]) -> Generator[str]:
"""Drop lines belonging to material cards.
Used on replacing materials in the model with ones actually used.
Args:
lines: mcnp file split to lines
Yields:
all the lines of the model without material cards
"""
in_material_card = False
for line in lines:
match = CARD_PATTERN.search(line)
if match and match.lastgroup == "card":
in_material_card = MATERIAL_PATTERN.search(line) is not None
if not in_material_card:
yield line
[docs]
def materials_spec_mapper(materials_map: dict[int, str]) -> Callable[[int], str]:
"""Create method to extract a material specification by its number.
Args:
materials_map: map number -> spec
Returns:
method to be used in map extracting material specification.
"""
def _func(used_number: int) -> str:
if used_number > 0:
text = materials_map.get(used_number)
if not text:
logger.warning(
"Material M%s is not found "
"in provided materials specifications. "
"A dummy specification is issued to the tagged model.",
used_number,
)
text = (
f"m{used_number} "
"$ dummy: material was not provided to mapstp\n"
" 1001.31c 1.0\n"
)
return text
return ""
return _func
[docs]
def get_used_materials(materials_map: dict[int, str], path_info: pd.DataFrame) -> str:
"""Collect text of used materials specifications.
Args:
materials_map: map material number -> spec.
path_info: dataframe containing column with used material numbers.
Returns:
All the used materials specs to be used as part of MCNP model text.
"""
values = path_info["material_number"].to_numpy()
used_numbers = sorted({int(m) for m in values if not np.isnan(m)})
used_materials_texts = list(map(materials_spec_mapper(materials_map), used_numbers))
return "".join(used_materials_texts)
[docs]
def get_used_materials_sql(con: sq.Connection, materials_map: dict[int, str]) -> str:
"""Collect text of used materials specifications.
Args:
con: database connection
materials_map: map material number -> spec.
"""
used_numbers = [
x[0]
for x in con.execute(
"""
select distinct material
from cells
where material not null
order by material
""",
)
]
used_materials_texts = list(map(materials_spec_mapper(materials_map), used_numbers))
return "".join(used_materials_texts)