Distribution evolution

Distribution evolution#

Now that we can make a distribution and calculate coagulation rates, let’s move that forward in time.

try:
    import particula, matplotlib
except ImportError:
    print("Setting up computational environment...")
    %pip install -U particula -qqq
    %pip install matplotlib -qqq


from particula import particle, rates
import numpy as np
from matplotlib import pyplot as plt
from particula.util.coagulation_rate import CoagulationRate
from particula.util.dimensionless_coagulation import full_coag
from particula.util.input_handling import in_time

We can do this in a simple way by using a for loop to march through the time steps.

# Time steps

simple_dic_kwargs = {
    "mode": 200e-9, # 200 nm median
    "nbins": 500, # 1000 bins
    "nparticles": 1e6, # 1e4 #
    "volume": 1e-6, # per 1e-6 m^3 (or 1 cc)
    "gsigma": 1.5, # relatively narrow
}
particle_dist2 = particle.Particle(**simple_dic_kwargs) # pass the kwargs using ** prefix

#inital distribution
p_distribution_0 = particle_dist2.particle_distribution()
p_radius = particle_dist2.particle_radius
coag_kernel = full_coag(radius=p_radius)

p_distribution_i = p_distribution_0

time_interval = in_time(10)
time_array = np.arange(0, 1000, time_interval.m)

distribution_time = np.zeros([len(time_array), len(p_distribution_0)])

for i, dpa in enumerate(time_array):

    if i>0:
        # calculate coagulations
        coag_gain_i = CoagulationRate(p_distribution_i, p_radius, coag_kernel).coag_gain()
        coag_loss_i = CoagulationRate(p_distribution_i, p_radius, coag_kernel).coag_loss()

        net_change = (coag_gain_i-coag_loss_i)*time_interval
        p_distribution_i = p_distribution_i+net_change

    distribution_time[i,:] = p_distribution_i.m

For loop Graph#

fig, ax = plt.subplots(1, 1, figsize=[9, 6])

radius = p_radius.m
ax.semilogx(radius, p_distribution_0.m, '-b', label='Inital')
ax.semilogx(radius, distribution_time[49,:], '--', label='t=50')
ax.semilogx(radius, distribution_time[-1,:], '-r', label='t=end')

ax.legend()
ax.set_ylabel(f"Number, {particle_dist2.particle_distribution().u}")
ax.set_xlabel(f"Radius, {particle_dist2.particle_radius.u}")
ax.grid(True, alpha=0.5)
../_images/69538e06e64c96d54809deada797f1e6fd4b3159a0c006e9ca0622c34226bb17.png

ODE solver#

Instead of prescribing the time steps, which can make the problem solution unstable, let’s let the code handle that with an ODE solver.

Lets import that solver class

from particula.util.simple_solver import SimpleSolver

Then let us run the same dynamic problem again

simple_dic_kwargs = {
    "mode": 200e-9, # 200 nm median
    "nbins": 500, # 1000 bins
    "nparticles": 1e6, # 1e4 #
    "volume": 1e-6, # per 1e-6 m^3 (or 1 cc)
    "gsigma": 1.5, # relatively narrow
}
particle_dist2 = particle.Particle(**simple_dic_kwargs) # pass the kwargs using ** prefix

#inital distribution coag kernel
coag_kernel = full_coag(radius=particle_dist2.particle_radius)
time_array = np.arange(0, 1000, 10)

#setup the inital state of the distribution
problem = {
    "distribution": particle_dist2.particle_distribution(),
    "radius": particle_dist2.particle_radius,
    "kernel": coag_kernel,
    "tspan": time_array
}

#call the solver
solution = SimpleSolver(**problem).solution()

#plot
fig, ax = plt.subplots(1, 1, figsize=[9, 6])

radius = p_radius.m
ax.semilogx(radius, particle_dist2.particle_distribution().m, '-b', label='Inital')
ax.semilogx(radius, solution.m[49,:], '--', label='t=50')
ax.semilogx(radius, solution.m[-1,:], '-r', label='t=end')

ax.legend()
ax.set_ylabel(f"Number, {particle_dist2.particle_distribution().u}")
ax.set_xlabel(f"Radius, {particle_dist2.particle_radius.u}")
ax.grid(True, alpha=0.5)
../_images/3511f05e36b730b892c9f2b01226630f0fa0d379b860e7fa804b75dc1e4ba09c.png

summary on stepping#

As we walked through, using the ODE solver is quite a nice way to get to the answer, without figuring out what time-step you might need.