from math import pi
from collections import defaultdict
from openalea.mtg import *
#from openalea.mtg import algo
import numpy as np
from scipy.interpolate import UnivariateSpline
import pylab
from openalea.hydroroot.length import fit_law
[docs]
def setting_k0_according_to_order(g, k0_pr, k0_lr):
r"""
Set uniform radial conductivity to roots according to their order:
- to k0_pr for the primary root (order == 0),
- to k0_lr otherwise
:param g: (MTG)
:param k0_pr: (float) - radial donductivity (:math:`10^{-9}\ m.MPa^{-1}.s^{-1}`) for the primary root
:param k0_lr: (float) - radial donductivity (:math:`10^{-9}\ m.MPa^{-1}.s^{-1}`) for root of order > 0
:returns: - g: (MTG) - the root architecture with k0_pr and k0_lr set
"""
d = {k: k0_pr if g.property('order')[k] == 0 else k0_lr for k, v in list(g.property('order').items())}
g.properties()['k0'] = d
return g
[docs]
def set_conductances(g, axial_pr, k0_pr, axial_lr = None, k0_lr = None):
"""
Set the MTG properties 'K_exp', 'K' and 'k':
- K_exp: the model input axial conductance in :math:`[L^4.P^{-1}.T^{-1}]`
- K: the effective axial conductance of each vertex :math:`K=K_{exp}/l\\ [L^3.P^{-1}.T^{-1}]`
- k: the radial conductance :math:`k=2 \\pi r l k_0 \\ [L^3.P^{-1}.T^{-1}]`
with r and l the radius and the length of the vertex respectively.
if axial_lr is None, set 'K_exp' and 'K' according to axial_pr whatever the roots order, otherwise set
the roots of order == 0 according to axial_pr and others according to axial_lr. idem for the radial conductivity if
k0_lr is not None.
:param g: (MTG)
:param axial_pr: (list) - axial conductance (:math:`10^{-9}\\ m^4.MPa^{-1}.s^{-1}`) vs distance to tip, 2 lists of float
:param k0_pr: (float) - radial donductivity (:math:`10^{-9}\\ m.MPa^{-1}.s^{-1}`)
:param axial_lr: (list) - axial conductance (:math:`10^{-9}\\ m^4.MPa^{-1}.s^{-1}`) for root of order > 0, 2 lists of float (Default value = None)
:param k0_lr: (float) - radial donductivity (:math:`10^{-9}\\ m.MPa^{-1}.s^{-1}`) for root of order > 0 (Default value = None)
:returns: - g (MTG)
"""
xa, ya = axial_pr
axial_conductivity_law = fit_law(xa, ya)
# Compute K using axial conductance data
if axial_lr is None:
g = fit_property_from_spline(g, axial_conductivity_law, 'position', 'K_exp')
else:
K = {}
xa, ya = axial_lr
axial_conductivity_lr_law = fit_law(xa, ya)
for v, k in list(g.property('order').items()):
x = g.property('position')[v]
if k == 0:
K[v] = axial_conductivity_law(x)
else:
K[v] = axial_conductivity_lr_law(x)
g.properties()['K_exp'] = K
g = compute_K(g) # Fabrice 2020-01-17: calculation of K in dimension [L^3 P^(-1) T^(-1)]
if k0_lr is None: k0_lr = k0_pr
g = setting_k0_according_to_order(g, k0_pr, k0_lr)
g = compute_k(g, k0 = 'k0')
return g
def compute_K_from_laws(g):
"""
:deprecated:
compute the axial conductance K versus the vertex position according to some laws and to the root types: crown, seminal,
laterals
:param g: (MTG)
"""
K={}
segment_length = 1e-4
seminal_axial_conductivity_law = lambda x: 2135.05*x + 21338.63
crown_axial_conductivity_law = lambda x: 9228.45*x + 42600.14
lateral_axial_conductivity_law= lambda x: 2951.65*x + 2720.09
positions= g.property('position')
orders = g.property('order')
for vid in g.vertices_iter(g.max_scale()):
if g.label(vid)=='Crown':
K[vid] = crown_axial_conductivity_law(positions[vid])
elif g.label(vid) == 'Seminal' :
if orders[vid] == 0:
K[vid] = seminal_axial_conductivity_law(positions[vid])
else:
K[vid] = lateral_axial_conductivity_law(positions[vid])
else:
K[vid] = crown_axial_conductivity_law(positions[vid])
g.properties()['K'] = K
return g
[docs]
def compute_K(g, scale_factor=1.):
"""
Compute the conductance in dimension :math:`[L^3 P^{-1} T^{-1}]` from the 'experimental' one which is
in :math:`[L^4 P^{-1} T^{-1}]`
In each vertex compute :math:`K = K_{exp} \\frac{\\text{scale_factor}}{\\text{vertex_length}}`
:param g: (MTG) - the root architecture
:param scale_factor: (float) - a factor used for sensitivity analysis (Default value = 1)
:returns:
- g (MTG) - with the property K set
"""
# Fabrice 2020-01-17: this calculation was done hydroroot.flux.run but that meant that the MTG was changed at each
# flux calculation which is not relevant the MTG properties have to be fixed
length = g.property('length')
K_exp = g.property('K_exp')
K = {}
for vid in K_exp:
K[vid] = K_exp[vid] / length[vid]
K[vid] = K[vid] * scale_factor
g.properties()['K'] = K
return g
[docs]
def poiseuille(radius, length, viscosity=1e-3): # DEPRECATED
"""
Compute a conductance of a cylindrical element based on its radius and length.
:param radius: (float)
:param length: (float)
:param viscosity: (float) (Default value = 1e-3)
The Poiseuille formula is, for a cylinder :math:`K = {\\pi r^4} / {8 \\mu l}`
with :math:`r` the radius of a pipe, :math:`\\mu` the viscosity of the liquid, :math:`l` the length of the pipe.
"""
return pi*(radius**4) / ( 8 * viscosity * length)
[docs]
def compute_k(g, k0 = 300.):
r"""Compute the radial conductance k (:math:`10^{-9}\ m^4.s^{-1}.MPa^{-1}`) of each vertex of the MTG.
.. math::
k = 2 \\pi r l k0
with l and r the segment length and radius of the vertex
- if k0 == "k0": calculation using k0 values from g.property('k0') on each vertex
- if k0 is a float: use this value in the calculation
:param g: (MTG)
:param k0: (float or string) - "k0" or the radial conductivity in :math:`10^{-9}\\ m.s^{-1}.MPa^{-1}` (Default value = 300.)
"""
#print 'entering radial k fitting'
radius = g.property('radius')
length = g.property('length')
kr={}
if k0 == 'k0':
k0 = g.property('k0')
kr = dict((vid, radius[vid] * 2 * pi * length[vid] * k0[vid]) for vid in g.vertices(scale=g.max_scale()))
else:
kr = dict((vid, radius[vid] * 2 * pi * length[vid] * k0) for vid in g.vertices(scale=g.max_scale()))
g.properties()['k'] = kr
#print 'exiting radial k fitting'
return g
def compute_K_from_Poiseuille(g, nb_xylem=5, radius_scale = 1/10.): # DEPRECATED
"""
:Deprecated:
Compute the axial conductance K in a MTG according to the Poiseuille law.
The conductance depends on the radius of each xylem pipe, the number of xylem pipes,
and on the length of a root segment.
radius_scale allows to compute the radius of a xylem pipe from the radius of a root segment.
:param g: (MTG)
:param nb_xylem: (Default value = 5)
:param radius_scale: (Default value = 1/10.)
"""
# Fabrice 2020-01-17: changed the function name from "compute_K" to "compute_K_from_Poiseuille" because this name
# is now used to calculate the real conductance in [L^3 P^(-1) T^(-1)] from the experimental one
# in [L^4 P^(-1) T^(-1)]
radius = g.property('radius_xylem')
if not radius:
full_radius = g.property('radius')
radius = dict( (vid,r*radius_scale) for vid,r in full_radius.items())
nb_xylem = g.property('nb_xylem')
length= g.property('length')
if not nb_xylem:
nb_xylem = defaultdict(lambda : 5)
K = dict((vid, nb_xylem[vid]*poiseuille(radius[vid], length[vid]))
for vid in g.vertices(scale=g.max_scale()))
g.properties()['K'] = K
return g
def fit_property(g, x, y, prop_in, prop_out, s=3.):
"""
:Deprecated:
Fit a 1D spline from x, y data and plot it
Retrieve the values from the prop_in of the MTG.
And evaluate the spline to compute the property 'prop_out'
:param g:
:param x:
:param y:
:param prop_in:
:param prop_out:
:param s: (Default value = 3.)
"""
spline = UnivariateSpline(x, y, s=s)
keys = list(g.property(prop_in).keys())
x_values = np.array(list(g.property(prop_in).values()))
y_values = spline(x_values)
g.properties()[prop_out] = dict(list(zip(keys,y_values)))
xx = np.linspace(0,1,1000)
yy = spline(xx)
pylab.clf()
pylab.plot(x, y)
pylab.plot(xx, yy)
pylab.show()
#print 'Update figure ', yy.min(), yy.max()
return g
[docs]
def fit_property_from_spline(g, spline, prop_in, prop_out):
"""compute a property from another one using a spline transformation.
Retrieve the values from the prop_in of the MTG.
And evaluate the *spline* according to *prop_in* to compute the property *prop_out*
:param g: MTG
:param spline: (class scipy)
:param prop_in: (string)
:param prop_out: (string)
:Example:
Typically, we have, as input, the axial conductance versus distance to tip K(x) given as a list of 2 lists
of few number of floats. Then we have to calculate on each vertex the axial conductance according to it.
To do that, we first fit with a spline K(x) and then use the present function to use the spline to compute
K according to the position of the vertex.
"""
#spline = UnivariateSpline(x, y, s=s)
keys = list(g.property(prop_in).keys())
x_values = np.array(list(g.property(prop_in).values()))
y_values = spline(x_values)
g.properties()[prop_out] = dict(list(zip(keys, y_values)))
return g
def fit_property_from_csv(g, csvdata, prop_in, prop_out, k=1., s=0., plot=False, direct_input=None):
"""
:Deprecated:
Fit a 1D spline from (x, y) csv extracted data or from direct input dictionary
Retrieve the values it will be applied to from the prop_in of the MTG.
And evaluate the spline to compute the property 'prop_out'
Toggle plot option to visualize the spline fit
:param g:
:param csvdata:
:param prop_in:
:param prop_out:
:param k: (Default value = 1.)
:param s: (Default value = 0.)
:param plot: (Default value = False)
:param direct_input: (Default value = None)
"""
#print 'entering K fitting'
from .read_file import readCSVFile
if isinstance(csvdata, str):
csvdata = readCSVFile(csvdata)
if direct_input is None:
x_name = csvdata.dtype.names[0]
y_name = csvdata.dtype.names[1]
x = list(csvdata[x_name])
y = list(csvdata[y_name])
else:
x, y = [], []
for key in sorted(direct_input.keys()): # dictionnary key are not ordered by default
x.append(key)
y.append(direct_input[key])
spline = UnivariateSpline(x, y, k=k, s=s)
fit_property_from_spline(g, spline, prop_in, prop_out)
if plot:
x_n = np.array(list(g.property(prop_in).values()))
#y_n = np.array(g.property(prop_out).values())
xx = np.linspace(min(x_n), max(x_n), 1000)
yy = spline(xx)
# plot the reference (x_values,y_values) data and the fitted spline
pylab.clf()
pylab.plot(x, y, 'x')
#pylab.plot(x_values, y_values)
pylab.plot(xx, yy, '-')
pylab.show()
#print 'Update figure ', xx.min(), yy.max()
#print 'exiting K fitting'
return g
def fit_K(g, s=0.): # DEPRECATED
"""
:Deprecated:
call :func:`fit_property`
:param g:
:param s: (Default value = 0.)
"""
x = np.linspace(0.,1.,100)
y = np.linspace(50, 500, 100)+100*np.random.random(100)-50
if s == 0.:
s = None
fit_property(g,x,y,'relative_position', 'K', s=s)
return g
# Below these function does not do very complicated things but are used in most of the scripts
[docs]
def radial(v = 92, acol = [], scale = 1):
"""create a list of uniform value v*scale of the same length than acol
the purpose is to return x-y data in a form of two lists
:param v: (float) (Default value = 92)
:param acol: (list) 2 lists of floats (Default value = [])
:param scale: (float) (Default value = 1)
:returns: - xr, yr (list)
:Example:
in HydroRoot the conductivity and the conductance are properties versus distance to tip. Then if in the
yaml file with parameters we give a float for k0, we must transform it to a list of 2 lists of float as the axial
conductance.
"""
xr = acol[0] # at this stage kr constant so the same x than Ka
yr = [v * scale] * len(xr)
return xr, yr
[docs]
def radial_step(kmin = 92, factor = 1.0, x_step = None, x_base = 1., dx = 1.0e-4, scale = 1.0):
"""Radial conductivity k as a step function.
k is set to its maximum value (kmin * factor) from the tip (x=0) to x_step, and its minimum value kmin from x_step + dx to x_base.
:param kmin: Float (Default value = 92)
:param factor: Float (Default value = 1.0)
:param x_step: Float (Default value = None)
:param x_base: Float (Default value = 1.)
:param dx: Float (Default value = 1.0e-4)
:param scale: Float (Default value = 1.0)
:returns: - xr: (list), list of distance from the tip
- yr: (list), list of radial k values
"""
xr = [0.0]
yr = [kmin * factor * scale]
if x_step is not None:
xr.append(x_step)
yr.append(kmin * factor * scale)
xr.append(x_step + dx)
yr.append(kmin * scale)
xr.append(x_base)
yr.append(kmin * scale)
return xr, yr
[docs]
def axial(acol = [], scale = 1):
"""the purpose is to give in arguments a set of 2 lists representing a x-y data and to return it with y*scale
:param acol: list (Default value = [])
:param scale: float (Default value = 1)
:returns: - x, y (list) - 2 lists
"""
x, y = acol
y = [a * scale for a in y]
return x, y