The null-collision method
MonteCarloCollisions implements the null-collision method of Vahedi and Surendra
(Comput. Phys. Commun. 87, 179 (1995)). A marker of species with velocity meets
a neutral of background with velocity , drawn from the background’s Maxwellian, at
relative speed . Process has the collision frequency
where is the neutral density and the energy the cross section is tabulated against (see energy frames).
One step
Section titled “One step”- Bound. The operator takes as the fastest marker’s speed plus eight thermal speeds of the neutrals and looks up a bound for all .
- Candidates. Every marker becomes a candidate with probability . On NumPy the number of candidates is drawn from a binomial distribution and then the candidates themselves, so the cost scales with the candidates, not with all markers. Other generators (CuPy’s) draw one number per marker.
- Selection. Each candidate draws a neutral velocity and a uniform number with . It undergoes process if , and a null collision (nothing happens) if exceeds the total.
- Kinematics. The chosen process changes the velocity, removes the marker or creates products. See collision processes.
Real collisions of process thus happen with probability per step. That is to first order, independent of the bound.
The frequency bound
Section titled “The frequency bound”At construction the operator tabulates on a logarithmic grid of
num_bound_samples speeds, merged with the speeds of every table point and threshold, and stores
its running maximum. Because the cross sections are piecewise linear in energy, the frequency’s
local maxima lie at or near those points. bound_safety (default 1.02) covers maxima between
grid points. Beyond the last table point the cross sections are constant and grows
linearly with , which the direct evaluation at covers.
The bound must hold. If a candidate’s real total frequency exceeds , the collision rate
is underestimated. The operator counts such candidates in MCCDiagnostics.bound_violations and
warns once.
Density profiles and thinning
Section titled “Density profiles and thinning”A background with a profile has density . The bound uses the largest factor , and each candidate’s real frequency uses the factor at its position. Markers where the gas is thin are therefore mostly null collisions. This is thinning of a Poisson process, and it is exact.
A profile is a DensityProfile (piecewise linear along one axis) or a
FunctionProfile(function, max_factor) of all position components; any object with a
max_factor and a call on the positions works. A FunctionProfile must not exceed its
max_factor; where it does, the candidates are counted as bound violations.
Flowing and heated gas
Section titled “Flowing and heated gas”NeutralBackground(..., drift=(ux, uy, uz)) is a gas flowing with that mean velocity, and
temperature_profile= scales its temperature in space like a density profile. Neutral partners
are drawn from the local drifting Maxwellian, and the bound covers relative speeds up to the
marker speed plus the flow speed plus eight of the largest thermal speeds.
A background is fixed during a step. To follow gas heating or depletion, build the new
background between steps and pass it to MonteCarloCollisions.set_background, which
retabulates the bound.
Choosing the time step
Section titled “Choosing the time step”A marker collides at most once per step, and products do not collide in the step that created them. Rates are therefore correct to first order in . A population that ionizes at constant grows by per step instead of , which tutorial 3 measures. Keep (a few percent error on the rate), or collide every steps with only if stays that small.
The operator checks this: MCCDiagnostics.collision_probability reports
of every call, and the operator warns once when it exceeds
max_collision_probability (default 0.1).
Random numbers
Section titled “Random numbers”Every operator owns a generator. seed and rank select the stream
numpy.random.SeedSequence(seed, spawn_key=(rank,)), so MPI ranks that pass their rank draw
independent numbers from one seed, and a run with the same seed and rank count is reproducible.
On the CuPy backend the stream seeds a CuPy generator. A generator can also be passed directly
with rng=.