import math
from warnings import warn
import numpy as np
from openalea.mtg import traversal
from openalea.hydroroot.length import fit_law
from openalea.hydroroot import radius, flux, conductance
from openalea.hydroroot.generator import markov, measured_root # 21-12-14: FB __init__.py in src not doing job
from openalea.hydroroot.water_solute_transport import pressure_calculation_no_non_permeating_solutes, init_some_MTG_properties, pressure_calculation
[docs]
def hydroroot_mtg(
primary_length=0.15,
delta=2.e-3,
beta=0.25,
order_max=5,
segment_length=1e-4,
nude_length=0.02,
seed=2,
ref_radius=1e-4,
order_decrease_factor=0.7,
length_data=None,
n=None,
**kwds
):
"""Simulate and generate a root system.
:param primary_length: Float (Default value = 0.15)
:param delta: Float (Default value = 2.e-3)
:param beta: Float (Default value = 0.25)
:param order_max: int (Default value = 5)
:param segment_length: Float (Default value = 1e-4)
:param nude_length: Float (Default value = 0.02)
:param seed: int (Default value = 2)
:param ref_radius: Float (Default value = 1e-4)
:param order_decrease_factor: Float (Default value = 0.7)
:param length_data: pandas dataframe (Default value = None)
:param n: int (Default value = None)
:returns:
- g (MTG)
- surface (float)
- volume (float)
"""
# F. Bauget 2022-08-12: added if-else to be able to use the function without length data, useful for usage demo
if length_data:
xl, yl = length_data
length_law = fit_law(xl, yl, scale=segment_length)
else:
length_law = None
# compute the architecture
nb_nude_vertices = int(nude_length / segment_length)
branching_delay = int(delta / segment_length)
if n is None:
nb_vertices = int(primary_length / segment_length)
else:
nb_vertices = n
warn("Use primary_length instead")
g = markov.markov_binary_tree(
nb_vertices=nb_vertices,
branching_variability=beta,
branching_delay=branching_delay,
length_law=length_law,
nude_tip_length=nb_nude_vertices,
order_max=order_max,
seed=seed)
# compute radius property on MTG
g = radius.ordered_radius(g, ref_radius=ref_radius, order_decrease_factor=order_decrease_factor)
# compute length property and parametrisation
g = radius.compute_length(g, segment_length)
g = radius.compute_relative_position(g)
g, surface = radius.compute_surface(g)
g, volume = radius.compute_volume(g)
return g, surface, volume
[docs]
def hydroroot_flow(
g,
segment_length=1.e-4,
k0=300,
Jv=0.1,
psi_e=0.4,
psi_base=0.1,
axial_conductivity_data=None,
radial_conductivity_data=None,
):
r"""Flux and equivalent conductance calculation
:param g: MTG
:param segment_length: (float) - not used vertices length in hydroroot in m (Default value = 1.0e-4) #TODO not used delete it
:param k0: (float) - not used radial conductivity in :math:`10^{-9}\ m.s^{-1}.MPa^{-1}` (Default value = 300) #TODO not used delete it
:param Jv: (Float) not used because invert_model=True in Flux.Flux (Default value = 0.1) #TODO delete it or add invert_model in arguments
:param psi_e: (Float) external hydrostatic potential in MPa (Default value = 0.4)
:param psi_base: (Float) root base hydrostatic potential in MPa (Default value = 0.1)
:param axial_conductivity_data: (2 list of Float) axial conductance (:math:`10^{-9}\ m^4.MPa^{-1}.s^{-1}`) versus distance to tip (m) (Default value = None)
:param radial_conductivity_data: (2 list of Float) radial conductivity (:math:`10^{-9}\ m.MPa^{-1}.s^{-1}`) versus distance to tip (m) (Default value = None)
:returns:
- g (MTG): the MTG with the following properties filled: K (axial conductance), k (radial donductivity),
j (radial flux), J_out (axial flux), psi_in and psi_out (hydrostatic pressure into the root at the
input and output of a MTG node
- Keq (float): the equivalent conductance of the whole root
- Jv_global (float): the outgoing flux at the root base
"""
if axial_conductivity_data:
# Compute K using axial conductance data
xa, ya = axial_conductivity_data
# commented line below, BUG correction, the global flux was diverging when decreasing segment_length
# ya was not supposed to be multiplied by (segment_length / 1.e-4)
# ya = list(np.array(ya) * (segment_length / 1.e-4))
axial_conductivity_law = fit_law(xa, ya)
g = conductance.fit_property_from_spline(g, axial_conductivity_law, 'position', 'K_exp')
g = conductance.compute_K(g) # Fabrice 2020-01-17: calculation of K in dimension [L^3 P^(-1) T^(-1)]
if radial_conductivity_data:
xr, yr = radial_conductivity_data
radial_conductivity_law = fit_law(xr, yr)
g = conductance.fit_property_from_spline(g, radial_conductivity_law, 'position', 'k0')
g = conductance.compute_k(g, k0='k0')
# Compute the flux
g = flux.flux(g, Jv, psi_e, psi_base, invert_model=True)
Keqs = g.property('Keq')
v_base = next(g.component_roots_at_scale_iter(g.root, scale=1))
Keq = Keqs[v_base]
if psi_e is None:
Peq = g.property('Peq')
Jv_global = Keq * (Peq[v_base] - psi_base)
else:
Jv_global = Keq * (psi_e - psi_base)
return g, Keq, Jv_global
[docs]
def hydroroot_solute_flow(
g,
psi_e=0.4,
psi_base=0.1,
k0=None,
axial_conductivity_data=None,
radial_conductivity_data=None,
J_s = 1.0e-7, Ps = 1.0e-9, Cse = 13.96e-9, Ce = 0.0, sigma=1.0, Temp=298, C_base = None, eps = 1.0e-9):
r"""Water and solute fluxes and equivalent conductance calculation on an MTG
:param g: MTG
:param psi_e: (Float) external hydrostatic potential in MPa (Default value = 0.4)
:param psi_base: (Float) root base hydrostatic potential in MPa (Default value = 0.1)
:param k0: (float) - uniform radial conductivity in :math:`10^{-9}\ m.s^{-1}.MPa^{-1}` (Default value = None)
:param axial_conductivity_data: (2 list of Float) axial conductance profile (:math:`10^{-9}\ m^4.MPa^{-1}.s^{-1}`) versus distance to tip (m) (Default value = None)
:param radial_conductivity_data: (2 list of Float) radial conductivity profile (:math:`10^{-9}\ m.MPa^{-1}.s^{-1}`) versus distance to tip (m) (Default value = None)
:param J_s: (float) active pumping rate in mol/(m2.s) (Default value = 1e-7)
:param Ps: (float) permeability coefficient in m/s (Default value = 1e-9)
:param Cse: (float) initial permeating solute concentration inside the root in mol/microL (Default value = 13.96e-9)
:param Ce: (float) initial non-permeating solute concentration outside the root in mol/microL (Default value = 0.)
:param sigma: (float) - the reflexion coefficient between 0 and 1 (Default value = 1.0)
:param Temp: (float) - temperature in Kelvin (Default value = 298)
:param C_base: (float) solute concentration at the root base in mol/microL (Default value = None)
:param eps: (float) convergence criterium (Default value = 1e-9)
:returns:
- g (MTG): the MTG with the following properties filled: K (axial conductance), k (radial donductivity),
j (radial flux), J_out (axial flux), psi_in and psi_out (hydrostatic pressure into the root at the
input and output of a MTG node)
- Jv_global (float): the outgoing flux at the root base
"""
xa, ya = axial_conductivity_data
axial_conductivity_law = fit_law(xa, ya)
# Compute K using axial conductance data
g = conductance.fit_property_from_spline(g, axial_conductivity_law, 'position', 'K_exp')
g = conductance.compute_K(g)
if k0:
g = conductance.setting_k0_according_to_order(g, k0, k0)
else:
xr, yr = radial_conductivity_data
radial_conductivity_law = fit_law(xr, yr)
g = conductance.fit_property_from_spline(g, radial_conductivity_law, 'position', 'k0')
g = conductance.compute_k(g, k0='k0')
g = flux.flux(g, psi_e = psi_e, psi_base = psi_base, invert_model=True)
g = init_some_MTG_properties(g, tau=J_s, Cini=Cse, Cpeg_ini = Ce, t = 1, Ps = Ps)
if Ce <= 0.0:
calculation = pressure_calculation_no_non_permeating_solutes
else:
calculation = pressure_calculation
# Newton-Raphson loop
nb_v = g.nb_vertices()
Fdx = 1.0
Fdx_old = 1.
while Fdx > eps:
g, dx, data, row, col = calculation(g, Temp=Temp, sigma=sigma, Ce=Ce, Cse=Cse, Pe=psi_e,
Pbase=psi_base, C_base=C_base)
Fdx = math.sqrt(sum(dx ** 2.0)) / nb_v
if abs(Fdx - Fdx_old) < eps: break
Fdx_old = Fdx
Jv_global = g.property('J_out')[1]
return g, Jv_global
[docs]
def hydroroot(
primary_length=0.15,
delta=2.e-3,
beta=0.25,
order_max=5,
segment_length=1e-4,
nude_length=0.02,
seed=2,
ref_radius=1e-4,
order_decrease_factor=0.7,
k0=300,
Jv=0.1,
psi_e=0.4,
psi_base=0.1,
length_data=None,
axial_conductivity_data=None,
radial_conductivity_data=None,
n=None
):
"""Simulate a root system and compute global conductance and flux.
see :func:`~main.hydroroot_mtg` and :func:`~main.hydroroot_flow`
deprecated
:param primary_length: (Default value = 0.15)
:param delta: (Default value = 2.e-3)
:param beta: (Default value = 0.25)
:param order_max: (Default value = 5)
:param segment_length: (Default value = 1e-4)
:param nude_length: (Default value = 0.02)
:param seed: (Default value = 2)
:param ref_radius: (Default value = 1e-4)
:param order_decrease_factor: (Default value = 0.7)
:param k0: (Default value = 300)
:param Jv: (Default value = 0.1)
:param psi_e: (Default value = 0.4)
:param psi_base: (Default value = 0.1)
:param length_data: (Default value = None)
:param axial_conductivity_data: (Default value = None)
:param radial_conductivity_data: (Default value = None)
:param n: (Default value = None)
"""
g, surface, volume = hydroroot_mtg(primary_length=primary_length,
delta=delta,
beta=beta,
order_max=order_max,
segment_length=segment_length,
nude_length=nude_length,
seed=seed,
ref_radius=ref_radius,
order_decrease_factor=order_decrease_factor,
length_data=length_data,
n=n,
)
xa, ya = axial_conductivity_data
# commented line below, BUG correction, the global flux was diverging when decreasing segment_length
# ya was not supposed to be multiplied by (segment_length / 1.e-4)
# ya = list(np.array(ya) * (segment_length / 1.e-4))
axial_conductivity_law = fit_law(xa, ya)
xr, yr = radial_conductivity_data
radial_conductivity_law = fit_law(xr, yr)
# Compute K using axial conductance data
g = conductance.fit_property_from_spline(g, axial_conductivity_law, 'position', 'K_exp')
g = conductance.compute_K(g) # Fabrice 2020-01-17: calculation of K in dimension [L^3 P^(-1) T^(-1)]
# Compute the flux
g = conductance.fit_property_from_spline(g, radial_conductivity_law, 'position', 'k0')
g = conductance.compute_k(g, k0='k0')
# TODO: return Keq base and Jv
g = flux.flux(g, Jv, psi_e, psi_base, invert_model=True)
Keqs = g.property('Keq')
v_base = next(g.component_roots_at_scale_iter(g.root, scale=1))
Keq = Keqs[v_base]
Jv_global = Keq * (psi_e - psi_base)
return g, surface, volume, Keq, Jv_global
[docs]
def hydroroot_from_data(
primary_length=0.15,
delta=2.e-3,
beta=0.25,
order_max=5,
segment_length=1e-4,
nude_length = 0.02,
seed = 2,
ref_radius = 1e-4,
order_decrease_factor = 0.7,
k0 = 300,
Jv = 0.1,
psi_e = 0.4,
psi_base = 0.1,
length_data=None,
axial_conductivity_data=None,
radial_conductivity_data=None,
primary_length_data=None,
lateral_length_data=None,
):
"""Reconstruct a root system and compute global conductance and flux.
deprecated
:param primary_length_data: list Float (Default value = None)
:param lateral_length_data: list Float (Default value = None)
:param return:
:param g: MTG
:param surface: float
:param volume: float
:param Keq: float
:param Jv_global: float
:param see: func
:param primary_length: (Default value = 0.15)
:param delta: (Default value = 2.e-3)
:param beta: (Default value = 0.25)
:param order_max: (Default value = 5)
:param segment_length: (Default value = 1e-4)
:param nude_length: (Default value = 0.02)
:param seed: (Default value = 2)
:param ref_radius: (Default value = 1e-4)
:param order_decrease_factor: (Default value = 0.7)
:param k0: (Default value = 300)
:param Jv: (Default value = 0.1)
:param psi_e: (Default value = 0.4)
:param psi_base: (Default value = 0.1)
:param length_data: (Default value = None)
:param axial_conductivity_data: (Default value = None)
:param radial_conductivity_data: (Default value = None)
"""
xl, yl = length_data
length_law = fit_law(xl, yl, scale=segment_length)
xa, ya = axial_conductivity_data
# commented line below, BUG correction, the global flux was diverging when decreasing segment_length
# ya was not supposed to be multiplied by (segment_length / 1.e-4)
# ya = list(np.array(ya) * (segment_length / 1.e-4))
axial_conductivity_law = fit_law(xa, ya)
xr, yr = radial_conductivity_data
radial_conductivity_law = fit_law(xr, yr)
# compute the architecture
nb_nude_vertices = int(nude_length / segment_length)
branching_delay = int(delta / segment_length)
g = measured_root.mtg_builder(
primary_length,
primary_length_data,
lateral_length_data,
segment_length=segment_length,
branching_variability=beta,
branching_delay=branching_delay,
length_law=length_law,
nude_tip_length=nb_nude_vertices,
order_max=order_max,
seed=seed)
# compute radius property on MTG
g = radius.ordered_radius(g, ref_radius=ref_radius, order_decrease_factor=order_decrease_factor)
# compute length property and parametrisation
g = radius.compute_length(g, segment_length)
g = radius.compute_relative_position(g)
# Compute K using axial conductance data
g = conductance.fit_property_from_spline(g, axial_conductivity_law, 'position', 'K_exp')
g = conductance.compute_K(g) # Fabrice 2020-01-17: calculation of K in dimension [L^3 P^(-1) T^(-1)]
g, surface = radius.compute_surface(g)
g, volume = radius.compute_volume(g)
# Compute the flux
g = conductance.fit_property_from_spline(g, radial_conductivity_law, 'position', 'k0')
g = conductance.compute_k(g, k0='k0')
# TODO: return Keq base and Jv
g = flux.flux(g, Jv, psi_e, psi_base, invert_model=True)
Keqs = g.property('Keq')
v_base = next(g.component_roots_at_scale_iter(g.root, scale=1))
Keq = Keqs[v_base]
Jv_global = Keq * (psi_e - psi_base)
return g, surface, volume, Keq, Jv_global
[docs]
def root_builder(primary_length = 0.13, seed = None, delta = 2.0e-3, nude_length = 2.0e-2, df = None, segment_length = 1.0e-4,
length_data = None, branching_variability = 0.25, order_max = 4.0, order_decrease_factor = 0.7,
ref_radius = 7.0e-5, Flag_radius = False):
"""wrapper function: build a MTG with properties that are set like : radius, vertex length, position (distance to tip) and mylength (distance to base).
The MTG is either generated or built from a DataFrame. The unit length in hydroroot should be in m.
:param primary_length: (float) - primary root length for generated mtg (Default value = 0.13)
:param seed: (int) - seed for generated mtg (Default value = None)
:param delta: (float) - branching delay for generated mtg in hydroroot should be in m (Default value = 2.0e-3)
:param nude_length: (float) - length from tip without lateral for generated mtg (Default value = 2.0e-2)
:param df: (DataFrame) - DataFrame with the architecture data to be reconstructed if not None (Default value = None)
:param segment_length: (float) - vertices length in hydroroot should be in m (Default value = 1.0e-4)
:param length_data: (string list) - the file name with the length data laws (Default value = None)
:param branching_variability: (float) (Default value = 0.25)
:param order_max: (float) (Default value = 4.0)
:param order_decrease_factor: (float) (Default value = 0.7)
:param ref_radius: (float) - radius in hydroroot should be in m (Default value = 7.0e-5)
:param Flag_radius: (boolean) - True if radius exist in df (Default value = False)
:returns:
- g: MTG with the different properties set or computed (see comments above),
- primary_length: primary root length (output for generated mtg)
- total_length: total root length
- surface: total root surface
- _seed: the seed used in the generator
see also :func:`generator.measured_root.mtg_from_aqua_data` for details about df
"""
if df is not None:
g = measured_root.mtg_from_aqua_data(df, segment_length)
_seed = None
else:
# if no seed just create one
if seed is None:
_seed = markov.my_seed()
else:
_seed = seed
g = markov.generate_g(_seed, length_data,
branching_variability, delta,
nude_length, primary_length, segment_length,
order_max)
# compute radius property on MTG
# F. Bauget 2022-05-17 : added if
if (not Flag_radius) or ('radius' not in g.property_names()):
g = radius.ordered_radius(g, ref_radius, order_decrease_factor)
# compute length property and parametrisation
g = radius.compute_length(g, segment_length)
g = radius.compute_relative_position(g)
# Calculation of the distance from base of each vertex, used for cut and flow
_mylength = {}
for v in traversal.pre_order2(g, 1):
pid = g.parent(v)
_mylength[v] = _mylength[pid] + segment_length if pid else segment_length
g.properties()['mylength'] = _mylength
# total_length is the total length of the RSA (sum of the length of all the segments)
total_length = g.nb_vertices(scale = 1) * segment_length
g, surface = radius.compute_surface(g)
g, volume = radius.compute_volume(g)
if df is not None:
v_base = next(g.component_roots_at_scale_iter(g.root, scale = g.max_scale()))
primary_length = g.property('position')[v_base]
return g, primary_length, total_length, surface, _seed
[docs]
def soil_1D(g, soil_data = None):
"""
add a soil as heterogeneous water potential along
This is $\Psi_e$ versus depth with $depth>0$.
:param g: MTG
:param soil_data: tuple of 2 lists, (z,psi_e) z=depth (m), psi_e=water potential (MPa)
:return: g
"""
z,psi_e = soil_data
# Compute absolute z coordinate and normalize
vids = g.property('xyz').keys()
zs = np.array([np.abs(pt.z) for pt in g.property('xyz').values()])
zs = zs.tolist()
# Fit data on z coordinate to compute psi_e on each vertex
g.properties()['height'] = dict(zip(vids,zs))
soil_law = fit_law(z,psi_e)
g = conductance.fit_property_from_spline(g, soil_law, 'height', 'psi_e')
return g