Source code for tyssue.solvers.viscous
"""Viscosity based ODE solver
"""
import logging
import warnings
import numpy as np
from ..behaviors.event_manager import EventManager
from ..behaviors.sheet.basic_events import reconnect
from ..core.history import History
log = logging.getLogger(__name__)
MAX_ITER = 1000
[docs]def set_pos(eptm, geom, pos):
"""Updates the vertex position of the :class:`Epithelium` object.
Assumes that pos is passed as a 1D array to be reshaped as (eptm.Nv, eptm.dim)
"""
log.debug("set pos")
eptm.vert_df.loc[eptm.active_verts, eptm.coords] = pos.reshape((-1, eptm.dim))
geom.update_all(eptm)
[docs]class EulerSolver:
"""Explicit Euler solver"""
def __init__(
self,
eptm,
geom,
model,
history=None,
auto_reconnect=False,
manager=None,
bounds=None,
with_t1=False,
with_t3=False,
):
"""creates an instance of EulerSolver
Parameters
----------
eptm : a :class:`tyssue.Epithelium` instance
geom : a Geometry class
model : a Model class
history : a :class:`tyssue.History` or :class:`tyssue.Hdf5History` instance
auto_reconnect : bool
if True, will automatically perform reconnections, default False
manager : a :class:`tyssue.EventManager` instance
bounds : tuple of (min, max),
bonds the displacement of the vertices at each time step
"""
self._set_pos = set_pos
if with_t1:
warnings.warn("with_t1 is deprecated and has no effect")
# self._set_pos = auto_t1(self._set_pos)
if with_t3:
warnings.warn("with_t3 is deprecated and has no effect")
# self._set_pos = auto_t3(self._set_pos)
# self.rearange = with_t1 or with_t3
# self.with_t3 = with_t3
self.eptm = eptm
self.geom = geom
self.model = model
if history is None:
self.history = History(eptm)
else:
self.history = history
self.prev_t = 0
if auto_reconnect:
if manager is None:
manager = EventManager()
if "reconnect" not in [n[0].__name__ for n in manager.next]:
manager.append(reconnect)
self.manager = manager
self.bounds = bounds
@property
def current_pos(self):
return self.eptm.vert_df.loc[
self.eptm.active_verts, self.eptm.coords
].values.ravel()
[docs] def set_pos(self, pos):
"""Updates the eptm vertices position"""
return self._set_pos(self.eptm, self.geom, pos)
[docs] def record(self, t):
self.history.record(time_stamp=t)
[docs] def solve(self, tf, dt, on_topo_change=None, topo_change_args=()):
"""Solves the system of differential equations from the current time
to tf with steps of dt with a forward Euler method.
Parameters
----------
tf : float, final time when we stop solving
dt : float, time step
on_topo_change : function, optional, default None
function of `self.eptm`
topo_change_args : tuple, arguments passed to `on_topo_change`
"""
self.eptm.settings["dt"] = dt
for t in np.arange(self.prev_t, tf + dt, dt):
pos = self.current_pos
dot_r = self.ode_func(t, pos)
if self.bounds is not None:
dot_r = np.clip(dot_r, *self.bounds)
pos = pos + dot_r * dt
self.set_pos(pos)
self.prev_t = t
if self.manager is not None:
self.manager.execute(self.eptm)
self.geom.update_all(self.eptm)
self.manager.update()
if self.eptm.topo_changed:
log.info("Topology changed")
if on_topo_change is not None:
on_topo_change(*topo_change_args)
self.eptm.topo_changed = False
self.record(t)
[docs] def ode_func(self, t, pos):
"""Computes the models' gradient.
Returns
-------
dot_r : 1D np.ndarray of shape (self.eptm.Nv * self.eptm.dim, )
.. math::
\frac{dr_i}{dt} = -\frac{\nabla U_i}{\eta_i}
"""
grad_U = self.model.compute_gradient(self.eptm).loc[self.eptm.active_verts]
return (
-grad_U.values
/ self.eptm.vert_df.loc[self.eptm.active_verts, "viscosity"].values[:, None]
).ravel()
[docs]class IVPSolver:
def __init__(self, *args, **kwargs):
raise NotImplementedError(
"""It is not yet clear how to use scipy's `solve_ivp` with topology changes
Previous attempts where made but turned out to be clumsy..."""
)