Source code for openpnm.algorithms._transient_reactive_transport
import logging
import numpy as np
from openpnm.algorithms import ReactiveTransport
from openpnm.utils import Docorator
from openpnm.integrators import ScipyRK45
from openpnm.algorithms._solution import SolutionContainer
__all__ = ['TransientReactiveTransport']
docstr = Docorator()
logger = logging.getLogger(__name__)
@docstr.get_sections(base='TransientReactiveTransportSettings',
sections=['Parameters', 'Other Parameters'])
@docstr.dedent
class TransientReactiveTransportSettings:
r"""
Parameters
----------
%(ReactiveTransportSettings.parameters)s
"""
pore_volume = 'pore.volume'
[docs]
class TransientReactiveTransport(ReactiveTransport):
r"""
A subclass of ReactiveTransport for transient simulations.
Parameters
----------
network : Network
The Network with which this algorithm is associated.
Notes
-----
Either a Network or a Project must be specified.
"""
def __init__(self, phase, name='trans_react_?', **kwargs):
super().__init__(phase=phase, name=name, **kwargs)
self.settings._update(TransientReactiveTransportSettings())
self.settings['phase'] = phase.name
self["pore.ic"] = np.nan
[docs]
def run(self, x0, tspan, saveat=None, integrator=None):
"""
Runs the transient algorithm and returns the solution.
Parameters
----------
x0 : ndarray or float
Array (or scalar) containing initial condition values.
tspan : array_like
Tuple (or array) containing the integration time span.
saveat : array_like or float, optional
If an array is passed, it signifies the time points at which
the solution is to be stored, and if a scalar is passed, it
refers to the interval at which the solution is to be stored.
integrator : Integrator, optional
Integrator object which will be used to to the time stepping.
Can be instantiated using openpnm.integrators module.
Returns
-------
TransientSolution
The solution object, which is basically a numpy array with
the added functionality that it can be called to return the
solution at intermediate times (i.e., those not stored in the
solution object).
"""
logger.info('Running TransientTransport')
if np.isscalar(saveat):
saveat = np.arange(*tspan, saveat)
# FIXME: why do we forcibly add tspan[1] to saveat even if the user
# didn't want to?
if (saveat is not None) and (tspan[1] not in saveat):
saveat = np.hstack((saveat, [tspan[1]]))
integrator = ScipyRK45() if integrator is None else integrator
# Perform pre-solve validations
self._validate_settings()
self._validate_topology_health()
self._validate_linear_system()
# Write x0 to algorithm the obj (needed by _update_iterative_props)
self['pore.ic'] = x0 = np.ones(self.Np, dtype=float) * x0
self._merge_inital_and_boundary_values()
# Build RHS (dx/dt = RHS), then integrate the system of ODEs
rhs = self._build_rhs()
# Integrate RHS using the given solver
soln = integrator.solve(rhs, x0, tspan, saveat)
# Return solution as dictionary
self.soln = SolutionContainer()
self.soln[self.settings['quantity']] = soln
def _run_special(self, x0):
pass
def _build_rhs(self):
"""
Returns a function handle, which calculates dy/dt = rhs(y, t).
Notes
-----
``y`` is the variable that the algorithms solves for, e.g., for
``TransientFickianDiffusion``, it would be concentration.
"""
def ode_func(t, y):
# TODO: add a cache mechanism
self.x = y
self._update_A_and_b()
A = self.A.tocsc()
b = self.b
V = self.network[self.settings["pore_volume"]]
return (-A.dot(y) + b) / V # much faster than A*y
return ode_func
def _merge_inital_and_boundary_values(self):
x0 = self['pore.ic']
bc_pores = ~np.isnan(self['pore.bc.value'])
x0[bc_pores] = self['pore.bc.value'][bc_pores]
quantity = self.settings['quantity']
self[quantity] = x0