Compare commits

...

8 Commits

16 changed files with 123 additions and 30 deletions

View File

@ -31,6 +31,14 @@ With the virtual environment activated, run:
pip install -e .[dev]
```
## Running example landscape
The example landscape can be generated using the command-line interface. Still in the virtual environment, run
```
pgrad --test --loglevel DEBUG
```
## Tests
Tests can be run with `pytest` from the root directory of the repository. With the virtual environment activated, run:

View File

@ -7,6 +7,7 @@ where = ["src"]
[tool.setuptools.package-data]
"pg_rad.data" = ["*.csv"]
"pg_rad.configs" = ["*.yml"]
[project]
name = "pg-rad"
@ -26,6 +27,9 @@ dependencies = [
license = "MIT"
license-files = ["LICEN[CS]E*"]
[project.scripts]
pgrad = "pg_rad.main:main"
[project.urls]
Homepage = "https://github.com/pim-n/pg-rad"
Issues = "https://github.com/pim-n/pg-rad/issues"

View File

@ -0,0 +1,3 @@
ATTENUATION_TABLE = 'attenuation_table.csv'
TEST_EXP_DATA = 'test_path_coords.csv'
LOGGING_CONFIG = 'logging.yml'

View File

@ -3,7 +3,7 @@ from .isotope import Isotope
class CS137(Isotope):
def __init__(self):
super.__init__(
super().__init__(
name="Cs-137",
E=661.66,
b=0.851

View File

@ -1,8 +1,11 @@
# do not expose internal logger when running mkinit
__ignore__ = ["logger"]
from pg_rad.landscape import director
from pg_rad.landscape import landscape
from pg_rad.landscape.director import (LandscapeDirector,)
from pg_rad.landscape.landscape import (Landscape, LandscapeBuilder,)
__all__ = ['Landscape', 'LandscapeBuilder', 'landscape']
__all__ = ['Landscape', 'LandscapeBuilder', 'LandscapeDirector', 'director',
'landscape']

View File

@ -0,0 +1,23 @@
from importlib.resources import files
import logging
from pg_rad.configs.filepaths import TEST_EXP_DATA
from pg_rad.isotopes import CS137
from pg_rad.landscape.landscape import LandscapeBuilder
from pg_rad.objects import PointSource
class LandscapeDirector:
def __init__(self):
self.logger = logging.getLogger(__name__)
self.logger.debug("LandscapeDirector initialized.")
def build_test_landscape(self):
fp = files('pg_rad.data').joinpath(TEST_EXP_DATA)
source = PointSource(activity=100, isotope=CS137(), pos=(0, 0, 0))
lb = LandscapeBuilder("Test landscape")
lb.set_air_density(1.243)
lb.set_path_from_experimental_data(fp, z=0)
lb.set_point_sources(source)
landscape = lb.build()
return landscape

View File

@ -16,6 +16,7 @@ class Landscape:
"""
def __init__(
self,
name: str,
path: Path,
point_sources: list[PointSource] = [],
size: tuple[int, int, int] = [500, 500, 50],
@ -35,12 +36,13 @@ class Landscape:
TypeError: _description_
"""
self.name = name
self.path = path
self.point_sources = point_sources
self.size = size
self.air_density = air_density
logger.debug("Landscape initialized.")
logger.debug(f"Landscape created: {self.name}")
def calculate_fluence_at(self, pos: tuple):
total_phi = 0.
@ -61,12 +63,15 @@ class Landscape:
class LandscapeBuilder:
def __init__(self):
def __init__(self, name: str = "Unnamed landscape"):
self.name = name
self._path = None
self._point_sources = []
self._size = None
self._air_density = None
logger.debug(f"LandscapeBuilder initialized: {self.name}")
def set_air_density(self, air_density) -> Self:
"""Set the air density of the world."""
self._air_density = air_density
@ -95,7 +100,8 @@ class LandscapeBuilder:
self._path = path_from_RT90(
df=df,
east_col=east_col,
north_col=north_col
north_col=north_col,
z=z
)
# The size of the landscape will be updated if
@ -108,11 +114,12 @@ class LandscapeBuilder:
if needs_resize:
if not self._size:
logger.info("Landscape size set to path dimensions.")
logger.debug("Because no Landscape size was set, "
"it will now set to path dimensions.")
else:
logger.warning(
"Path exceeds current landscape size. "
"Expanding landscape to accommodate it."
"Landscape size will be expanded to accommodate path."
)
self.set_landscape_size(self._path.size)
@ -142,9 +149,13 @@ class LandscapeBuilder:
self._point_sources = sources
def build(self):
return Landscape(
landscape = Landscape(
name=self.name,
path=self._path,
point_sources=self._point_sources,
size=self._size,
air_density=self._air_density
)
logger.info(f"Landscape built successfully: {landscape.name}")
return landscape

View File

@ -0,0 +1,5 @@
from pg_rad.logger import logger
from pg_rad.logger.logger import (setup_logger,)
__all__ = ['logger', 'setup_logger']

View File

@ -1,8 +1,10 @@
import logging
import pathlib
import logging.config
from importlib.resources import files
import yaml
from pg_rad.configs.filepaths import LOGGING_CONFIG
def setup_logger(log_level: str = "WARNING"):
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
@ -10,8 +12,7 @@ def setup_logger(log_level: str = "WARNING"):
if log_level not in levels:
raise ValueError(f"Log level must be one of {levels}.")
base_dir = pathlib.Path(__file__).resolve().parent
config_file = base_dir / "configs" / "logging.yml"
config_file = files('pg_rad.configs').joinpath(LOGGING_CONFIG)
with open(config_file) as f:
config = yaml.safe_load(f)

View File

@ -1,5 +0,0 @@
from pg_rad.logging import logger
from pg_rad.logging.logger import (setup_logger,)
__all__ = ['logger', 'setup_logger']

33
src/pg_rad/main.py Normal file
View File

@ -0,0 +1,33 @@
import argparse
from pg_rad.logger import setup_logger
from pg_rad.landscape import LandscapeDirector
def main():
parser = argparse.ArgumentParser(
prog="pg-rad",
description="Primary Gamma RADiation landscape tool"
)
parser.add_argument(
"--test",
action="store_true",
help="Load and run the test landscape"
)
parser.add_argument(
"--loglevel",
default="INFO",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
)
args = parser.parse_args()
setup_logger(args.loglevel)
if args.test:
landscape = LandscapeDirector().build_test_landscape()
print(landscape.name)
if __name__ == "__main__":
main()

View File

@ -6,7 +6,7 @@ import numpy as np
class BaseObject:
def __init__(
self,
pos: tuple[float, float, float],
pos: tuple[float, float, float] = (0, 0, 0),
name: str = "Unnamed object",
color: str = 'grey'):
"""

View File

@ -11,22 +11,23 @@ class PointSource(BaseObject):
def __init__(
self,
pos: tuple,
activity: int,
isotope: Isotope,
pos: tuple[float, float, float] = (0, 0, 0),
name: str | None = None,
color: str = "red"):
color: str = 'red'
):
"""A point source.
Args:
pos (tuple): a position vector of length 3 (x,y,z).
activity (int): Activity A in MBq.
isotope (Isotope): The isotope.
name (str | None, optional): Can give the source a unique name.
Defaults to None, making the name sequential.
(Source-1, Source-2, etc.).
color (str, optional): Matplotlib compatible color string.
Defaults to "red".
pos (tuple[float, float, float], optional):
Position of the PointSource.
name (str, optional): Can give the source a unique name.
If not provided, point sources are sequentially
named: Source1, Source2, ...
color (str, optional): Matplotlib compatible color string
"""
self.id = PointSource._id_counter

View File

@ -42,14 +42,18 @@ class Path:
def __init__(
self,
coord_list: Sequence[tuple[float, float]],
z: float = 0
z: float = 0.,
z_box: float = 50.
):
"""Construct a path of sequences based on a list of coordinates.
Args:
coord_list (Sequence[tuple[float, float]]): List of x,y
coordinates.
z (float, optional): Height of the path. Defaults to 0.
z (float, optional): position of the path in z-direction in meters.
Defaults to 0 meters.
z_box (float, optional): How much empty space to set
above the path in meters. Defaults to 50 meters.
"""
if len(coord_list) < 2:
@ -73,7 +77,7 @@ class Path:
self.size = (
np.ceil(max(self.x_list)),
np.ceil(max(self.y_list)),
z
z + z_box
)
logger.debug("Path created.")

View File

@ -3,11 +3,13 @@ from importlib.resources import files
from pandas import read_csv
from scipy.interpolate import interp1d
from pg_rad.configs.filepaths import ATTENUATION_TABLE
def get_mass_attenuation_coeff(
*args
) -> float:
csv = files('pg_rad.data').joinpath('attenuation_table.csv')
csv = files('pg_rad.data').joinpath(ATTENUATION_TABLE)
data = read_csv(csv)
x = data["energy_mev"].to_numpy()
y = data["mu"].to_numpy()