openmc icon indicating copy to clipboard operation
openmc copied to clipboard

TimeFilter reducing flux results

Open tibetscarlett opened this issue 3 years ago • 3 comments

When I try to obtain cell tally flux results on a simple shell of Iron for a time-dependent neutron source, I notice that applying a time filter reduces the flux results. It doesn't matter how large a time bin I use, I always seem to get a reduction in flux results. At the moment I simply have:

time_filter = openmc.TimeFilter(values=[0, 1E+100])

This should easily cover the range of times in the simulation, but when I try:

results = openmc.StatePoint("statepoint.10.h5")
tally = results.get_tally(name='flux_tally', scores=['flux'])
print("flux : ", tally.mean)

I get lower results than without a time filter (~25 vs ~19). My aim is to benchmark a new method for obtaining shutdown dose rates by generating decay photons at certain times, and I noticed that applying a time filter returns 0 for photon flux (versus ~1E-12 for without a time filter). I tried to use OpenMC's prompt photon generator to see if the problem was with the modified code, but even then I got 0 with a time filter. Then I tried simply neutrons, as above.

EDIT: I suspect it may be to do with the 'tracklength' tally estimator, as when I set the estimator to 'analog' or 'collision', the results were unchanged when tallied with/without a time filter.

The full code I used is as follows:


import openmc
import numpy as np


# remove old xml files
!rm *.xml


iron=openmc.Material(name='iron')
iron.set_density('g/cm3', 7.3)
iron.add_nuclide('Fe56', 1.0, percent_type='wo')

mats=openmc.Materials([iron])


sphere_inner_surface=openmc.Sphere(x0=0, y0=0, z0=0, r=30.0)
sphere_outer_surface=openmc.Sphere(x0=0, y0=0, z0=0, r=39.0)
sphere_boundary_surface=openmc.Sphere(x0=0, y0=0, z0=0, r=40.0, boundary_type='vacuum')


inner_sphere_region=-sphere_inner_surface
inner_sphere_cell=openmc.Cell(region=inner_sphere_region)

sphere_shell_region=+sphere_inner_surface & -sphere_outer_surface #& ~inner_sphere_region
sphere_shell_cell=openmc.Cell(region=sphere_shell_region)
sphere_shell_cell.fill=iron

boundary_region=+sphere_outer_surface & -sphere_boundary_surface
boundary_cell=openmc.Cell(region=boundary_region)


universe = openmc.Universe(cells=[inner_sphere_cell, sphere_shell_cell, boundary_cell])

geom = openmc.Geometry(universe)


# creates a 14MeV point source
source = openmc.Source()
source.space = openmc.stats.Point((0, 0, 0))
source.angle = openmc.stats.Isotropic()
source.energy = openmc.stats.Discrete([14e6], [1])


time_shakes=[0.00000E+00,\
      4.22664E+14,\
      8.17023E+14,\
      1.18497E+15,\
      1.52828E+15,\
      1.84860E+15,\
      2.14747E+15,\
      2.42632E+15,\
      2.68650E+15,\
      2.92926E+15,\
      3.15576E+15]

time_seconds=[x*1e-8 for x in time_shakes]
print("time_seconds : ", time_seconds)

source.time=openmc.stats.Discrete(time_seconds, [0.000000000,\
      0.133934017,\
      0.124964856,\
      0.116596334,\
      0.108788226,\
      0.101503004,\
      0.094705652,\
      0.088363497,\
      0.082446058,\
      0.076924892,\
      0.071773463])


sett = openmc.Settings()
batches = 10
sett.batches = batches
sett.inactive=0
sett.max_lost_particles=1000
sett.particles = int(1e6)
sett.particle = "neutron"
sett.run_mode = 'fixed source'
sett.photon_transport = True  # This line is required to switch on photons tracking

sett.source = source




##############################################################################################


energy_bins = [0, 10000, 15000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 100000,\
               150000, 200000, 300000, 400000, 500000, 600000, 800000, 1000000, 2000000,\
               4000000, 6000000, 8000000, 10000000]


time_bins = [0.00000E+00, 1.0E+6,\
      4.32664E+6,\
      8.27023E+6,\
      1.28497E+7,\
      1.62828E+7,\
      1.94860E+7,\
      2.24747E+7,\
      2.52632E+7,\
      2.78650E+7,\
      3.02926E+7,\
      3.25576E+7]

time_bins_photon = [0.00000E+0, 1E+100]


energy_filter = openmc.EnergyFilter(energy_bins)

