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

Refactoring tests #123

Merged
Merged
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
10 changes: 5 additions & 5 deletions .github/workflows/test-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
pip3 install -r test_requirements.txt

- name: Run main tests
run: python -m pytest tests/
run: python -m pytest


build-and-test:
Expand Down Expand Up @@ -61,7 +61,7 @@ jobs:

- name: Run main tests
# when triggering tests with `python -m pytest` (instead of simply calling `pytest`) we add current directory to sys.path
run: python -m pytest tests/
run: python -m pytest

make-package:
runs-on: ubuntu-latest
Expand All @@ -87,7 +87,7 @@ jobs:
name: packages
path: |
./dist
./input_examples
./tests/shieldhit/resources

test-package:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -117,6 +117,6 @@ jobs:
- name: Test package
run: |
yaptide-converter -h
yaptide-converter ./input_examples/sh_parser_test.json
yaptide-converter ./tests/shieldhit/resources/project.json
python3 -m converter.main -h
python3 -m converter.main ./input_examples/sh_parser_test.json
python3 -m converter.main ./tests/shieldhit/resources/project.json
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,4 @@ cython_debug/
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,pycharm

**/*.dat
!input_examples/expected_shieldhit_output/*.dat
!tests/shieldhit/resources/expected_shieldhit_output/*.dat
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
The Converter of the project file (JSON file generated by frontend part) into a set of input files for particle transport simulators:
- SHIELD-HIT12A (beam.dat, mat.dat, geo.dat and detect.dat).
- TOPAS
- Fluka

## Requirements
- Python 3.9+
Expand All @@ -28,7 +29,7 @@ It is capable of transforming JSON project file (generated in the yaptide web in
To run the converter use the following command:

```bash
python converter/main.py editor.json workspace
python converter/main.py tests/shieldhit/resources/project.json workspace
```

## Testing
Expand Down
6 changes: 2 additions & 4 deletions converter/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path
from typing import Union
from converter.shieldhit.parser import DummmyParser as SHDummyParser, ShieldhitParser
from converter.shieldhit.parser import ShieldhitParser
from converter.topas.parser import TopasParser
from converter.common import Parser
from converter.fluka.parser import FlukaParser
Expand All @@ -9,8 +9,6 @@
def get_parser_from_str(parser_type: str) -> Parser:
"""Get a converter object based on the provided type."""
# This is temporary, suggestions on how to do this better appreciated.
if parser_type.lower() == 'sh_dummy':
return SHDummyParser()
if parser_type.lower() == 'shieldhit':
return ShieldhitParser()
if parser_type.lower() == 'topas':
Expand All @@ -19,7 +17,7 @@ def get_parser_from_str(parser_type: str) -> Parser:
return FlukaParser()

print(f"Invalid parser type \"{parser_type}\".")
raise ValueError("Parser type must be either 'sh_dummy', 'shieldhit', 'topas' or 'fluka'.")
raise ValueError("Parser type must be either 'shieldhit', 'topas' or 'fluka'.")


def run_parser(parser: Parser, input_data: dict, output_dir: Union[Path, None] = None, silent: bool = True) -> dict:
Expand Down
26 changes: 20 additions & 6 deletions converter/common.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
from abc import ABC, abstractmethod
from pathlib import Path


class Parser(ABC):
class Parser:
"""Abstract parser, the template for implementing other parsers."""

@abstractmethod
def __init__(self) -> None:
self.info = {
"version": "",
"label": "",
"simulator": "",
}

def parse_configs(self, json: dict) -> None:
"""Convert the json dict to the 4 config dataclasses."""
raise NotImplementedError

@abstractmethod
def save_configs(self, target_dir: Path) -> None:
def save_configs(self, target_dir: str):
"""
Save the configs as text files in the target_dir.
The files are: beam.dat, mat.dat, detect.dat and geo.dat.
"""
if not Path(target_dir).exists():
raise ValueError("Target directory does not exist.")

for file_name, content in self.get_configs_json().items():
with open(Path(target_dir, file_name), 'w') as conf_f:
conf_f.write(content)

@abstractmethod
def get_configs_json(self) -> dict:
"""
Return a dict representation of the config files. Each element has
the config files name as key and its content as value.
"""
configs_json = {
"info.json": str(self.info),
}
return configs_json
25 changes: 5 additions & 20 deletions converter/fluka/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from pathlib import Path
from converter.common import Parser


DEFAULT_CONFIG = """TITLE
proton beam simulation
* default physics settings for hadron therapy
Expand Down Expand Up @@ -67,32 +65,19 @@ class FlukaParser(Parser):
"""A simple placeholder that ignores the json input and prints example (default) configs."""

def __init__(self) -> None:
version = "unknown"
self.info = {
"version": version,
"label": "development",
"simulator": "fluka",
}
super().__init__()
self.info['simulator'] = 'fluka'
self.info['version'] = 'unknown'

def parse_configs(self, json: dict) -> None:
"""Basicaly do nothing since we work on defaults in this parser."""

def save_configs(self, target_dir: Path) -> None:
"""
This will save the Fluka Configuration to a file named fl_sim.imp
in the target_dir directory.
"""
if not Path(target_dir).exists():
raise FileNotFoundError("Target directory doest not exist.")
for file_name, content in self.get_configs_json().items():
with open(Path(target_dir, file_name), "w") as conf_f:
conf_f.write(content)

def get_configs_json(self) -> dict:
"""
Return a dict representation of the config files. Each element has
the config files name as key and its content as value.
"""
configs_json = {"info.json": str(self.info), "fl_sim.inp": DEFAULT_CONFIG}
configs_json = super().get_configs_json()
configs_json["fl_sim.inp"] = DEFAULT_CONFIG

return configs_json
3 changes: 2 additions & 1 deletion converter/shieldhit/beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ class BeamConfig:
energy_cutoff_template = "TCUT0 {energy_low_cutoff} {energy_high_cutoff} ! energy cutoffs [MeV]"
sad_template = "BEAMSAD {sad_x} {sad_y} ! BEAMSAD value [cm]"
beam_source_type: BeamSourceType = BeamSourceType.SIMPLE
beam_source_file: Optional[str] = None
beam_source_filename: Optional[str] = None
beam_source_file_content: Optional[str] = None

beam_dat_template: str = """
RNDSEED 89736501 ! Random seed
Expand Down
23 changes: 12 additions & 11 deletions converter/shieldhit/detect.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass, field
from typing import Optional
from converter.shieldhit.scoring_geometries import ScoringGeometry, ScoringCylinder, ScoringMesh


Expand Down Expand Up @@ -28,10 +29,10 @@ class OutputQuantity:

detector_type: str
filter_name: str = ""
diff1: tuple[float, float, float, str] = None
diff1_t: str = None
diff2: tuple[float, float, float, str] = None
diff2_t: str = None
diff1: Optional[tuple[float, float, float, str]] = None
diff1_t: Optional[str] = None
diff2: Optional[tuple[float, float, float, str]] = None
diff2_t: Optional[str] = None

quantity_template: str = """
Quantity {detector_type} {filter_name}"""
Expand All @@ -52,14 +53,14 @@ def __str__(self) -> str:
class ScoringOutput:
"""Dataclass storing information about shieldhit scoring outputs."""

filename: str = None
fileformat: str = None
geometry: str = None
medium: str = None
offset: float = None
primaries: float = None
filename: Optional[str] = None
fileformat: Optional[str] = None
geometry: Optional[str] = None
medium: Optional[str] = None
offset: Optional[float] = None
primaries: Optional[int] = None
quantities: list[OutputQuantity] = field(default_factory=lambda: [])
rescale: float = None
rescale: Optional[float] = None

filename_str_template: str = """
Filename {filename}"""
Expand Down
21 changes: 11 additions & 10 deletions converter/shieldhit/geo.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Optional
from converter.solid_figures import SolidFigure, BoxFigure, CylinderFigure, SphereFigure
from dataclasses import dataclass, field
from math import log10, ceil, isclose
Expand All @@ -17,7 +18,7 @@ class DefaultMaterial(IntEnum):

@staticmethod
def is_default_material(material_value: int) -> bool:
"""Check if the material is one of the predefined materials."""
"""Check if the material is one of the predefined default materials."""
return material_value in DefaultMaterial._value2member_map_


Expand All @@ -29,7 +30,7 @@ def format_float(number: float, n: int) -> float:
"""
result = number
# If number is zero we just want to get 0.0 (it would mess up the log10 operation below)
if isclose(result, 0.):
if isclose(result, 0., rel_tol=1e-9):
return 0.

length = n
Expand Down Expand Up @@ -68,14 +69,14 @@ def format_float(number: float, n: int) -> float:

# Formatting negative numbers smaller than the desired precission could result in -0.0 or 0.0 randomly.
# To avoid this we catch -0.0 and return 0.0.
if isclose(result, 0.):
if isclose(result, 0., rel_tol=1e-9):
return 0.

return result


def parse_figure(figure: SolidFigure, number: int) -> str:
"""Parse a SolidFigure into a str representation of SH12A input file."""
"""Parse a SolidFigure into a string representation of SH12A input file."""
if type(figure) is BoxFigure:
return _parse_box(figure, number)
if type(figure) is CylinderFigure:
Expand Down Expand Up @@ -161,7 +162,7 @@ class Material:

uuid: str
icru: int
density: float = None
density: Optional[float] = None
idx: int = 0

property_template = """{name} {value}\n"""
Expand All @@ -184,7 +185,7 @@ class Zone:

id: int = 1
figures_operators: list[set[int]] = field(default_factory=lambda: [{1}])
material: str = "0"
material: int = 0
material_override: dict[str, str] = field(default_factory=dict)

zone_template: str = """
Expand Down Expand Up @@ -214,19 +215,19 @@ class GeoMatConfig:
figures_operators=[{
1,
}],
material="1",
material=1,
),
Zone(
uuid="",
id=2,
figures_operators=[{-1, 2}],
material="1000",
material=1000,
),
Zone(
uuid="",
id=3,
figures_operators=[{-2, 3}],
material="0",
material=0,
),
])
materials: list[Material] = field(default_factory=lambda: [Material('', 276)])
Expand All @@ -244,7 +245,7 @@ class GeoMatConfig:
"""

@staticmethod
def _split_zones_to_rows(zones: list[Zone], max_size=14) -> list[list[Zone]]:
def _split_zones_to_rows(zones: list[int], max_size=14) -> list[list[int]]:
"""Split list of Zones into rows of not more than 14 elements"""
return [zones[i:min(i + max_size, len(zones))] for i in range(0, len(zones), max_size)]

Expand Down
Loading