mirror of
https://github.com/pim-n/pg-rad
synced 2026-03-23 21:58:12 +01:00
Compare commits
8 Commits
abc1195c91
...
845f006cd3
| Author | SHA1 | Date | |
|---|---|---|---|
| 845f006cd3 | |||
| ac8c38592d | |||
| a4fb4a7c57 | |||
| e8bf687563 | |||
| 49a0dcd301 | |||
| 26f96b06fe | |||
| 55258d7727 | |||
| 82331f3bbd |
@ -31,6 +31,14 @@ With the virtual environment activated, run:
|
|||||||
pip install -e .[dev]
|
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
|
||||||
|
|
||||||
Tests can be run with `pytest` from the root directory of the repository. With the virtual environment activated, run:
|
Tests can be run with `pytest` from the root directory of the repository. With the virtual environment activated, run:
|
||||||
|
|||||||
@ -7,6 +7,7 @@ where = ["src"]
|
|||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
"pg_rad.data" = ["*.csv"]
|
"pg_rad.data" = ["*.csv"]
|
||||||
|
"pg_rad.configs" = ["*.yml"]
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pg-rad"
|
name = "pg-rad"
|
||||||
@ -26,6 +27,9 @@ dependencies = [
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
license-files = ["LICEN[CS]E*"]
|
license-files = ["LICEN[CS]E*"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
pgrad = "pg_rad.main:main"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/pim-n/pg-rad"
|
Homepage = "https://github.com/pim-n/pg-rad"
|
||||||
Issues = "https://github.com/pim-n/pg-rad/issues"
|
Issues = "https://github.com/pim-n/pg-rad/issues"
|
||||||
|
|||||||
3
src/pg_rad/configs/filepaths.py
Normal file
3
src/pg_rad/configs/filepaths.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ATTENUATION_TABLE = 'attenuation_table.csv'
|
||||||
|
TEST_EXP_DATA = 'test_path_coords.csv'
|
||||||
|
LOGGING_CONFIG = 'logging.yml'
|
||||||
@ -3,7 +3,7 @@ from .isotope import Isotope
|
|||||||
|
|
||||||
class CS137(Isotope):
|
class CS137(Isotope):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super.__init__(
|
super().__init__(
|
||||||
name="Cs-137",
|
name="Cs-137",
|
||||||
E=661.66,
|
E=661.66,
|
||||||
b=0.851
|
b=0.851
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
# do not expose internal logger when running mkinit
|
# do not expose internal logger when running mkinit
|
||||||
__ignore__ = ["logger"]
|
__ignore__ = ["logger"]
|
||||||
|
|
||||||
|
from pg_rad.landscape import director
|
||||||
from pg_rad.landscape import landscape
|
from pg_rad.landscape import landscape
|
||||||
|
|
||||||
|
from pg_rad.landscape.director import (LandscapeDirector,)
|
||||||
from pg_rad.landscape.landscape import (Landscape, LandscapeBuilder,)
|
from pg_rad.landscape.landscape import (Landscape, LandscapeBuilder,)
|
||||||
|
|
||||||
__all__ = ['Landscape', 'LandscapeBuilder', 'landscape']
|
__all__ = ['Landscape', 'LandscapeBuilder', 'LandscapeDirector', 'director',
|
||||||
|
'landscape']
|
||||||
|
|||||||
23
src/pg_rad/landscape/director.py
Normal file
23
src/pg_rad/landscape/director.py
Normal 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
|
||||||
@ -16,6 +16,7 @@ class Landscape:
|
|||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
name: str,
|
||||||
path: Path,
|
path: Path,
|
||||||
point_sources: list[PointSource] = [],
|
point_sources: list[PointSource] = [],
|
||||||
size: tuple[int, int, int] = [500, 500, 50],
|
size: tuple[int, int, int] = [500, 500, 50],
|
||||||
@ -35,12 +36,13 @@ class Landscape:
|
|||||||
TypeError: _description_
|
TypeError: _description_
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.name = name
|
||||||
self.path = path
|
self.path = path
|
||||||
self.point_sources = point_sources
|
self.point_sources = point_sources
|
||||||
self.size = size
|
self.size = size
|
||||||
self.air_density = air_density
|
self.air_density = air_density
|
||||||
|
|
||||||
logger.debug("Landscape initialized.")
|
logger.debug(f"Landscape created: {self.name}")
|
||||||
|
|
||||||
def calculate_fluence_at(self, pos: tuple):
|
def calculate_fluence_at(self, pos: tuple):
|
||||||
total_phi = 0.
|
total_phi = 0.
|
||||||
@ -61,12 +63,15 @@ class Landscape:
|
|||||||
|
|
||||||
|
|
||||||
class LandscapeBuilder:
|
class LandscapeBuilder:
|
||||||
def __init__(self):
|
def __init__(self, name: str = "Unnamed landscape"):
|
||||||
|
self.name = name
|
||||||
self._path = None
|
self._path = None
|
||||||
self._point_sources = []
|
self._point_sources = []
|
||||||
self._size = None
|
self._size = None
|
||||||
self._air_density = None
|
self._air_density = None
|
||||||
|
|
||||||
|
logger.debug(f"LandscapeBuilder initialized: {self.name}")
|
||||||
|
|
||||||
def set_air_density(self, air_density) -> Self:
|
def set_air_density(self, air_density) -> Self:
|
||||||
"""Set the air density of the world."""
|
"""Set the air density of the world."""
|
||||||
self._air_density = air_density
|
self._air_density = air_density
|
||||||
@ -95,7 +100,8 @@ class LandscapeBuilder:
|
|||||||
self._path = path_from_RT90(
|
self._path = path_from_RT90(
|
||||||
df=df,
|
df=df,
|
||||||
east_col=east_col,
|
east_col=east_col,
|
||||||
north_col=north_col
|
north_col=north_col,
|
||||||
|
z=z
|
||||||
)
|
)
|
||||||
|
|
||||||
# The size of the landscape will be updated if
|
# The size of the landscape will be updated if
|
||||||
@ -108,11 +114,12 @@ class LandscapeBuilder:
|
|||||||
|
|
||||||
if needs_resize:
|
if needs_resize:
|
||||||
if not self._size:
|
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:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Path exceeds current landscape size. "
|
"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)
|
self.set_landscape_size(self._path.size)
|
||||||
@ -142,9 +149,13 @@ class LandscapeBuilder:
|
|||||||
self._point_sources = sources
|
self._point_sources = sources
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return Landscape(
|
landscape = Landscape(
|
||||||
|
name=self.name,
|
||||||
path=self._path,
|
path=self._path,
|
||||||
point_sources=self._point_sources,
|
point_sources=self._point_sources,
|
||||||
size=self._size,
|
size=self._size,
|
||||||
air_density=self._air_density
|
air_density=self._air_density
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info(f"Landscape built successfully: {landscape.name}")
|
||||||
|
return landscape
|
||||||
|
|||||||
5
src/pg_rad/logger/__init__.py
Normal file
5
src/pg_rad/logger/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from pg_rad.logger import logger
|
||||||
|
|
||||||
|
from pg_rad.logger.logger import (setup_logger,)
|
||||||
|
|
||||||
|
__all__ = ['logger', 'setup_logger']
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import logging
|
import logging.config
|
||||||
import pathlib
|
from importlib.resources import files
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from pg_rad.configs.filepaths import LOGGING_CONFIG
|
||||||
|
|
||||||
|
|
||||||
def setup_logger(log_level: str = "WARNING"):
|
def setup_logger(log_level: str = "WARNING"):
|
||||||
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
@ -10,8 +12,7 @@ def setup_logger(log_level: str = "WARNING"):
|
|||||||
if log_level not in levels:
|
if log_level not in levels:
|
||||||
raise ValueError(f"Log level must be one of {levels}.")
|
raise ValueError(f"Log level must be one of {levels}.")
|
||||||
|
|
||||||
base_dir = pathlib.Path(__file__).resolve().parent
|
config_file = files('pg_rad.configs').joinpath(LOGGING_CONFIG)
|
||||||
config_file = base_dir / "configs" / "logging.yml"
|
|
||||||
|
|
||||||
with open(config_file) as f:
|
with open(config_file) as f:
|
||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
@ -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
33
src/pg_rad/main.py
Normal 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()
|
||||||
@ -6,7 +6,7 @@ import numpy as np
|
|||||||
class BaseObject:
|
class BaseObject:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pos: tuple[float, float, float],
|
pos: tuple[float, float, float] = (0, 0, 0),
|
||||||
name: str = "Unnamed object",
|
name: str = "Unnamed object",
|
||||||
color: str = 'grey'):
|
color: str = 'grey'):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -11,22 +11,23 @@ class PointSource(BaseObject):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pos: tuple,
|
|
||||||
activity: int,
|
activity: int,
|
||||||
isotope: Isotope,
|
isotope: Isotope,
|
||||||
|
pos: tuple[float, float, float] = (0, 0, 0),
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
color: str = "red"):
|
color: str = 'red'
|
||||||
|
):
|
||||||
"""A point source.
|
"""A point source.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pos (tuple): a position vector of length 3 (x,y,z).
|
|
||||||
activity (int): Activity A in MBq.
|
activity (int): Activity A in MBq.
|
||||||
isotope (Isotope): The isotope.
|
isotope (Isotope): The isotope.
|
||||||
name (str | None, optional): Can give the source a unique name.
|
pos (tuple[float, float, float], optional):
|
||||||
Defaults to None, making the name sequential.
|
Position of the PointSource.
|
||||||
(Source-1, Source-2, etc.).
|
name (str, optional): Can give the source a unique name.
|
||||||
color (str, optional): Matplotlib compatible color string.
|
If not provided, point sources are sequentially
|
||||||
Defaults to "red".
|
named: Source1, Source2, ...
|
||||||
|
color (str, optional): Matplotlib compatible color string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.id = PointSource._id_counter
|
self.id = PointSource._id_counter
|
||||||
|
|||||||
@ -42,14 +42,18 @@ class Path:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coord_list: Sequence[tuple[float, float]],
|
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.
|
"""Construct a path of sequences based on a list of coordinates.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coord_list (Sequence[tuple[float, float]]): List of x,y
|
coord_list (Sequence[tuple[float, float]]): List of x,y
|
||||||
coordinates.
|
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:
|
if len(coord_list) < 2:
|
||||||
@ -73,7 +77,7 @@ class Path:
|
|||||||
self.size = (
|
self.size = (
|
||||||
np.ceil(max(self.x_list)),
|
np.ceil(max(self.x_list)),
|
||||||
np.ceil(max(self.y_list)),
|
np.ceil(max(self.y_list)),
|
||||||
z
|
z + z_box
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Path created.")
|
logger.debug("Path created.")
|
||||||
|
|||||||
@ -3,11 +3,13 @@ from importlib.resources import files
|
|||||||
from pandas import read_csv
|
from pandas import read_csv
|
||||||
from scipy.interpolate import interp1d
|
from scipy.interpolate import interp1d
|
||||||
|
|
||||||
|
from pg_rad.configs.filepaths import ATTENUATION_TABLE
|
||||||
|
|
||||||
|
|
||||||
def get_mass_attenuation_coeff(
|
def get_mass_attenuation_coeff(
|
||||||
*args
|
*args
|
||||||
) -> float:
|
) -> float:
|
||||||
csv = files('pg_rad.data').joinpath('attenuation_table.csv')
|
csv = files('pg_rad.data').joinpath(ATTENUATION_TABLE)
|
||||||
data = read_csv(csv)
|
data = read_csv(csv)
|
||||||
x = data["energy_mev"].to_numpy()
|
x = data["energy_mev"].to_numpy()
|
||||||
y = data["mu"].to_numpy()
|
y = data["mu"].to_numpy()
|
||||||
|
|||||||
Reference in New Issue
Block a user