├── .github └── workflows │ ├── black.yml │ ├── ci_with_install.yml │ ├── lint.yaml │ └── python-publish.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── environment.yml ├── examples ├── example_gamma_spec_plot.py ├── example_get_particle_data.py ├── example_plot_plasma_source_position.py ├── example_plot_source_direction.py ├── example_plot_source_energy.py ├── example_plot_source_position.py ├── example_plot_two_source_energies.py └── gamma_spec.png ├── pyproject.toml ├── src └── openmc_source_plotter │ ├── __init__.py │ ├── core.py │ └── material.py └── tests ├── test_core_with_model.py ├── test_core_with_settings.py ├── test_core_with_source.py └── todo.md /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: black 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.py' 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | jobs: 13 | black: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | with: 18 | ref: ${{ github.head_ref }} 19 | - name: Setup Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.x 23 | - name: Install black 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install black 27 | - name: Run black 28 | run: | 29 | black . 30 | - uses: stefanzweifel/git-auto-commit-action@v4 31 | with: 32 | commit_message: "[skip ci] Apply formatting changes" 33 | -------------------------------------------------------------------------------- /.github/workflows/ci_with_install.yml: -------------------------------------------------------------------------------- 1 | 2 | # This CI will launch a Docker image that contains all the dependencies required 3 | # within that image the pytest test suite is run 4 | 5 | name: CI with install 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - develop 14 | - main 15 | paths-ignore: 16 | - 'docs/**' 17 | - '.gitignore' 18 | - '*.md' 19 | - 'CITATION.cff' 20 | - 'LICENSE.txt' 21 | - 'readthedocs.yml' 22 | 23 | jobs: 24 | testing: 25 | runs-on: ubuntu-latest 26 | container: 27 | image: openmc/openmc:develop 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: install package 33 | run: | 34 | pip install . 35 | python -c "import openmc_source_plotter" 36 | 37 | - name: Run examples 38 | run: | 39 | python examples/example_get_particle_data.py 40 | python examples/example_plot_source_direction.py 41 | python examples/example_plot_source_energy.py 42 | python examples/example_plot_source_position.py 43 | python examples/example_plot_two_source_energies.py 44 | pip install openmc_plasma_source 45 | # python examples/example_plot_plasma_source_position.py 46 | 47 | - name: Run test_utils 48 | run: | 49 | pip install .[tests] 50 | pytest tests -v --cov=openmc_source_plotter --cov-append --cov-report term --cov-report xml 51 | 52 | - name: Upload to codecov 53 | uses: codecov/codecov-action@v2 54 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Python Flake8 Lint 2 | 3 | on: 4 | # Trigger the workflow pull requests to any branch 5 | pull_request: 6 | 7 | jobs: 8 | run-linters: 9 | name: lint 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check out Git repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: "3.10" 20 | 21 | - name: Install Python dependencies 22 | run: pip install flake8 23 | 24 | - name: Run linters 25 | uses: wearerequired/lint-action@v2 26 | with: 27 | flake8: true 28 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This yml file will trigger a Github Actions event that builds and upload the 2 | # Python package to PiPy. This makes use of Twine and is triggered when a push 3 | # to the main branch occures. For more information see: 4 | # https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 5 | # and for details on the Autobump version section see: 6 | # https://github.com/grst/python-ci-versioneer 7 | 8 | name: Upload Python Package 9 | 10 | on: 11 | # allows us to run workflows manually 12 | workflow_dispatch: 13 | release: 14 | types: [created] 15 | 16 | jobs: 17 | deploy: 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: '3.x' 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install setuptools wheel build twine 32 | 33 | - name: Build and publish 34 | env: 35 | TWINE_USERNAME: __token__ 36 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 37 | run: | 38 | python -m build 39 | twine check dist/* 40 | twine upload dist/* 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # openmc files 132 | *.h5 133 | *.xml 134 | 135 | # version file 136 | openmc_source_plotter/_version.py 137 | 138 | # plots 139 | *.png 140 | src/openmc_source_plotter/_version.py 141 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fusion Energy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI with install](https://github.com/fusion-energy/openmc_source_plotter/actions/workflows/ci_with_install.yml/badge.svg?branch=main)](https://github.com/fusion-energy/openmc_source_plotter/actions/workflows/ci_with_install.yml) 2 | 3 | [![Upload Python Package](https://github.com/fusion-energy/openmc_source_plotter/actions/workflows/python-publish.yml/badge.svg)](https://github.com/fusion-energy/openmc_source_plotter/actions/workflows/python-publish.yml) 4 | 5 | A Python package for plotting the positions, directions or energy distributions of OpenMC sources. 6 | 7 | # Installation 8 | 9 | You will need to have OpenMC version 0.14.0 or newer installed first. 10 | 11 | ```bash 12 | pip install openmc_source_plotter 13 | ``` 14 | 15 | # Features 16 | 17 | The package provides three plotting functions that can plot source data from openmc objects. 18 | - ```plot_source_energy``` 19 | - ```plot_source_position``` 20 | - ```plot_source_direction``` 21 | - ```plot_gamma_emission``` 22 | 23 | Additionally the package provides a convienient method of sampling particles 24 | - ```sample_initial_particles``` 25 | 26 | 27 | # Example plots 28 | 29 | Below are some basic examples, for more examples see the [examples folder](https://github.com/fusion-energy/openmc_source_plotter/tree/main/examples) for example usage scripts. 30 | 31 | 32 | ## Plot of energy distribution of the source 33 | 34 | :link:[Link](https://github.com/fusion-energy/openmc_source_plotter/blob/main/examples/example_plot_source_energy.py) to example script. 35 | 36 | ![openmc particle source energy plot](https://user-images.githubusercontent.com/8583900/143615694-a3578115-f8a2-4971-bf26-458177b4f113.png) 37 | 38 | ## Plot of energy distribution of two sources 39 | 40 | :link:[Link](https://github.com/fusion-energy/openmc_source_plotter/blob/main/examples/example_plot_two_source_energies.py) to example script. 41 | 42 | ![openmc particle source energy plot](https://user-images.githubusercontent.com/8583900/151376414-fb1555eb-61d1-4c82-bc4d-a05f62819c5d.png) 43 | 44 | ## Plot direction of particles 45 | 46 | :link:[Link](https://github.com/fusion-energy/openmc_source_plotter/blob/main/examples/example_plot_source_direction.py) to example script. 47 | 48 | ![openmc particle source direction plot](https://user-images.githubusercontent.com/8583900/143615706-3b3a8467-0233-42d6-a66c-d536c80a01d8.png) 49 | 50 | 51 | ## Plot position of particles 52 | 53 | :link:[Link](https://github.com/fusion-energy/openmc_source_plotter/blob/main/examples/example_plot_source_position.py) to example script. 54 | 55 | 56 | ![openmc particle source position plot](https://user-images.githubusercontent.com/8583900/179424915-bee56a87-6214-46ef-8625-92b8f4cbd1b3.png) 57 | 58 | ## Plot labeled gamma lines from material 59 | 60 | :link:[Link](https://github.com/fusion-energy/openmc_source_plotter/blob/main/examples/example_gamma_spec_plot.py) to example script. 61 | 62 | ![gamma spec with labels](examples/gamma_spec.png) 63 | 64 | 65 | ## Extract particle objects 66 | 67 | A list of ```openmc.Particle``` objects can be obtained using ```model.sample_initial_particles()``` or ```openmc.SourceBase.sample_initial_particles()``` 68 | 69 | ```python 70 | import openmc 71 | from openmc_source_plotter import sample_initial_particles 72 | 73 | settings = openmc.Settings() 74 | settings.particles = 1 75 | settings.batches = 1 76 | my_source = openmc.IndependentSource() 77 | my_source.energy = openmc.stats.muir(e0=14080000.0, m_rat=5.0, kt=20000.0) 78 | settings.source = my_source 79 | materials = openmc.Materials() 80 | sph = openmc.Sphere(r=100, boundary_type="vacuum") 81 | cell = openmc.Cell(region=-sph) 82 | geometry = openmc.Geometry([cell]) 83 | 84 | model = openmc.Model(geometry, materials, settings) 85 | 86 | particles = sample_initial_particles(this=model, n_samples=10) 87 | 88 | print(particles) 89 | >>>[, , , , , , , , , ] 90 | 91 | print(particles[0].E) 92 | >>>1.440285e+07 93 | ``` 94 | 95 | ## Related packages 96 | 97 | Tokamak sources can also be plotted using the [openmc-plasma-source](https://github.com/fusion-energy/openmc-plasma-source) package 98 | 99 | ![openmc_source_plotter_openmc-plasma-source_tokamak](https://user-images.githubusercontent.com/8583900/187487894-ba0bd025-46f2-4c7d-8b15-3d260aed47a0.png) 100 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # build with 2 | # conda env create -f environment.yml 3 | 4 | name: openmc_source_plotter_environment 5 | 6 | channels: 7 | - conda-forge 8 | 9 | dependencies: 10 | - openmc "openmc=0.13.1=dagmc*nompi*" 11 | -------------------------------------------------------------------------------- /examples/example_gamma_spec_plot.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import plot_gamma_emission 3 | 4 | # you will need to install optional dependancies for this example 5 | # pip install openmc_source_plotter[gammas] 6 | 7 | # this path will need changing to point to your chain file 8 | # openmc.config["chain_file"] = "chain-endf.xml" 9 | 10 | my_material = openmc.Material() 11 | my_material.add_nuclide("Xe135", 1e-12) 12 | my_material.add_nuclide("U235", 1) 13 | my_material.add_nuclide("U238", 1) 14 | my_material.add_nuclide("Co60", 1e-9) 15 | my_material.volume = 1 # must be set so number of atoms can be found 16 | 17 | # adds labels to the most active 3 gamma energies 18 | plt = plot_gamma_emission(material=my_material, label_top=3) 19 | plt.xscale("log") # modify axis from default settings 20 | plt.savefig("gamma_spec.png") 21 | -------------------------------------------------------------------------------- /examples/example_get_particle_data.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import sample_initial_particles 3 | 4 | # initialises a new source object 5 | my_source = openmc.Source() 6 | 7 | # sets the location of the source to x=0 y=0 z=0 8 | my_source.space = openmc.stats.Point((0, 0, 0)) 9 | 10 | # sets the direction to isotropic 11 | my_source.angle = openmc.stats.Isotropic() 12 | 13 | # sets the energy distribution to 100% 14MeV neutrons 14 | my_source.energy = openmc.stats.Discrete([14e6], [1]) 15 | 16 | # gets the particle corrdiantes, energy and direction 17 | particles = sample_initial_particles(my_source) 18 | 19 | print(particles) 20 | 21 | for particle in particles: 22 | print(particle.E) 23 | -------------------------------------------------------------------------------- /examples/example_plot_plasma_source_position.py: -------------------------------------------------------------------------------- 1 | from openmc_plasma_source import tokamak_source 2 | from openmc_source_plotter import plot_source_position 3 | import openmc 4 | 5 | my_sources = tokamak_source( 6 | elongation=1.557, 7 | ion_density_centre=1.09e20, 8 | ion_density_peaking_factor=1, 9 | ion_density_pedestal=1.09e20, 10 | ion_density_separatrix=3e19, 11 | ion_temperature_centre=45.9, 12 | ion_temperature_peaking_factor=8.06, 13 | ion_temperature_pedestal=6.09, 14 | ion_temperature_separatrix=0.1, 15 | major_radius=9.06, 16 | minor_radius=2.92258, 17 | pedestal_radius=0.8 * 2.92258, 18 | mode="H", 19 | shafranov_factor=0.44789, 20 | triangularity=0.270, 21 | ion_temperature_beta=6, 22 | angles=(0, 3.14), # makes a sector of 0 radians to 3.14 radians 23 | sample_size=100, # reduces the number of samples from a default of 1000 to reduce plot time 24 | ) 25 | 26 | settings = openmc.Settings() 27 | settings.Source = my_sources 28 | 29 | 30 | # plots the particle energy distribution 31 | plot = plot_source_position(settings) 32 | 33 | plot.show() 34 | -------------------------------------------------------------------------------- /examples/example_plot_source_direction.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import plot_source_direction 3 | 4 | # initializes a new source object 5 | my_source = openmc.IndependentSource() 6 | 7 | # sets the direction to isotropic 8 | my_source.angle = openmc.stats.Isotropic() 9 | 10 | # plots the particle energy distribution 11 | plot = plot_source_direction(this=my_source, n_samples=200) 12 | 13 | plot.show() 14 | -------------------------------------------------------------------------------- /examples/example_plot_source_energy.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import plot_source_energy 3 | 4 | # initialise a new source object 5 | my_source = openmc.IndependentSource() 6 | 7 | # sets the energy distribution to a muir distribution neutrons 8 | my_source.energy = openmc.stats.muir(e0=14080000.0, m_rat=5.0, kt=20000.0) 9 | 10 | # plots the particle energy distribution 11 | plot = plot_source_energy(this=my_source, n_samples=10000) 12 | 13 | plot.show() 14 | -------------------------------------------------------------------------------- /examples/example_plot_source_position.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import plot_source_position 3 | 4 | # initialises a new source object 5 | my_source = openmc.Source() 6 | 7 | # the distribution of radius is just a single value 8 | radius = openmc.stats.Discrete([10], [1]) 9 | 10 | # the distribution of source z values is just a single value 11 | z_values = openmc.stats.Discrete([0], [1]) 12 | 13 | # the distribution of source azimuthal angles 14 | # values is a uniform distribution between 0 and 2 Pi 15 | angle = openmc.stats.Uniform(a=0.0, b=2 * 3.14159265359) 16 | 17 | # this makes the ring source using the three distributions and a radius 18 | my_source.space = openmc.stats.CylindricalIndependent( 19 | r=radius, phi=angle, z=z_values, origin=(0.0, 0.0, 0.0) 20 | ) 21 | 22 | # plots the particle energy distribution 23 | plot = plot_source_position(my_source) 24 | 25 | plot.show() 26 | -------------------------------------------------------------------------------- /examples/example_plot_two_source_energies.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import plot_source_energy 3 | 4 | # initialises a new source object 5 | my_dt_source = openmc.Source() 6 | 7 | # sets the energy distribution to a muir distribution DT neutrons 8 | my_dt_source.energy = openmc.stats.muir(e0=14080000.0, m_rat=5.0, kt=20000.0) 9 | 10 | # initialises a new source object 11 | my_dd_source = openmc.Source() 12 | # sets the energy distribution to a muir distribution DD neutrons 13 | my_dd_source.energy = openmc.stats.muir(e0=2080000.0, m_rat=2.0, kt=20000.0) 14 | 15 | # plots the particle energy distribution 16 | figure1 = plot_source_energy(this=my_dd_source, n_samples=10000) 17 | figure2 = plot_source_energy(this=my_dt_source, figure=figure1, n_samples=10000) 18 | 19 | figure2.show() 20 | -------------------------------------------------------------------------------- /examples/gamma_spec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fusion-energy/openmc_source_plotter/b755b12bc7753c9bf8de0fa72593dd279bcc1e83/examples/gamma_spec.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 65.4.0", "setuptools_scm[toml]>=7.0.5"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "openmc_source_plotter" 7 | authors = [ 8 | { name="Jonathan Shimwell", email="mail@jshimwell.com" }, 9 | ] 10 | license = {file = "LICENSE.txt"} 11 | description = "A Python package for extracting and plotting the locations, directions, energy distributions of OpenMC source particles." 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | keywords = ["energy", "plot", "source", "particle", "coordinates", "direction", "openmc", "gamma"] 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | ] 20 | dependencies = [ 21 | "h5py", 22 | "plotly", 23 | "numpy", 24 | ] 25 | dynamic = ["version"] 26 | 27 | [tool.setuptools_scm] 28 | write_to = "src/openmc_source_plotter/_version.py" 29 | 30 | [project.optional-dependencies] 31 | tests = [ 32 | "pytest" 33 | ] 34 | gammas = [ 35 | "lineid_plot" # appears to not be maintained hence not in main dependencies 36 | ] 37 | 38 | [project.scripts] 39 | openmc_source_plotter = "openmc_source_plotter.launch:main" 40 | 41 | [project.urls] 42 | "Homepage" = "https://github.com/fusion-energy/openmc_source_plotter" 43 | "Bug Tracker" = "https://github.com/fusion-energy/openmc_source_plotter/issues" 44 | 45 | [tool.setuptools] 46 | package-dir = {"" = "src"} 47 | -------------------------------------------------------------------------------- /src/openmc_source_plotter/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | # this works for python 3.7 and lower 3 | from importlib.metadata import version, PackageNotFoundError 4 | except (ModuleNotFoundError, ImportError): 5 | # this works for python 3.8 and higher 6 | from importlib_metadata import version, PackageNotFoundError 7 | try: 8 | __version__ = version("openmc_source_plotter") 9 | except PackageNotFoundError: 10 | from setuptools_scm import get_version 11 | 12 | __version__ = get_version(root="..", relative_to=__file__) 13 | 14 | __all__ = ["__version__"] 15 | 16 | from .core import * 17 | from .material import * 18 | -------------------------------------------------------------------------------- /src/openmc_source_plotter/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Provides functions for plotting source information""" 4 | 5 | import typing 6 | from tempfile import TemporaryDirectory 7 | import numpy as np 8 | import openmc 9 | import openmc.lib 10 | import plotly.graph_objects 11 | 12 | import pkg_resources 13 | 14 | system_openmc_version = pkg_resources.parse_version(openmc.__version__) 15 | min_openmc_version = pkg_resources.parse_version("0.14.0") 16 | if system_openmc_version < min_openmc_version: 17 | msg = ( 18 | "openmc_source_plotter requires openmc version 0.14.0 or above. " 19 | f"You have openmc version {system_openmc_version} installed. " 20 | "Please update the version of openmc installed" 21 | ) 22 | raise ImportError(msg) 23 | 24 | 25 | def sample_initial_particles(this, n_samples: int = 1000, prn_seed: int = None): 26 | """smaples particles from the source. 27 | 28 | Args: 29 | this: The openmc source, settings or model containing the source to plot 30 | n_samples: The number of source samples to obtain. 31 | prn_seed: The pseudorandom number seed. 32 | """ 33 | with TemporaryDirectory() as tmpdir: 34 | if isinstance(this, openmc.Model): 35 | model = this 36 | 37 | else: 38 | model = openmc.Model() 39 | 40 | materials = openmc.Materials() 41 | model.materials = materials 42 | 43 | sph = openmc.Sphere(r=99999999999, boundary_type="vacuum") 44 | cell = openmc.Cell(region=-sph) 45 | geometry = openmc.Geometry([cell]) 46 | model.geometry = geometry 47 | 48 | if isinstance(this, openmc.Settings): 49 | model.settings = this 50 | 51 | else: # source object 52 | settings = openmc.Settings() 53 | settings.particles = 1 54 | settings.batches = 1 55 | settings.source = this 56 | model.settings = settings 57 | 58 | model.export_to_model_xml() 59 | 60 | openmc.lib.init(output=False) 61 | particles = openmc.lib.sample_external_source( 62 | n_samples=n_samples, prn_seed=prn_seed 63 | ) 64 | openmc.lib.finalize() 65 | 66 | return particles 67 | 68 | 69 | def plot_source_energy( 70 | this, 71 | figure: plotly.graph_objects.Figure = None, 72 | n_samples: int = 2000, 73 | prn_seed: int = 1, 74 | energy_bins: typing.Union[str, np.array] = "auto", 75 | name: typing.Optional[str] = None, 76 | yaxis_type: str = "linear", 77 | xaxis_type: str = "linear", 78 | xaxis_units: str = "MeV", 79 | ): 80 | """makes a plot of the initial creation positions of an OpenMC source 81 | 82 | Args: 83 | this: The openmc source, settings or model containing the source to plot 84 | figure: Optional base plotly figure to use for the plot. Passing in 85 | a pre made figure allows one to build up plots with from 86 | multiple sources. Defaults to None which makes a new figure for 87 | the plot. 88 | source: The openmc.Source object or list of openmc.Source objects 89 | to plot. 90 | n_samples: The number of source samples to obtain. 91 | prn_seed: The pseudorandom number seed 92 | energy_bins: Defaults to 'auto' which uses inbuilt auto binning in 93 | Numpy bins can also be manually set by passing in a numpy array 94 | of bin edges. 95 | name: the legend name to use 96 | yaxis_type: The type (scale) to use for the Y axis. Options are 'log' 97 | or 'linear. 98 | xaxis_type: The type (scale) to use for the Y axis. Options are 'log' 99 | or 'linear. 100 | xaxis_units: The units to use for the x axis. Options are 'eV' or 'MeV'. 101 | """ 102 | 103 | if xaxis_units not in ["eV", "MeV"]: 104 | raise ValueError(f"xaxis_units must be either 'eV' or 'MeV' not {xaxis_units}") 105 | 106 | if figure is None: 107 | figure = plotly.graph_objects.Figure() 108 | figure.update_layout( 109 | title="Particle energy", 110 | xaxis={"title": f"Energy [{xaxis_units}]", "type": xaxis_type}, 111 | yaxis={"title": "Probability", "type": yaxis_type}, 112 | showlegend=True, 113 | ) 114 | 115 | data = sample_initial_particles(this, n_samples, prn_seed) 116 | 117 | e_values = [particle.E for particle in data] 118 | 119 | # Calculate pdf for source energies 120 | probability, bin_edges = np.histogram(e_values, bins=energy_bins, density=True) 121 | 122 | # scaling by strength 123 | if isinstance(this, openmc.SourceBase): 124 | probability = probability * this.strength 125 | energy = bin_edges[:-1] 126 | if xaxis_units == "MeV": 127 | energy = energy / 1e6 128 | # Plot source energy histogram 129 | figure.add_trace( 130 | plotly.graph_objects.Scatter( 131 | x=energy, 132 | y=probability * np.diff(bin_edges), 133 | line={"shape": "hv"}, 134 | hoverinfo="text", 135 | name=name, 136 | ) 137 | ) 138 | 139 | return figure 140 | 141 | 142 | def plot_source_position( 143 | this: typing.Union[openmc.SourceBase, openmc.Settings, openmc.Model], 144 | figure=None, 145 | n_samples: int = 2000, 146 | prn_seed: int = 1, 147 | ): 148 | """makes a plot of the initial creation positions of an OpenMC source(s) 149 | 150 | Args: 151 | this: The openmc source, settings or model containing the source to plot 152 | figure: Optional base plotly figure to use for the plot. Passing in 153 | a pre made figure allows one to build up plots with from 154 | multiple sources. Defaults to None which makes a new figure for 155 | the plot. 156 | source: The openmc.Source object or list of openmc.Source objects 157 | to plot. 158 | n_samples: The number of source samples to obtain. 159 | prn_seed: The pseudorandom number seed 160 | """ 161 | 162 | if figure is None: 163 | figure = plotly.graph_objects.Figure() 164 | figure.update_layout( 165 | title="Particle creation position", 166 | showlegend=True, 167 | ) 168 | 169 | data = sample_initial_particles(this, n_samples, prn_seed) 170 | 171 | text = ["Energy = " + str(particle.E) + " eV" for particle in data] 172 | 173 | figure.add_trace( 174 | plotly.graph_objects.Scatter3d( 175 | x=[particle.r[0] for particle in data], 176 | y=[particle.r[1] for particle in data], 177 | z=[particle.r[2] for particle in data], 178 | hovertext=text, 179 | text=text, 180 | mode="markers", 181 | marker={ 182 | "size": 2, 183 | "color": [particle.E for particle in data], 184 | }, 185 | ) 186 | ) 187 | title = "Particle production coordinates coloured by energy" 188 | figure.update_layout(title=title) 189 | 190 | return figure 191 | 192 | 193 | def plot_source_direction( 194 | this: typing.Union[openmc.SourceBase, openmc.Settings, openmc.Model], 195 | figure=None, 196 | n_samples: int = 2000, 197 | prn_seed: int = 1, 198 | ): 199 | """makes a plot of the initial creation positions of an OpenMC source(s) 200 | 201 | Args: 202 | this: The openmc source, settings or model containing the source to plot 203 | figure: Optional base plotly figure to use for the plot. Passing in 204 | a pre made figure allows one to build up plots with from 205 | multiple sources. Defaults to None which makes a new figure for 206 | the plot. 207 | source: The openmc.Source object or list of openmc.Source objects 208 | to plot. 209 | n_samples: The number of source samples to obtain. 210 | prn_seed: The pseudorandom number seed 211 | """ 212 | 213 | figure = plotly.graph_objects.Figure() 214 | figure.update_layout(title="Particle initial directions") 215 | 216 | data = sample_initial_particles(this, n_samples, prn_seed) 217 | 218 | biggest_coord = max( 219 | max([particle.r[0] for particle in data]), 220 | max([particle.r[1] for particle in data]), 221 | max([particle.r[2] for particle in data]), 222 | ) 223 | smallest_coord = min( 224 | min([particle.r[0] for particle in data]), 225 | min([particle.r[1] for particle in data]), 226 | min([particle.r[2] for particle in data]), 227 | ) 228 | 229 | figure.add_trace( 230 | { 231 | "type": "scatter3d", 232 | "marker": {"color": "rgba(255,255,255,0)"}, 233 | "x": [biggest_coord, smallest_coord], 234 | "y": [biggest_coord, smallest_coord], 235 | "z": [biggest_coord, smallest_coord], 236 | } 237 | ) 238 | figure.add_trace( 239 | { 240 | "type": "cone", 241 | "cauto": False, 242 | "x": [particle.r[0] for particle in data], 243 | "y": [particle.r[1] for particle in data], 244 | "z": [particle.r[2] for particle in data], 245 | "u": [particle.u[0] for particle in data], 246 | "v": [particle.u[1] for particle in data], 247 | "w": [particle.u[2] for particle in data], 248 | "cmin": 0, 249 | "cmax": 1, 250 | "anchor": "tail", 251 | "colorscale": "Viridis", 252 | "hoverinfo": "u+v+w+norm", 253 | "sizemode": "absolute", 254 | "sizeref": 30, 255 | "showscale": False, 256 | } 257 | ) 258 | 259 | return figure 260 | -------------------------------------------------------------------------------- /src/openmc_source_plotter/material.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | def _get_energy_prob(energy_dis): 6 | """gets the energy and probability for different openmc.stats including the 7 | openmc.stats.Mixutre which itself is made from openmc.stats""" 8 | 9 | if isinstance(energy_dis, openmc.stats.Mixture): 10 | stats = energy_dis.distribution 11 | multipliers = energy_dis.probability 12 | else: 13 | stats = [energy_dis] 14 | multipliers = [1.0] 15 | 16 | probs = [] 17 | en = [] 18 | 19 | for stat, multiplier in zip(stats, multipliers): 20 | 21 | for p in stat.p: 22 | probs.append(0) 23 | probs.append(p * multiplier) 24 | probs.append(0) 25 | for x in stat.x: 26 | en.append(x) 27 | en.append(x) 28 | en.append(x) 29 | 30 | return en, probs 31 | 32 | 33 | def plot_gamma_emission( 34 | material, 35 | label_top: int = None, 36 | ): 37 | """makes a plot of the gamma energy spectra for a material. The 38 | material should contain unstable nuclide which undergo gamma emission 39 | to produce a plot. Such materials can be made manually or obtained via 40 | openmc deplete simulations. 41 | 42 | Args: 43 | label_top: Optionally label the n highest activity energies with 44 | the nuclide that generates them. 45 | 46 | Returns: 47 | Matplotlib pyplot object. 48 | """ 49 | 50 | plt.clf() 51 | if label_top: 52 | energies_to_label = [] 53 | labels = [] 54 | possible_energies_to_label = [] 55 | import lineid_plot 56 | 57 | atoms = material.get_nuclide_atoms() 58 | for nuc, num_atoms in atoms.items(): 59 | dists = [] 60 | probs = [] 61 | source_per_atom = openmc.data.decay_photon_energy(nuc) 62 | if source_per_atom is not None: 63 | dists.append(source_per_atom) 64 | probs.append(num_atoms) 65 | combo = openmc.data.combine_distributions(dists, probs) 66 | for p, x in zip(combo.p, combo.x): 67 | possible_energies_to_label.append((nuc, p, x)) 68 | 69 | possible_energies_to_label = sorted( 70 | possible_energies_to_label, key=lambda x: x[1], reverse=True 71 | )[:label_top] 72 | for entry in possible_energies_to_label: 73 | energies_to_label.append(entry[2]) 74 | labels.append(entry[0]) 75 | 76 | probs = [] 77 | en = [] 78 | energy_dis = material.get_decay_photon_energy(clip_tolerance=0.0) 79 | 80 | en, probs = _get_energy_prob(energy_dis) 81 | 82 | lineid_plot.plot_line_ids( 83 | en, 84 | # material.decay_photon_energy.x, 85 | probs, 86 | # material.decay_photon_energy.p, 87 | energies_to_label, 88 | labels, 89 | ) 90 | 91 | else: 92 | energy_dis = material.get_decay_photon_energy(clip_tolerance=0.0) 93 | 94 | en, probs = _get_energy_prob(energy_dis) 95 | 96 | # plt.scatter(energy_dis.x, energy_dis.p) 97 | plt.plot(en, probs) 98 | # print(energy_dis.p) 99 | # print(energy_dis.x) 100 | plt.xlabel("Energy [eV]") 101 | plt.ylabel("Activity [Bq/s]") 102 | return plt 103 | -------------------------------------------------------------------------------- /tests/test_core_with_model.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import ( 3 | sample_initial_particles, 4 | plot_source_energy, 5 | plot_source_position, 6 | plot_source_direction, 7 | ) 8 | import numpy as np 9 | import plotly.graph_objects as go 10 | import pytest 11 | 12 | 13 | @pytest.fixture 14 | def test_model(): 15 | # initialises a new source object 16 | my_source = openmc.IndependentSource() 17 | 18 | # sets the location of the source to x=0 y=0 z=0 19 | my_source.space = openmc.stats.Point((1.0, 2.0, 3.0)) 20 | 21 | # sets the direction to isotropic 22 | my_source.angle = openmc.stats.Isotropic() 23 | 24 | # sets the energy distribution to 100% 14MeV neutrons 25 | my_source.energy = openmc.stats.Discrete([15e6], [1]) 26 | 27 | my_source.particle = "photon" 28 | 29 | settings = openmc.Settings() 30 | settings.particles = 1 31 | settings.batches = 1 32 | settings.source = my_source 33 | 34 | materials = openmc.Materials() 35 | 36 | sph = openmc.Sphere(r=9999999999, boundary_type="vacuum") 37 | cell = openmc.Cell(region=-sph) 38 | geometry = openmc.Geometry([cell]) 39 | 40 | model = openmc.Model(geometry, materials, settings) 41 | 42 | return model 43 | 44 | 45 | def test_sample_initial_particles(test_model): 46 | particles = sample_initial_particles(this=test_model, n_samples=43) 47 | for particle in particles: 48 | assert particle.E == 15e6 49 | assert str(particle.particle) == "photon" 50 | assert particle.r == (1.0, 2.0, 3.0) 51 | assert len(particles) == 43 52 | 53 | 54 | def test_energy_plot_with_bins(test_model): 55 | plot = plot_source_energy( 56 | this=test_model, 57 | n_samples=10, 58 | energy_bins=np.linspace(0, 20e6, 100), 59 | ) 60 | assert isinstance(plot, go.Figure) 61 | 62 | 63 | def test_energy_plot(test_model): 64 | plot = plot_source_energy(this=test_model, n_samples=10) 65 | assert isinstance(plot, go.Figure) 66 | assert len(plot.data[0]["x"]) == 1 67 | 68 | 69 | def test_position_plot(test_model): 70 | plot = plot_source_position(this=test_model, n_samples=10) 71 | assert isinstance(plot, go.Figure) 72 | 73 | 74 | def test_direction_plot(test_model): 75 | plot = plot_source_direction(this=test_model, n_samples=10) 76 | assert isinstance(plot, go.Figure) 77 | 78 | 79 | def test_energy_plot_with_figure(test_model): 80 | base_figure = go.Figure() 81 | plot = plot_source_energy(this=test_model, figure=base_figure, n_samples=10) 82 | assert isinstance(plot, go.Figure) 83 | assert len(plot.data[0]["x"]) == 1 84 | 85 | 86 | def test_position_plot_with_figure(test_model): 87 | base_figure = go.Figure() 88 | plot = plot_source_position(this=test_model, figure=base_figure, n_samples=10) 89 | assert isinstance(plot, go.Figure) 90 | 91 | 92 | def test_direction_plot_with_figure(test_model): 93 | base_figure = go.Figure() 94 | plot = plot_source_direction(this=test_model, figure=base_figure, n_samples=10) 95 | assert isinstance(plot, go.Figure) 96 | -------------------------------------------------------------------------------- /tests/test_core_with_settings.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import ( 3 | sample_initial_particles, 4 | plot_source_energy, 5 | plot_source_position, 6 | plot_source_direction, 7 | ) 8 | import numpy as np 9 | import plotly.graph_objects as go 10 | import pytest 11 | 12 | 13 | @pytest.fixture 14 | def test_settings(): 15 | # initialises a new source object 16 | my_source = openmc.IndependentSource() 17 | 18 | # sets the location of the source to x=0 y=0 z=0 19 | my_source.space = openmc.stats.Point((1.0, 2.0, 3.0)) 20 | 21 | # sets the direction to isotropic 22 | my_source.angle = openmc.stats.Isotropic() 23 | 24 | # sets the energy distribution to 100% 14MeV neutrons 25 | my_source.energy = openmc.stats.Discrete([15e6], [1]) 26 | 27 | my_source.particle = "photon" 28 | 29 | settings = openmc.Settings() 30 | settings.particles = 1 31 | settings.batches = 1 32 | settings.source = my_source 33 | 34 | return settings 35 | 36 | 37 | def test_sample_initial_particles(test_settings): 38 | particles = sample_initial_particles(this=test_settings, n_samples=43) 39 | for particle in particles: 40 | assert particle.E == 15e6 41 | assert str(particle.particle) == "photon" 42 | assert particle.r == (1.0, 2.0, 3.0) 43 | assert len(particles) == 43 44 | 45 | 46 | def test_energy_plot_with_bins(test_settings): 47 | plot = plot_source_energy( 48 | this=test_settings, 49 | n_samples=10, 50 | energy_bins=np.linspace(0, 20e6, 100), 51 | ) 52 | assert isinstance(plot, go.Figure) 53 | 54 | 55 | def test_energy_plot(test_settings): 56 | plot = plot_source_energy(this=test_settings, n_samples=10) 57 | assert isinstance(plot, go.Figure) 58 | assert len(plot.data[0]["x"]) == 1 59 | 60 | 61 | def test_position_plot(test_settings): 62 | plot = plot_source_position(this=test_settings, n_samples=10) 63 | assert isinstance(plot, go.Figure) 64 | 65 | 66 | def test_direction_plot(test_settings): 67 | plot = plot_source_direction(this=test_settings, n_samples=10) 68 | assert isinstance(plot, go.Figure) 69 | 70 | 71 | def test_energy_plot_with_figure(test_settings): 72 | base_figure = go.Figure() 73 | plot = plot_source_energy(this=test_settings, figure=base_figure, n_samples=10) 74 | assert isinstance(plot, go.Figure) 75 | assert len(plot.data[0]["x"]) == 1 76 | 77 | 78 | def test_position_plot_with_figure(test_settings): 79 | base_figure = go.Figure() 80 | plot = plot_source_position(this=test_settings, figure=base_figure, n_samples=10) 81 | assert isinstance(plot, go.Figure) 82 | 83 | 84 | def test_direction_plot_with_figure(test_settings): 85 | base_figure = go.Figure() 86 | plot = plot_source_direction(this=test_settings, figure=base_figure, n_samples=10) 87 | assert isinstance(plot, go.Figure) 88 | -------------------------------------------------------------------------------- /tests/test_core_with_source.py: -------------------------------------------------------------------------------- 1 | import openmc 2 | from openmc_source_plotter import ( 3 | sample_initial_particles, 4 | plot_source_energy, 5 | plot_source_position, 6 | plot_source_direction, 7 | ) 8 | import numpy as np 9 | import plotly.graph_objects as go 10 | import pytest 11 | 12 | 13 | @pytest.fixture 14 | def test_source(): 15 | # initialises a new source object 16 | my_source = openmc.IndependentSource() 17 | 18 | # sets the location of the source to x=0 y=0 z=0 19 | my_source.space = openmc.stats.Point((4.0, 5.0, 6.0)) 20 | 21 | # sets the direction to isotropic 22 | my_source.angle = openmc.stats.Isotropic() 23 | 24 | # sets the energy distribution to 100% 14MeV neutrons 25 | my_source.energy = openmc.stats.Discrete([14e6], [1]) 26 | 27 | my_source.particle = "neutron" 28 | 29 | my_source = my_source 30 | return my_source 31 | 32 | 33 | def test_sample_initial_particles(test_source): 34 | particles = sample_initial_particles(this=test_source, n_samples=42) 35 | for particle in particles: 36 | assert particle.E == 14e6 37 | assert str(particle.particle) == "neutron" 38 | assert particle.r == (4.0, 5.0, 6.0) 39 | assert len(particles) == 42 40 | 41 | 42 | def test_energy_plot_with_bins(test_source): 43 | plot = plot_source_energy( 44 | this=test_source, 45 | n_samples=10, 46 | energy_bins=np.linspace(0, 20e6, 100), 47 | ) 48 | assert isinstance(plot, go.Figure) 49 | 50 | 51 | def test_energy_plot(test_source): 52 | plot = plot_source_energy(this=test_source, n_samples=10) 53 | assert isinstance(plot, go.Figure) 54 | assert len(plot.data[0]["x"]) == 1 55 | 56 | 57 | def test_energy_plot_axis(test_source): 58 | plot = plot_source_energy( 59 | this=test_source, 60 | n_samples=10, 61 | xaxis_type="log", 62 | yaxis_type="linear", 63 | xaxis_units="eV", 64 | ) 65 | plot = plot_source_energy( 66 | this=test_source, 67 | n_samples=10, 68 | xaxis_type="linear", 69 | yaxis_type="log", 70 | xaxis_units="MeV", 71 | ) 72 | assert isinstance(plot, go.Figure) 73 | assert len(plot.data[0]["x"]) == 1 74 | 75 | 76 | def test_position_plot(test_source): 77 | plot = plot_source_position(this=test_source, n_samples=10) 78 | assert isinstance(plot, go.Figure) 79 | 80 | 81 | def test_direction_plot(test_source): 82 | plot = plot_source_direction(this=test_source, n_samples=10) 83 | assert isinstance(plot, go.Figure) 84 | 85 | 86 | def test_energy_plot_with_figure(test_source): 87 | base_figure = go.Figure() 88 | plot = plot_source_energy(this=test_source, figure=base_figure, n_samples=10) 89 | assert isinstance(plot, go.Figure) 90 | assert len(plot.data[0]["x"]) == 1 91 | 92 | 93 | def test_position_plot_with_figure(test_source): 94 | base_figure = go.Figure() 95 | plot = plot_source_position(this=test_source, figure=base_figure, n_samples=10) 96 | assert isinstance(plot, go.Figure) 97 | 98 | 99 | def test_direction_plot_with_figure(test_source): 100 | base_figure = go.Figure() 101 | plot = plot_source_direction(this=test_source, figure=base_figure, n_samples=10) 102 | assert isinstance(plot, go.Figure) 103 | -------------------------------------------------------------------------------- /tests/todo.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fusion-energy/openmc_source_plotter/b755b12bc7753c9bf8de0fa72593dd279bcc1e83/tests/todo.md --------------------------------------------------------------------------------