# -*- python -*-
#
# OpenAlea.StdLib
#
# Copyright 2006-2023 INRIA - CIRAD - INRA
#
# File author(s): Da SILVA David <david.da_silva@cirad.fr>
# BOUDON Frederic <frederic.boudon@cirad.fr>
#
# Distributed under the Cecill-C License.
# See accompanying file LICENSE.txt or copy at
# http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
# OpenAlea WebSite : http://openalea.gforge.inria.fr
#
"""Set of distributions"""
__license__ = "Cecill-C"
__revision__ = " $Id$"
from openalea.core import Node, IEnumStr, IInt, IFloat
#from scipy import stats
import random
from math import sqrt
def _testDistance(x, y, xctr, yctr, d):
"""
Helper function. Test if a given 2D point is inside any cluster centered on
points from `ctr` list with `d` as radius.
:param x: x-axis point value
:param y: y-axis point value
:param xctr: list of center coordinates as [(x1,y1) .. (xn,yn)]
:param yctr: list of center coordinates as [(x1,y1) .. (xn,yn)]
:param d: cluster radius
:type x: float
:type y: float
:type xctr: cople list
:type yctr: cople list
:type d: float
:returns: True if the point is inside a cluster, False otherwise
:rtype: boolean
"""
if len(xctr)==0:
return True
for i in range(len(xctr)):
if((x-xctr[i])**2 + (y-yctr[i])**2 <= d**2):
return True
return False
def _randomRange(min, max):
"""
Helper function. Generate a random number within a given range.
:param min: low bound of the range
:param max: high bound of the range
:type min: float
:type max: float
:returns: random value between `min` and `max`
:rtype: float
"""
return min + random.random() * (max - min)
def _minDistPtClstr(x, y, ptX, ptY):
"""
Helper function. Compute the minimal distance between a given point and
the ones represented by in the `ptX` and `ptY` lists.
:param x: x-axis point value
:param y: y-axis point value
:param ptX: list of X coordinates
:param ptY: list of Y coordinates
:type x:float
:type y:float
:type ptX:float
:type ptY:float
:returns: The minimal distance between (x,y) and a list of points
:rtype: float
"""
if len(ptX)==0:
return 1000000.0
dmin = (x-ptX[0])**2 + (y-ptY[0])**2
for i in range(len(ptX)):
dtmp = (x-ptX[i])**2 + (y-ptY[i])**2
if(dtmp <= dmin**2):
dmin = dtmp
return sqrt(dmin)
def _linearProba(dtest, d):
"""
Helper function. Simulate a linear probability from 0 (proba=0) to
d (proba=1). And test wether the `dtest` value with proba=
`dtest`/`d` is kept.
:Note: All points with `dtest` > `d` will automatically be kept
:param dtest: value to test against `d`
:param d: limit value above wich all values are ok
:type dtest: float
:type d: float
:return: True if the value is kept
:rtype: boolean
"""
if random.random() <= 1.0*dtest/d:
return True
else:
return False
[docs]
def random_distrib(n=100, xr=(0, 1), yr=(0, 1)):
assert(xr[0] < xr[1] and yr[0] < yr[1] and
" min value must be lesser than max")
x_range = xr
y_range = yr
x = [_randomRange(x_range[0], x_range[1]) for i in range(n)]
y = [_randomRange(y_range[0], y_range[1]) for i in range(n)]
return (x, y)
[docs]
def regular_distrib(n =100, xr=(0, 1), yr=(0, 1)):
"""
Simulation d'une realisation du processus regulier correspondant a une
proba lineaire fonction du rayon caracterisant l'espace disponible par
point
:param n: the number of random points to be generated
:param xr: the x range, default is [0,1]
:param yr: the y range, default is [0,1]
:type n: int
:type xr: tuple
:type yr: tuple
:returns: two lists representing coordinates
:rtype: list
"""
assert (xr[0] < xr[1] and yr[0] < yr[1] and
" min value must be lesser than max")
x_range = xr
y_range = yr
dist = sqrt((xr[1]-xr[0])*(yr[1]-yr[0]))
ptX = []
ptY = []
NbTry = 0 #count the number of try allowed to find a type2 point
while(len(ptX) < n and NbTry < 100):
xtmp = _randomRange(x_range[0], x_range[1])
ytmp = _randomRange(y_range[0], y_range[1])
NbTry += 1
min_dist = _minDistPtClstr(xtmp, ytmp, ptX, ptY)
if _linearProba(min_dist, dist):
ptX.append(xtmp)
ptY.append(ytmp)
NbTry = 0
if NbTry >= 100:
print("Impossible to get all Type2 points with required parameters")
return (ptX, ptY)
[docs]
def neman_scott__distrib(n=100, xr=(0, 1), yr=(0, 1), **kwds):
"""
simulation d'une realisation du processus de type Neman Scott
(Nb Agregats, Rayon Agregats)
"""
assert (xr[0] < xr[1] and yr[0] < yr[1] and
" min value must be lesser than max")
x_range = xr
y_range = yr
nb_cluster = kwds.get('cluster', 5)
cl_radius = kwds.get('cluster_radius', 0.1)
x_cl, y_cl = random_distrib(nb_cluster, x_range, y_range)
ptX = []
ptY = []
while(len(ptX) < n):
xtmp = _randomRange(x_range[0], x_range[1])
ytmp = _randomRange(y_range[0], y_range[1])
if _testDistance(xtmp, ytmp, x_cl, y_cl, cl_radius):
ptX.append(xtmp)
ptY.append(ytmp)
return (ptX, ptY)
[docs]
def gibbs_distrib(n=10, xr=(0, 1), yr=(0, 1)):
pass
[docs]
def spatial_distrib(n=100, xrange=(0, 1), yrange=(0, 1), type='Random',
params=None):
if type == 'Random':
return random_distrib(n, xrange, yrange)
elif type == 'Regular':
return regular_distrib(n, xrange, yrange)
elif type == 'Neman Scott':
return neman_scott__distrib(n, xrange, yrange, **params)
elif type == 'Gibbs':
pass
[docs]
def domain(xmin, xmax, ymin, ymax, scale):
return ((xmin*scale, xmax*scale), (ymin*scale, ymax*scale)),
[docs]
def random_distribution(n=10):
x = [random.random() for i in range(n)]
y = [random.random() for i in range(n)]
return x, y
[docs]
def regular_distribution(n=10):
dist = 1./n
ptX = []
ptY = []
NbTry = 0 #count the number of try allowed to find a type2 point
while(len(ptX) < n and NbTry < 100):
xtmp = random.random()
ytmp = random.random()
NbTry += 1
min_dist = _minDistPtClstr(xtmp, ytmp, ptX, ptY)
if _linearProba(min_dist, dist):
ptX.append(xtmp)
ptY.append(ytmp)
NbTry = 0
if NbTry >= 100:
print("Impossible to get all Type2 points with required parameters")
return ptX, ptY
[docs]
class basic_distrib(Node):
"""
Basic distribution(type) -> distribution func
:input: Type of the function.
:output: function that generates distribution.
"""
distr_func = {"Random": random_distribution,
"Regular": regular_distribution, }
def __init__(self):
Node.__init__(self)
funs = list(self.distr_func.keys())
funs.sort()
self.add_input(name="Type", interface=IEnumStr(funs), value=funs[0])
self.add_output(name="Distribution", interface=None)
def __call__(self, inputs):
func_name = self.get_input("Type")
f = self.distr_func[func_name]
self.set_caption(func_name)
return f
[docs]
def neman_scott__distribution(n=10, cl_nbr=2, cl_radius=0.2):
"""
simulation d'une realisation du processus de type Neman Scott
(Nb Agregats, Rayon Agregats)
"""
x_cl, y_cl = random_distrib(cl_nbr)
ptX = []
ptY = []
while(len(ptX) < n):
xtmp = random.random()
ytmp = random.random()
if _testDistance(xtmp, ytmp, x_cl, y_cl, cl_radius):
ptX.append(xtmp)
ptY.append(ytmp)
return ptX, ptY
[docs]
class aggregative_distrib(Node):
"""
Basic distribution(type) -> distribution func
.. :param input: Type of the function.
.. :param output: function that generates distribution.
"""
distr_func= {"NemanScott": neman_scott__distribution, }
def __init__(self):
Node.__init__(self)
funs = list(self.distr_func.keys())
funs.sort()
self.add_input(name="Type", interface=IEnumStr(funs), value=funs[0])
self.add_input(name="Cluster number", interface=IInt(min=1), value=2)
self.add_input(name="Cluster radius", interface=IFloat(0.01, 1, 0.01),
value=0.2)
self.add_output(name="Distribution", interface=None)
def __call__(self, inputs):
func_name = self.get_input("Type")
cluster_nb = self.get_input("Cluster number")
cluster_rd = self.get_input("Cluster radius")
f = self.distr_func[func_name]
self.set_caption(func_name)
return lambda n: neman_scott__distribution(n, cluster_nb, cluster_rd)
[docs]
def scale(seq, min, max):
mm = max-min
return [min+x*mm for x in seq]
[docs]
def random2D(n, distrib_func, domain):
if not distrib_func:
return
x, y = distrib_func(n)
if domain:
(xmin, xmax), (ymin, ymax) = domain
x = scale(x, xmin, xmax)
y = scale(y, ymin, ymax)
return x, y