OpenMC/openmc/tracks.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

348 lines
9.9 KiB
Python
Raw Permalink Normal View History

from collections import namedtuple
from collections.abc import Sequence
import h5py
from .checkvalue import check_filetype_version
from .particle_type import ParticleType
from .source import SourceParticle
2022-08-15 13:39:47 +01:00
from pathlib import Path
2022-05-17 07:33:55 -04:00
ParticleTrack = namedtuple('ParticleTrack', ['particle', 'states'])
ParticleTrack.__doc__ = """\
Particle track information
Parameters
----------
particle : openmc.ParticleType
Type of the particle
states : numpy.ndarray
Structured array containing each state of the particle. The structured array
contains the following fields: ``r`` (position; each direction in [cm]),
``u`` (direction), ``E`` (energy in [eV]), ``time`` (time in [s]), ``wgt``
(weight), ``cell_id`` (cell ID) , ``cell_instance`` (cell instance), and
``material_id`` (material ID).
"""
def _particle_track_repr(self):
return f"<ParticleTrack: {str(self.particle)}, {len(self.states)} states>"
ParticleTrack.__repr__ = _particle_track_repr
_VERSION_TRACK = 3
2022-05-17 07:33:55 -04:00
def _identifier(dset_name):
"""Return (batch, gen, particle) tuple given dataset name"""
_, batch, gen, particle = dset_name.split('_')
return (int(batch), int(gen), int(particle))
class Track(Sequence):
2022-05-17 07:33:55 -04:00
"""Tracks resulting from a single source particle
This class stores information for all tracks resulting from a primary source
particle and any secondary particles that it created. The track for each
primary/secondary particle is stored in the :attr:`particle_tracks`
attribute.
2022-08-16 16:19:55 -05:00
.. versionadded:: 0.13.1
Parameters
----------
2022-05-17 07:33:55 -04:00
dset : h5py.Dataset
Dataset to read track data from
Attributes
----------
2022-05-17 07:33:55 -04:00
identifier : tuple
Tuple of (batch, generation, particle number)
particle_tracks : list
2022-05-17 07:33:55 -04:00
List of tuples containing (particle type, array of track states)
sources : list
List of :class:`SourceParticle` representing each primary/secondary
particle
"""
2022-05-17 07:33:55 -04:00
def __init__(self, dset):
tracks = dset[()]
offsets = dset.attrs['offsets']
particles = dset.attrs['particles']
self.identifier = _identifier(dset.name)
# Construct list of track histories
tracks_list = []
for particle, start, end in zip(particles, offsets[:-1], offsets[1:]):
ptype = ParticleType(particle)
2022-05-17 07:33:55 -04:00
tracks_list.append(ParticleTrack(ptype, tracks[start:end]))
self.particle_tracks = tracks_list
def __repr__(self):
return f'<Track {self.identifier}: {len(self.particle_tracks)} particles>'
def __getitem__(self, index):
return self.particle_tracks[index]
def __len__(self):
return len(self.particle_tracks)
def filter(self, particle=None, state_filter=None):
"""Filter particle tracks by given criteria
Parameters
----------
particle : str or int or openmc.ParticleType
Matching particle type (name, PDG number, or type)
state_filter : function
Function that takes a state (structured datatype) and returns a bool
depending on some criteria.
Returns
-------
Track
New instance with only matching :class:`openmc.ParticleTrack` objects
Examples
--------
Get all particle tracks for photons:
>>> track.filter(particle='photon')
Get all particle tracks that entered cell with ID=15:
>>> track.filter(state_filter=lambda s: s['cell_id'] == 15)
Get all particle tracks in entered material with ID=2:
>>> track.filter(state_filter=lambda s: s['material_id'] == 2)
See Also
--------
openmc.ParticleTrack
"""
matching = []
for t in self:
# Check for matching particle
if particle is not None:
if t.particle != ParticleType(particle):
continue
# Apply arbitrary state filter
match = True
if state_filter is not None:
for state in t.states:
if state_filter(state):
break
else:
match = False
if match:
matching.append(t)
# Return new Track instance with only matching particle tracks
track = type(self).__new__(type(self))
track.identifier = self.identifier
track.particle_tracks = matching
return track
2022-05-17 07:50:36 -04:00
def plot(self, axes=None):
"""Produce a 3D plot of particle tracks
Parameters
----------
axes : matplotlib.axes.Axes, optional
2022-05-17 07:50:36 -04:00
Axes for plot
2022-05-23 12:28:28 -05:00
Returns
-------
axes : matplotlib.axes.Axes
Axes for plot
2022-05-17 07:50:36 -04:00
"""
import matplotlib.pyplot as plt
2022-05-17 07:50:36 -04:00
# Setup axes is one wasn't passed
if axes is None:
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.set_xlabel('x [cm]')
ax.set_ylabel('y [cm]')
ax.set_zlabel('z [cm]')
else:
ax = axes
# Plot each particle track
for _, states in self:
r = states['r']
ax.plot3D(r['x'], r['y'], r['z'])
2022-05-17 07:50:36 -04:00
2022-05-23 12:28:28 -05:00
return ax
@property
def sources(self):
sources = []
for particle_track in self:
particle_type = particle_track.particle
state = particle_track.states[0]
sources.append(
SourceParticle(
r=state['r'], u=state['u'], E=state['E'],
time=state['time'], wgt=state['wgt'],
particle=particle_type
)
)
return sources
2022-05-17 07:33:55 -04:00
2022-05-25 16:58:20 -05:00
class Tracks(list):
2022-05-17 07:33:55 -04:00
"""Collection of particle tracks
This class behaves like a list and can be indexed using the normal subscript
notation. Each element in the list is a :class:`openmc.Track` object.
2022-08-16 16:19:55 -05:00
.. versionadded:: 0.13.1
2022-05-17 07:33:55 -04:00
Parameters
----------
filepath : str or pathlib.Path
Path of file to load
"""
2022-05-25 16:58:20 -05:00
def __init__(self, filepath='tracks.h5'):
2022-05-17 07:33:55 -04:00
# Read data from track file
with h5py.File(filepath, 'r') as fh:
# Check filetype and version
check_filetype_version(fh, 'track', _VERSION_TRACK)
2022-05-17 07:33:55 -04:00
for dset_name in sorted(fh, key=_identifier):
dset = fh[dset_name]
self.append(Track(dset))
2022-05-17 07:50:36 -04:00
def filter(self, particle=None, state_filter=None):
"""Filter tracks by given criteria
Parameters
----------
particle : {'neutron', 'photon', 'electron', 'positron'}
Matching particle type
state_filter : function
Function that takes a state (structured datatype) and returns a bool
depending on some criteria.
Returns
-------
Tracks
List of :class:`openmc.Track` objects
See Also
--------
openmc.Track.filter
"""
# Create a new Tracks instance but avoid call to __init__
matching = type(self).__new__(type(self))
# Append matching Track objects
for track in self:
if track.filter(particle, state_filter):
matching.append(track)
return matching
2022-05-17 07:50:36 -04:00
def plot(self):
2022-05-23 12:28:28 -05:00
"""Produce a 3D plot of particle tracks
Returns
-------
matplotlib.axes.Axes
Axes for plot
"""
2022-05-17 07:50:36 -04:00
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection='3d')
2022-05-23 12:28:28 -05:00
ax.set_xlabel('x [cm]')
ax.set_ylabel('y [cm]')
ax.set_zlabel('z [cm]')
2022-05-17 07:50:36 -04:00
for track in self:
track.plot(ax)
2022-05-23 12:28:28 -05:00
return ax
def write_to_vtk(self, filename=Path('tracks.vtp')):
2022-08-15 13:39:47 +01:00
"""Creates a VTP file of the tracks
2022-08-13 12:32:14 +01:00
Parameters
----------
2022-08-15 13:39:47 +01:00
filename : path-like
2022-08-15 13:40:56 +01:00
Name of the VTP file to write.
2022-08-13 12:32:14 +01:00
Returns
-------
2022-08-15 13:39:47 +01:00
vtk.vtkPolyData
the VTK vtkPolyData object produced
2022-08-13 12:32:14 +01:00
"""
import vtk
# Initialize data arrays and offset.
points = vtk.vtkPoints()
cells = vtk.vtkCellArray()
2022-08-16 16:19:55 -05:00
2022-08-15 13:22:44 +01:00
point_offset = 0
2022-08-13 12:32:14 +01:00
for particle in self:
2022-08-15 13:22:44 +01:00
for pt in particle.particle_tracks:
for state in pt.states:
points.InsertNextPoint(state['r'])
2022-08-13 12:32:14 +01:00
# Create VTK line and assign points to line.
n = pt.states.size
line = vtk.vtkPolyLine()
line.GetPointIds().SetNumberOfIds(n)
for i in range(n):
line.GetPointIds().SetId(i, point_offset + i)
point_offset += n
# Add line to cell array
cells.InsertNextCell(line)
2022-08-13 12:32:14 +01:00
data = vtk.vtkPolyData()
data.SetPoints(points)
data.SetLines(cells)
writer = vtk.vtkXMLPPolyDataWriter()
if vtk.vtkVersion.GetVTKMajorVersion() > 5:
writer.SetInputData(data)
else:
writer.SetInput(data)
2022-08-15 13:39:47 +01:00
writer.SetFileName(str(filename)) # SetFileName requires a string
2022-08-13 12:32:14 +01:00
writer.Write()
2022-08-15 13:39:47 +01:00
return data
2022-08-13 12:32:14 +01:00
@staticmethod
def combine(track_files, path='tracks.h5'):
"""Combine multiple track files into a single track file
Parameters
----------
track_files : list of path-like
Paths to track files to combine
path : path-like
Path of combined track file to create
"""
with h5py.File(path, 'w') as h5_out:
for i, fname in enumerate(track_files):
with h5py.File(fname, 'r') as h5_in:
# Copy file attributes for first file
if i == 0:
h5_out.attrs['filetype'] = h5_in.attrs['filetype']
h5_out.attrs['version'] = h5_in.attrs['version']
# Copy each 'track_*' dataset from input file
for dset in h5_in:
h5_in.copy(dset, h5_out)