photon_particle_filter = openmc.ParticleFilter(["photon"])
#surface_filter = openmc.SurfaceFilter(sphere_outer_surface)
cell_filter = openmc.CellFilter(sphere_shell_cell)
             
time_filter = openmc.TimeFilter(values=time_bins_photon)


tallies = openmc.Tallies()

dose_tally = openmc.Tally(name="flux_tally")
dose_tally.scores = ["flux"]
dose_tally.filters = [
    cell_filter,
#    photon_particle_filter,
#    energy_filter,
    time_filter
]
tallies.append(dose_tally)



##################################################################################################



######## generate xml files ########

geom.export_to_xml()
mats.export_to_xml()
sett.export_to_xml()
tallies.export_to_xml()


# remove old h5 and xml files
!rm *.h5

# run openmc
openmc.run()


results = openmc.StatePoint("statepoint.10.h5")

tally = results.get_tally(name='flux_tally', scores=['flux'])

print("flux : ", tally.mean)

tibetscarlett avatar Aug 09 '22 12:08 tibetscarlett

I have also noticed some underprediction with the time filter when we have been trying to compare with experimental results and MCNP. We have seen better results with surface tallies results than track length estimators, but still some possible issues. I am going to do some digging into the code a bit more.

py1sl avatar Aug 10 '22 10:08 py1sl

In src/tallies/filter_time.cpp, the function "get_all_bins" seems to skip particle tracks where t_start == t_end, if the estimator is equal to "TRACKLENGTH":

if (estimator == TallyEstimator::TRACKLENGTH) {
    // -------------------------------------------------------------------------
    // For tracklength estimator, we have to check the start/end time of
    // the current track and find where it overlaps with time bins and score
    // accordingly

    // Skip if time interval is zero
    if (t_start == t_end)
      return;

I'm not sure why the tracklength estimator should just skip tallying a particle track if the interval is zero. Couldn't it just tally in whichever bin that particle fits. It seems that the problem is in dividing by the difference between t_start and t_end to determine weight adjustments for a given bin.

// Find matching bins
    double dt_total = t_end - t_start;
    for (; i_bin < bins_.size() - 1; ++i_bin) {
      double t_left = std::max(t_start, bins_[i_bin]);
      double t_right = std::min(t_end, bins_[i_bin + 1]);

      // Add match with weight equal to the fraction of the time interval within
      // the current time bin
      const double fraction = (t_right - t_left) / dt_total;

EDIT: This appears to directly affect photon flux tallies as t_start does equal t_end, at least that's what it appears to be if I try to output these values at run-time.

EDIT 2: This also seems to affect neutrons as occasionally t_start-t_end = 0 for neutrons.

tibetscarlett avatar Aug 10 '22 10:08 tibetscarlett

I think changing the contents of "get_all_bins" in src/tallies/filter_time.cpp to the following might work:

// Get the start/end time of the particle for this track
  const auto t_start = p.time_last();
  const auto t_end = p.time();

  // If time interval is entirely out of time bin range, exit
  if (t_end < bins_.front() || t_start >= bins_.back())
    return;

  if (estimator == TallyEstimator::TRACKLENGTH) {
    // -------------------------------------------------------------------------
    // For tracklength estimator, we have to check the start/end time of
    // the current track and find where it overlaps with time bins and score
    // accordingly

    // Determine first bin containing a portion of time interval
    auto i_bin = lower_bound_index(bins_.begin(), bins_.end(), t_start);

    if (t_start != t_end) {
      // Find matching bins
      double dt_total = t_end - t_start;
      for (; i_bin < bins_.size() - 1; ++i_bin) {
        double t_left = std::max(t_start, bins_[i_bin]);
        double t_right = std::min(t_end, bins_[i_bin + 1]);

        // Add match with weight equal to the fraction of the time interval within
        // the current time bin
        const double fraction = (t_right - t_left) / dt_total;
        match.bins_.push_back(i_bin);
        match.weights_.push_back(fraction);

        if (t_end < bins_[i_bin + 1])
          break;
      }
    }
    else {
      match.bins_.push_back(i_bin);
      match.weights_.push_back(1.0);
    }
  } else {
    // -------------------------------------------------------------------------
    // For collision estimator or surface tallies, find a match based on the
    // exact time of the particle
    const auto i_bin = lower_bound_index(bins_.begin(), bins_.end(), t_end);
    match.bins_.push_back(i_bin);
    match.weights_.push_back(1.0);
  }

tibetscarlett avatar Aug 10 '22 13:08 tibetscarlett