Exemplo n.º 1
0
    def __init__(self, birthdate=None, id=None, min_radius=0.001, latency=6, 
                 store_data=True):
        """**Constructor**

        :param datetime.datetime birthdate:
        :param int id:
        :param float min_radius: in meters
        :param int store_data:
        :param float latency: number of days of latency before stopping the gu.

        :attributes:
            * :attr:`length`: total length of the growth unit in meters
            * :attr:`radius`:  radius of at the base of the growth unit (see plants module)
            * those inherited by :class:`~openalea.plantik.biotik.component.ComponentInterface`:
                * :attr:`age`
                * :attr:`demand`
                * :attr:`birthdate`
                * :attr:`state` set to 'growing' by default in the constructor. Then, the :meth:`update`
                  may set to it to 'stopped' if no changement has been done for a duration> :attr:`latency`.
            * :attr:`internode_counter`: number of internodes in the growth unit
            * :attr:`variables` is a :class:`CollectionVariables` instance containing the :attr:`age`,
              :attr:`radius` and :attr:`length` at each time step



        .. note:: when creating a gu, :attr:`state` is by definition set 
            to 'growing'.
        """
        self.context = Context()
        # when creating a gu, it is by definition in a growing state.
        ComponentInterface.__init__(self, label='GrowthUnit', 
                                    birthdate=birthdate, id=id, state='growing')

        self.store_data = store_data

        self._length = 0.
        self._radius = min_radius
        self._latency = latency
        self._internode_counter = 0.

        self.variables = CollectionVariables()
        self.variables.add(SingleVariable(name='age', unit='days', 
                                          values=[self.age.days]))
        self.variables.add(SingleVariable(name='length', unit='meters', 
                                          values=[self.length]))
        self.variables.add(SingleVariable(name='radius', unit='meters', 
                                          values=[self.radius]))

        self.__step_without_growing = 0
Exemplo n.º 2
0
    def __init__(self,
                 time_step,
                 options=None,
                 revision=None,
                 pipe_fraction=1.,
                 filename=None,
                 tag=None):
        """**Constructor**

        :param float time_step: the time step of the simulation.
        :param options: a variable containing the simulation options from the config 
            file (see :class:`~vplants.plantik.tools.config.ConfigParams`)
        :param str revision: a SVN revision for book keeping.
        :param float pipe_fraction:
        :param str filename:  if None, populates attribute :attr:`filename` with 'plant'
        :param str tag:  append to the filename if not None

        :param pipe_fraction: cost of the pipe model is metamer_growth volume times the
          pipe_fraction parameter

            >>> from vplants.plantik import get_shared_data
            >>> from vplants.plantik.tools import  ConfigParams
            >>> from vplants.plantik.biotik import Plant
            >>> options = ConfigParams(get_shared_data('pruning.ini'))
            >>> plant = Plant(1, options, revision=1, tag='test')
            >>> plant.filename
            'plant_test'
            >>> plant.dt
            1


        :Attributes:

            * :attr:`revision` :
            * :attr:`options`  :
            * :attr:`time`  :
            * :attr:`filename`:
            * :attr:`mtg`:
            * :attr:`lstring`: used to store the lstring of an lsystem
            * :attr:`dt`:
            * :attr:`mtgtools`: an attribute of type :class:`~vplants.plantik.tools.mtgtools.MTGTools` that is used
              to store the mtg and retrieve many relevant information. It also has a DB facility.
            * :attr:`variables`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the `pipe_ratio`
              variable. pipe_ratio contains is the ratio of resource attributed to the pipe model over time.
            * :attr:`counter`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the count of 
              leaves/internodes/apices/growth units/branches over time. See also :meth:`plot_counter` and :meth:`update_counter`.
            * :attr:`DARC`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the DARC values. 
              See also :meth:`plot_DARC` and :meth:`update_DARC`. DARC stands for Demand, Allocated, Resource, Cost


        .. todo:: clean up all other variables that could be extracted from the lstring ?
        .. todo:: difference pipe_ratio pipe_fraction
        .. todo::   duration,        apex,        all,        dv 
        """

        # RESERVE related
        try:
            self.reserve_duration = options.reserve.duration  # used for the reserve sigmoid
            self.reserve_starting_time = options.reserve.starting_time  # used for the reserve sigmoid
            assert options.reserve.alpha >= 0 and options.reserve.alpha <= 1, 'options reserve.alpha must be in [0,1] in ini file.'
            self.reserve_alpha = options.reserve.alpha
            self.verbose = options.general.verbose
        except:
            self.reserve_duration = 210
            self.reserve_starting_time = 105
            self.reserve_alpha = 1
            self.verbose = True
        self.Reserve = 0

        #FILENAME
        if filename != None: assert type(filename) == str
        if tag != None: assert type(tag) == str

        self._filename = self._set_filename(filename, tag)
        self._revision = revision
        self._options = options
        self._time = []
        self.age = 0
        self._mtg = None
        self._lstring = None
        self.label = 'Plant'
        # could be retrieved from options parameter but let us try to not depends on options !
        self._dt = time_step

        #!! when calling self.mtg, what you really do is self.mtgtools.mtg
        # !!when doing self.mtg = a, what you really do is self.mtgtools.mtg = a
        self.mtgtools = MTGTools(verbose=self.verbose)

        self.D = 0
        try:
            self.R = options.root.initial_resource
        except:
            self.R = 1
        self.C = 0
        self.A = 0
        self.allocated = []

        self.duration = 0  # used to store the duration of the simulation
        self.apex = {
            'demand': [],
            'allocated': [],
            'height': [],
            'age': []
        }  # keeps track of the main apex characteristics
        self.all = {'age': [], 'order': [], 'height': []}

        #: expected increase of volume related to the pipe model.
        self.dV = 0

        #PIPE FRACTION
        assert pipe_fraction <= 1 and pipe_fraction >= 0
        self.pipe_fraction = pipe_fraction

        self.radius = 0

        #: extract storeage for variables over time inluding :attr:`pipe_ratio` and :attr:`dV`.
        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='pipe_ratio', unit='ratio', values=[]))
        self.variables.add(SingleVariable(name='dV', unit='ratio'))
        self.variables.add(SingleVariable(name='reserve', unit='UR'))

        self.year = 1

        self._reserve_function = GrowthFunction(
            0,
            1,
            maturation=(self.year) * self.reserve_duration -
            self.reserve_starting_time - (self.year - 1) * 365,
            growth_rate=0.1,
            growth_function='sigmoid',
            nu=1)

        #: a CollectionVariable instance that containes the count of
        # **apices**, **internodes**, **leaves**
        #: **branches** and growth units (denoted **gus**) at each time step.
        #For instance, to acces to apices counter::
        #:
        #: plant.counter.apices.values
        self.counter = CollectionVariables()
        self.counter.add(SingleVariable(name='apices', unit=r'#'))
        self.counter.add(SingleVariable(name='internodes', unit=r'#'))
        self.counter.add(SingleVariable(name='leaves', unit=r'#'))
        self.counter.add(SingleVariable(name='branches', unit=r'#'))
        self.counter.add(SingleVariable(name='gus', unit=r'#'))

        #: a CollectionVariable instance that contains the demand (D), allocated resources(R),
        #: resources (R) and cost (C) at each time step. To acces to demand over time::
        #:
        #: >>> plant.DARC.D.values
        self.DARC = CollectionVariables()
        self.DARC.add(SingleVariable(name='D', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='A', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='R', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='C', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='pipe_cost', unit='biomass unit'))
Exemplo n.º 3
0
class GrowthUnit(ComponentInterface):
    """Specialised version of :class:`~openalea.plantik.biotik.component.ComponentInterface`
    dedicated to GrowthUnits.

    GrowthUnit class does not compute anything special, it mainly serves as 
    storage for various information. The update of the length and radius is 
    made in the :mod:`~openalea.plantik.biotik.plant` module with the
    method :meth:`~openalea.plantik.biotik.plant.Plant.growth_unit_update`

    :Example:

    >>> from vplants.plantik.biotik.growthunit import *
    >>> gu = GrowthUnit()
    >>> gu.radius
    0.001
    >>> gu.variables.radius.values
    [0.001]

    :plotting:

    If the store_data is True, the you can plot results either from the class instance :meth:`plot`
    of from the variables stored in :attr:`variables`. The former being less flexible with only `plot` of
    radius versus age, length versus age, length versus radius. And the latter provides plot of variable
    versus age (the same as before) as well as histograms.

    :Notation:

    * age of a growth unit is denoted :math:`t_a^{(gu)}`

    .. csv-table:: **Notation related to** :class:`~openalea.plantik.biotik.growthunit.GrowthUnit`
        :header: Name, symbol, default value, type
        :widths: 15,20,20, 20
        
        radius_min     , :math:`r^{(gu)}_{min}`    ,  0.001 (same as internode min radius), user parameter
        radius         , :math:`r^{(gu)}`          ,  -                             , attribute
        length         , :math:`l^{(gu)}`          ,    length of the growth unit    ,attribute
        # internode    , :math:`N_{(i)}^{(gu)}`    , 0 by default                    ,attribute
        latency        , :math:`T^{(gu)}_{latency}`, default is 6 days                , user parameter
        

    """
    def __init__(self, birthdate=None, id=None, min_radius=0.001, latency=6, 
                 store_data=True):
        """**Constructor**

        :param datetime.datetime birthdate:
        :param int id:
        :param float min_radius: in meters
        :param int store_data:
        :param float latency: number of days of latency before stopping the gu.

        :attributes:
            * :attr:`length`: total length of the growth unit in meters
            * :attr:`radius`:  radius of at the base of the growth unit (see plants module)
            * those inherited by :class:`~openalea.plantik.biotik.component.ComponentInterface`:
                * :attr:`age`
                * :attr:`demand`
                * :attr:`birthdate`
                * :attr:`state` set to 'growing' by default in the constructor. Then, the :meth:`update`
                  may set to it to 'stopped' if no changement has been done for a duration> :attr:`latency`.
            * :attr:`internode_counter`: number of internodes in the growth unit
            * :attr:`variables` is a :class:`CollectionVariables` instance containing the :attr:`age`,
              :attr:`radius` and :attr:`length` at each time step



        .. note:: when creating a gu, :attr:`state` is by definition set 
            to 'growing'.
        """
        self.context = Context()
        # when creating a gu, it is by definition in a growing state.
        ComponentInterface.__init__(self, label='GrowthUnit', 
                                    birthdate=birthdate, id=id, state='growing')

        self.store_data = store_data

        self._length = 0.
        self._radius = min_radius
        self._latency = latency
        self._internode_counter = 0.

        self.variables = CollectionVariables()
        self.variables.add(SingleVariable(name='age', unit='days', 
                                          values=[self.age.days]))
        self.variables.add(SingleVariable(name='length', unit='meters', 
                                          values=[self.length]))
        self.variables.add(SingleVariable(name='radius', unit='meters', 
                                          values=[self.radius]))

        self.__step_without_growing = 0

    def _getInternode(self):
        return self._internode_counter
    def _setInternode(self, value):
        prev = self._internode_counter
        self._internode_counter = value
        if self._internode_counter == prev:
            self.__step_without_growing += 1
        else:
            self.__step_without_growing = 0
    internode_counter = property(_getInternode, _setInternode, None, 
                                 "getter/setter to the GU radius")

    def _getRadius(self):
        return self._radius
    def _setRadius(self, value):
        if value< self._radius:
            raise ValueError("radius decreased in GU update!!")
        self._radius = value
    radius = property(_getRadius, _setRadius, None, 
            doc="getter/setter to the growth unit radius. Cannot decrease !")

    def _getLength(self):
        return self._length
    def _setLength(self, value):
        self._length = value
    length = property(_getLength, _setLength, None, "getter/setter to the GU length")

    def _getLatency(self):
        return self._latency
    latency = property(_getLatency, None, None, "getter to the GU latency")



    def update(self, dt):
        """Update the GU characteristics at each time step

        This function updates the :attr:`age` of the component by **dt**
        
        Moreover, if **store_data** is True, the age, length and radius 
        :attr:`variables` are also stored at each time step as long as 
        the growth unit state is *growing*, which is True when 
        the attribute :attr:`__step_without_growing` times :math:`\Delta t`
        is greater than the latency :math:`T_{latency}^{(gu)}`
          
        Tasks: 
        
        #. increment age by dt
        #. calls :meth:`demandCalculation`, :meth:`resourceCalculation` and 
           :meth:`computeLivingCost`
        #. store age into variables.age
        #. store radius into variables.radius
        #. store length into variables.length


        :param float dt: in days 

        
        """
        super(GrowthUnit, self).update(dt)
        self.demandCalculation()
        self.resourceCalculation()
        if self.store_data is True and self.state == 'growing':
            self.variables.age.append(self.age.days)
            self.variables.length.append(self.length)
            self.variables.radius.append(self.radius)
        if self.__step_without_growing * dt > self.latency:
            self.state = 'stopped'


    def demandCalculation(self, **kargs):
        """no demand for a gu (i.e., zero)"""
        pass

    def resourceCalculation(self, **kargs):
        """no resource for a gu (i.e., zero)"""
        pass

    def plot(self, variables=['radius', 'length'], show=True, grid=True, **args):
        """plot some results

        :param list variables: plot results related to the variables provided
        :param bool show: create but do not show the plot (useful for test, saving)
        :param  args: any parameters that pylab.plot would accept.

        .. plot::
            :width: 30%
            :include-source:

            from vplants.plantik.biotik.growthunit import *
            b = GrowthUnit()
            for v in range(1,100):
                b.radius = (v*0.001)**0.5
                b.length = v*0.01
                if v>50:
                    b.state = 'stopped'
                b.update(1)
            b.plot('radius')

        """
        self.variables.plot(variables=variables, show=show, grid=grid, **args)


    def __str__(self):
        from vplants.plantik.tools.misc import title
        res = super(GrowthUnit, self).__str__()
        res += self.context.__str__()
        res += self.variables.__str__()
        res += title('other growth unit attributes')
        res += '\n'
        for name in self.variables.keys():
            res += "%s = %s" % (name, getattr(self, name))
            res += '\n'

        return res
Exemplo n.º 4
0
    def __init__(self,
                 resource_per_day,
                 birthdate=None,
                 id=None,
                 maturation=21,
                 internode_vigor=1.,
                 livingcost=0.,
                 growth_rate=1,
                 area_optimal=30 * 0.01 * 0.01,
                 growth_function='sigmoid',
                 efficiency_method='unity',
                 store_data=False,
                 nu=1,
                 t1=15,
                 t2=150,
                 angle=0.):
        """**Constructor**

        

        :param float resource_per_day: stricly positive , notation :math:`r_0`. 

        :param datetime.datetime birthdate:
        :param int id:
        :param float maturation: leaf maturation in days
        :param float internode_vigor: a leaf area is proportional to the internode length. 
            intenode_vigor of 1 means the internode had full length and therefore leaf has
            a potential for maximum area as well.
        :param float livingcost: zero by default
        :param float growth_rate: the :math:`lambda` parameter of the growth function
        :param str growth_function: a GrowthFunction method in ['linear', 'sigmoid', 'logistic']
        :param nu: shape of the logistic function is :attr:`growth_function` is logisitic
        :param efficiency_method: 'unity' or 'sigmoid' (default is unity)
        :param int store_data:
        :param float t1: parameter of the leaf_efficiency method
        :param float t2: parameter of the leaf_efficiency method
        
        
        :attributes:

            * :attr:`area`: the leaf area, a :meth:`~vplants.plantik.biotik.growth.GrowthFunction` 
              instance (read-only). The input parameters **growth_function**, **growth_rate**, **nu**,
              and **maturation** are used by this function.
            * Those inherited by :class:`~vplants.plantik.biotik.component.ComponentInterface`: 
              :attr:`age`,
              :attr:`allocated`,
              :attr:`demand`,
              :attr:`livingcost`,
              :attr:`~vplants.plantik.biotik.component.ComponentInterface.resource`.
            * :attr:`variables` is a :class:`CollectionVariables` instance containing the :attr:`age`, 
              :attr:`demand`, :attr:`resource`, :attr:`area`.
        """
        assert internode_vigor >= 0 and internode_vigor <= 1, \
            "internode vifor must be in [0,1]"
        assert resource_per_day > 0, \
            'resource_per_day must be positive otherwise noting will happen...'

        self.context = Context()
        ComponentInterface.__init__(self,
                                    label='Leaf',
                                    birthdate=birthdate,
                                    id=id,
                                    state='growing')

        # leaf min must correspond to internode length min so that
        # leafmaxarea>-leafminarea
        self._radius = Leaf.petiole_radius  # for geometry purpose only

        # set the area growth function
        self.area_max = Leaf.area_max * internode_vigor
        self._area = GrowthFunction(Leaf.area_min,
                                    self.area_max,
                                    maturation=maturation,
                                    growth_rate=growth_rate,
                                    growth_function=growth_function,
                                    nu=nu)
        self._r0 = resource_per_day
        self.angle = angle

        # used for bookeeping only
        self.internode_vigor = internode_vigor  # in %
        self.maturation = maturation  # in days

        # used by update()
        self.store_data = store_data

        # some variables to store.
        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='age', unit='days', values=[self.age.days]))
        self.variables.add(
            SingleVariable(name='resource',
                           unit='biomass unit',
                           values=[self.resource]))
        self.variables.add(
            SingleVariable(name='demand',
                           unit='biomass unit',
                           values=[self.demand]))
        self.variables.add(
            SingleVariable(name='area',
                           unit='square meters',
                           values=[self.area]))

        # parameters
        self.livingcost = livingcost  # cost to maintain leaf alive !!
        self.efficiency_method = efficiency_method

        # other attributes.
        # radius used by the interpretation of the lsystem.
        #todo: move it elsewhere outsitde this class.
        self.father_radius = 0

        #others
        self.lg = 0.  # light interception

        # related to leaf efficiency
        self._leaf_efficiency = 1.
        self.t1 = t1
        self.t2 = t2
        self.growth_rate = growth_rate
Exemplo n.º 5
0
class Plant(object):
    """A Plant Factory to store and compute various information within a simulation


    This class should be used as the first module of an axialtree/lstring/mtg.

    It is used by the reactive model to

        #. store various information such as the simulation options and revision.
        #. compute the **DARC** numbers at each time step
        #. compute the pipe model cost at each time step
        #. store MTG/lstring
        #. provide facilities to extract informtion from lstring/mtg

    """
    def __init__(self,
                 time_step,
                 options=None,
                 revision=None,
                 pipe_fraction=1.,
                 filename=None,
                 tag=None):
        """**Constructor**

        :param float time_step: the time step of the simulation.
        :param options: a variable containing the simulation options from the config 
            file (see :class:`~vplants.plantik.tools.config.ConfigParams`)
        :param str revision: a SVN revision for book keeping.
        :param float pipe_fraction:
        :param str filename:  if None, populates attribute :attr:`filename` with 'plant'
        :param str tag:  append to the filename if not None

        :param pipe_fraction: cost of the pipe model is metamer_growth volume times the
          pipe_fraction parameter

            >>> from vplants.plantik import get_shared_data
            >>> from vplants.plantik.tools import  ConfigParams
            >>> from vplants.plantik.biotik import Plant
            >>> options = ConfigParams(get_shared_data('pruning.ini'))
            >>> plant = Plant(1, options, revision=1, tag='test')
            >>> plant.filename
            'plant_test'
            >>> plant.dt
            1


        :Attributes:

            * :attr:`revision` :
            * :attr:`options`  :
            * :attr:`time`  :
            * :attr:`filename`:
            * :attr:`mtg`:
            * :attr:`lstring`: used to store the lstring of an lsystem
            * :attr:`dt`:
            * :attr:`mtgtools`: an attribute of type :class:`~vplants.plantik.tools.mtgtools.MTGTools` that is used
              to store the mtg and retrieve many relevant information. It also has a DB facility.
            * :attr:`variables`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the `pipe_ratio`
              variable. pipe_ratio contains is the ratio of resource attributed to the pipe model over time.
            * :attr:`counter`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the count of 
              leaves/internodes/apices/growth units/branches over time. See also :meth:`plot_counter` and :meth:`update_counter`.
            * :attr:`DARC`: a :class:`~vplants.plantik.biotik.collection.CollectionVariables` instance containing the DARC values. 
              See also :meth:`plot_DARC` and :meth:`update_DARC`. DARC stands for Demand, Allocated, Resource, Cost


        .. todo:: clean up all other variables that could be extracted from the lstring ?
        .. todo:: difference pipe_ratio pipe_fraction
        .. todo::   duration,        apex,        all,        dv 
        """

        # RESERVE related
        try:
            self.reserve_duration = options.reserve.duration  # used for the reserve sigmoid
            self.reserve_starting_time = options.reserve.starting_time  # used for the reserve sigmoid
            assert options.reserve.alpha >= 0 and options.reserve.alpha <= 1, 'options reserve.alpha must be in [0,1] in ini file.'
            self.reserve_alpha = options.reserve.alpha
            self.verbose = options.general.verbose
        except:
            self.reserve_duration = 210
            self.reserve_starting_time = 105
            self.reserve_alpha = 1
            self.verbose = True
        self.Reserve = 0

        #FILENAME
        if filename != None: assert type(filename) == str
        if tag != None: assert type(tag) == str

        self._filename = self._set_filename(filename, tag)
        self._revision = revision
        self._options = options
        self._time = []
        self.age = 0
        self._mtg = None
        self._lstring = None
        self.label = 'Plant'
        # could be retrieved from options parameter but let us try to not depends on options !
        self._dt = time_step

        #!! when calling self.mtg, what you really do is self.mtgtools.mtg
        # !!when doing self.mtg = a, what you really do is self.mtgtools.mtg = a
        self.mtgtools = MTGTools(verbose=self.verbose)

        self.D = 0
        try:
            self.R = options.root.initial_resource
        except:
            self.R = 1
        self.C = 0
        self.A = 0
        self.allocated = []

        self.duration = 0  # used to store the duration of the simulation
        self.apex = {
            'demand': [],
            'allocated': [],
            'height': [],
            'age': []
        }  # keeps track of the main apex characteristics
        self.all = {'age': [], 'order': [], 'height': []}

        #: expected increase of volume related to the pipe model.
        self.dV = 0

        #PIPE FRACTION
        assert pipe_fraction <= 1 and pipe_fraction >= 0
        self.pipe_fraction = pipe_fraction

        self.radius = 0

        #: extract storeage for variables over time inluding :attr:`pipe_ratio` and :attr:`dV`.
        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='pipe_ratio', unit='ratio', values=[]))
        self.variables.add(SingleVariable(name='dV', unit='ratio'))
        self.variables.add(SingleVariable(name='reserve', unit='UR'))

        self.year = 1

        self._reserve_function = GrowthFunction(
            0,
            1,
            maturation=(self.year) * self.reserve_duration -
            self.reserve_starting_time - (self.year - 1) * 365,
            growth_rate=0.1,
            growth_function='sigmoid',
            nu=1)

        #: a CollectionVariable instance that containes the count of
        # **apices**, **internodes**, **leaves**
        #: **branches** and growth units (denoted **gus**) at each time step.
        #For instance, to acces to apices counter::
        #:
        #: plant.counter.apices.values
        self.counter = CollectionVariables()
        self.counter.add(SingleVariable(name='apices', unit=r'#'))
        self.counter.add(SingleVariable(name='internodes', unit=r'#'))
        self.counter.add(SingleVariable(name='leaves', unit=r'#'))
        self.counter.add(SingleVariable(name='branches', unit=r'#'))
        self.counter.add(SingleVariable(name='gus', unit=r'#'))

        #: a CollectionVariable instance that contains the demand (D), allocated resources(R),
        #: resources (R) and cost (C) at each time step. To acces to demand over time::
        #:
        #: >>> plant.DARC.D.values
        self.DARC = CollectionVariables()
        self.DARC.add(SingleVariable(name='D', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='A', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='R', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='C', unit='biomass unit', values=[]))
        self.DARC.add(SingleVariable(name='pipe_cost', unit='biomass unit'))

    def __str__(self):
        res = "R:" + str(self.R) + "\n"
        res += "D:" + str(self.D) + "\n"
        res += "C:" + str(self.C) + "\n"
        return res

    def plot_PARC(self,
                  show=True,
                  normalised=True,
                  savefig=False,
                  num_fig=1,
                  width=1,
                  linewidth=1):
        """plotting dedicated to the :attr:`PARC` attribute.

        where P stand for pipe cost

        :param bool show: 
        :param bool savefig: 
        :param bool normalised: normalise the quantity D, A, R, C by R (total resource)

        .. todo: this doc to be cleaned up

        .. plot::
            :include-source:
            :width: 50%

            from vplants.plantik import *
            options = ConfigParams(get_shared_data('pruning.ini'))
            plant = Plant(1, options)
            for x in range(100):
                plant.DARC.D.append(0.25)
                plant.DARC.A.append(0.25)
                plant.DARC.R.append(1)
                plant.DARC.C.append(0.25)
                plant.DARC.pipe_cost.append(0.5)
                plant._time.append(x)
            plant.plot_PARC()


        """
        from pylab import bar, hold, legend, title, figure, clf, xlabel, plot, ylabel
        import numpy

        T = numpy.array(self.time)
        D = numpy.array(self.DARC.D.values)
        A = numpy.array(self.DARC.A.values)
        Rn = numpy.array(self.DARC.R.values)
        C = numpy.array(self.DARC.C.values)
        Reserve = numpy.array(self.variables.reserve.values)
        if normalised == False:
            R = Rn / Rn
        else:
            R = Rn
        pipe = numpy.array(self.DARC.pipe_cost.values)
        figure(num_fig)
        clf()
        bar(T,
            A / R,
            label='Primary growth, A',
            width=width,
            linewidth=linewidth)
        hold(True)
        bar(T,
            C / R,
            bottom=A / R,
            label='Living cost, C',
            color='r',
            width=width,
            linewidth=linewidth)
        bar(T, (pipe / R),
            bottom=(C + A) / R,
            color='g',
            label='Secondary growth',
            width=width,
            linewidth=linewidth)
        bar(T, (Reserve / R),
            bottom=(C + A + pipe) / R,
            color='y',
            label='Reserve',
            width=width,
            linewidth=linewidth)
        plot(T, Rn, color='k', label='Resource, R', linewidth=2)
        plot(T, D, color='k', label='Demand, D', linewidth=1, linestyle='--')
        legend(loc='best')
        xlabel('Time (days)')
        ylabel('Unit Resource')
        title("Proportion of allocation, pipe cost and living cost")
        if show is True:
            from pylab import show as myshow
            myshow()

    def plot_DARC(self, show=True, normalised=False, savefig=False, num_fig=1):
        """plotting dedicated to the :attr:`DARC` attribute.

        :param bool show: 
        :param bool savefig: 
        :param bool normalised: normalise the quantity D, A, R, C by R (total resource)

        .. plot::
            :include-source:
            :width: 50%

            from vplants.plantik.biotik.plant import *
            from vplants.plantik import get_shared_data, ConfigParams
            options = ConfigParams(get_shared_data('pruning.ini'))
            plant = Plant(1,options)
            for x in range(100):
                plant.DARC.D.append(x**0.5)
                plant.DARC.A.append(x**0.3)
                plant.DARC.R.append(x**0.4)
                plant.DARC.C.append(x**0.3)
                plant.DARC.pipe_cost.append(x**0.3)
                plant._time.append(x)
            plant.plot_DARC()

        """
        import pylab
        import numpy
        pylab.rcParams['text.usetex'] = True

        fig = pylab.figure(num_fig)
        pylab.clf()
        if normalised == True:
            norm = numpy.array(self.R)
        else:
            norm = 1.
        T = numpy.array(self.time)
        D = numpy.array(self.DARC.D.values)
        A = numpy.array(self.DARC.A.values)
        R = numpy.array(self.DARC.R.values)
        C = numpy.array(self.DARC.C.values)
        try:
            pipe = numpy.array(self.DARC.pipe_cost.values)
        except:
            pass

        pylab.plot(T, D / norm, '-ob', label="Total demand")
        pylab.hold(True)
        pylab.plot(T, A / norm, '-.sr', label="Allocation cost")
        pylab.plot(T, R / norm, '-k', linewidth=2, label='Total resource')
        pylab.plot(T,
                   C / norm,
                   '-D',
                   color='magenta',
                   markerfacecolor=None,
                   markersize=12,
                   label='Total cost')
        pylab.plot(T, pipe / norm, '-og', label='Pipe model cost')
        try:
            pylab.plot(T, (C + A + pipe) / norm,
                       '-square',
                       markersize=15,
                       label='Total cost + allocation + pipe\_model cost')
        except:
            pass
        pylab.xlabel('Time (days)')
        pylab.ylabel(r'\#')
        pylab.legend(loc='best')
        pylab.grid(True)
        if show: pylab.show()
        if savefig: pylab.savefig(self.filename + "_DARC.png")

    def plot_counter(self, show=True, savefig=False, num_fig=1):
        """ plotting dedicated to counter

        :param bool show: 
        :param bool savefig: 

        .. plot::
            :include-source:
            :width: 50%

            from vplants.plantik.biotik.plant import *
            from vplants.plantik import get_shared_data, ConfigParams
            options = ConfigParams(get_shared_data('pruning.ini'))
            plant = Plant(1,options)
            for x in range(100):
                plant.counter.apices.append(x**0.5)
                plant.counter.leaves.append(x**0.35)
                plant.counter.internodes.append(x**0.4)
                plant.counter.branches.append(x**0.3)
                plant._time.append(x)
            plant.plot_counter()

        """
        import pylab
        pylab.rcParams['text.usetex'] = True
        fig = pylab.figure(num_fig)
        pylab.clf()

        pylab.semilogy(self.time,
                       self.counter.apices.values,
                       label='Apex number')
        pylab.hold(True)
        try:
            pylab.semilogy(self.time,
                           self.counter.internodes.values,
                           label='Internode number')
        except:
            pass
        try:
            pylab.semilogy(self.time,
                           self.counter.leaves.values,
                           label='Leaves number')
        except:
            pass
        try:
            pylab.semilogy(self.time,
                           self.counter.branches.values,
                           label='branches')
        except:
            pass
        try:
            pylab.semilogy(self.time, self.counter.gus.values, label='gu')
        except:
            pass
        pylab.xlabel('Time (days)')
        pylab.ylabel(r'\#')
        pylab.legend(loc='best')
        pylab.grid(True)
        if show: pylab.show()
        if savefig: pylab.savefig(self.filename + "_counter.png")

    def plot(self, normalised=False, show=True, savefig=False):
        """calls all plotting methods

        calls :meth:`plot_counter()`, :meth:`plot_DARC()` and :meth:`plot_PARC()`.

        many more plots can be found in :attr:`mtgtools` attribute
        """
        self.plot_counter(num_fig=1, savefig=savefig, show=show)
        self.plot_DARC(normalised=normalised,
                       show=show,
                       num_fig=2,
                       savefig=savefig)
        self.plot_PARC(normalised=normalised,
                       show=show,
                       num_fig=3,
                       savefig=savefig)

    def update_counter(self, lstring):
        """Parse the lstring attribute and count the number of elements to update the :attr:`counter` attribute.

        .. warning:: you should use the :meth:`update` to call this function. 
            Indeed, if you only call this function, other variables such as the
            :attr:`time` will not be updated at the same time leading to future
            errors in plotting for instance.!!

        """
        #import inspect
        #if inspect.stack()[1][3] != 'update':
        #    import warnings
        #    warnings.warn('You should not call Plant.update_counter
        #    directly but via the update() method. See docstring.')
        self.counter.apices.append(lstring.count('A'))
        self.counter.internodes.append(lstring.count('I'))
        self.counter.leaves.append(lstring.count('L'))
        self.counter.branches.append(lstring.count('B'))
        self.counter.gus.append(lstring.count('U'))

    def update_DARC(self, D, R, C):
        """Update the :attr:`DARC` attribute given D, A, R, C values

        .. todo:: include A in the parametr list.
        
        .. warning:: you should use the :meth:`update` to call this function. 
          Indeed, if you only call this function, other variables such as the
          :attr:`time` will not be updated at the same time leading to future
          errors in plotting for instance.!!
        """
        self.DARC.D.append(D)
        self.DARC.R.append(R)
        self.DARC.C.append(C)

    def update(self, time_elapsed, lstring, fast=True, dvmin=0.1):
        """Main core of the plant modelling to cionpute the DARC values at each step 

        plus the pipe model cost

        :param bool fast: if True, branch_update and growth_update are not 
            called (save about 20% of CPU).

        calls :meth:`update_DARC` and :meth:`update_counter` methods

        .. todo:: this documentation
        """
        self.age += self.dt

        self.mtgtools.set_order_path_rank()
        self.mtgtools.distance_to_apex_and_order_reassignment()

        #reset total demand
        self.D = 0.
        if self.options.misc.reset_resource:
            self.R = 0.

        #if self.Reserve > 0 and (self.age -self.year*365) < self.reserve_starting_time:
        #    _reserve = min(2, self.Reserve)
        #    self.R += _reserve
        #    self.Reserve -= _reserve
        # reset total cost
        self.C = 0.

        self.dV = 0.
        for elt in lstring:
            if elt.name in ['R', 'A', 'I', 'L']:
                self.C += elt[0].livingcost
                self.D += elt[0].demandCalculation(
                    context=self.options.context.model,
                    order_coeff=self.options.context.order_coeff,
                    height_coeff=self.options.context.height_coeff,
                    rank_coeff=self.options.context.rank_coeff,
                    d2a_coeff=self.options.context.d2a_coeff,
                    vigor_coeff=self.options.context.vigor_coeff,
                    age_coeff=self.options.context.age_coeff)
                self.R += elt[0].resourceCalculation()
            if elt.name in ['I']:
                self.dV += elt[0].dvolume / (1. / elt[0].cost_per_metamer)
        self.D *= self.dt
        self.R *= self.dt
        self.C *= self.dt

        # does not cost anything to check that dV is positive
        assert self.dV >= 0, self.dV

        self.update_DARC(self.D, self.R, self.C)

        # First sink processus:living cost ----------------------------------------------
        # substract the living cost from the total resource.
        if self.C > self.R:
            self.R = 0.
            #self.Reserve -= self.C
        else:
            self.R -= self.C

        # Second sink: Reserve
        # the compute_reserve function should return a value less than R, so self.R must be >0
        self.compute_reserve(alpha=self.reserve_alpha)

        # third sink is the pipe model ---------------------------------------------------
        self.variables.dV.append(self.dV)

        # let us compute the amount of dv that will be indeed allocated.
        # Given that we want to use at maximum the amount R*pipe_fraction.
        if self.R >= 0:
            dv_a = min(self.R * self.pipe_fraction, self.dV)
            assert dv_a >= 0 and dv_a <= self.R * self.pipe_fraction and dv_a <= self.dV
        else:
            dv_a = min(self.Reserve * self.pipe_fraction, self.dV)
            assert dv_a >= 0 and dv_a <= self.Reserve * self.pipe_fraction and dv_a <= self.dV

        # So, the pipe_ratio that is fulfilled
        if dv_a != 0:
            pipe_ratio = dv_a / self.dV
        else:
            pipe_ratio = 0.
        assert pipe_ratio >= 0 and pipe_ratio <= 1.

        self.DARC.pipe_cost.append(dv_a)

        cost_per_dv = 1.
        if self.R >= 0:
            self.R -= dv_a * cost_per_dv
        else:
            self.Reserve -= dv_a * cost_per_dv

        self.variables.pipe_ratio.append(pipe_ratio)

        #print 'new R=', self.R
        for elt in lstring:
            if elt.name in ['I']:
                # pipe_ratio**2 since v=2.pi r^2
                elt[0].radius += (elt[0]._target_radius -
                                  elt[0].radius) * pipe_ratio**2.

        self._time.append(time_elapsed)
        if time_elapsed == 365:
            print '###################3'
            self.new_season()

        self.update_counter(lstring)
        self.branch_update(fast=fast)
        self.growth_unit_update(fast=fast)

        if self.D < 0. and self.D > -1e-15: self.D = 0
        if self.A < 0. and self.A > -1e-15: self.A = 0
        if self.R < 0. and self.R > -1e-15: self.R = 0
        if self.C < 0. and self.C > -1e-15: self.C = 0
        if self.D < 0 or self.A < 0 or self.R < 0 or self.C < 0:
            raise ValueError(
                "D (%s), A  (%s), R (%s) and C (%s) cannot be negative!" %
                (self.D, self.A, self.R, self.C))

    def compute_reserve(self, alpha=1):
        r"""Compute the reserve

        :param float alpha: a multiplier factor

        return the amount of resource to be allocated to the reserve at a given time

        .. math::

            \textrm{Reserve}(t) = \alpha * f(t) * R

        where :math:`R` is the total resource of the system, and :math:`f(t)` a sigmoid function
        starting at a given **starting_time**. The parameter of te sigmoid function can be
        given when instanciating the :class:`Plant` object

        """
        # compute the status of the reserve function (sigmoid between 0-1)
        _reserve_fraction = alpha * self._reserve_function.growthValue(
            self.age - self.reserve_starting_time - 365 * (self.year - 1))

        # a user argument can decrease the max value by multiplying by an alpha parameter.
        _reserve = _reserve_fraction * self.R

        # so, the reserve at this time step is:
        self.Reserve += _reserve
        self.R -= _reserve
        self.variables.reserve.append(_reserve)

    def new_season(self):
        self.R = min(self.Reserve, 10)
        #self.Reserve = 0.
        self.year += 1

    def growth_unit_update(self, fast=True):
        """update growth unit  information.

        #. recompute the number of internode unit in a branch
        #. recompute the branch length
        #. recompute the branch base radius (e.g., first internode radius)

        """

        if fast == True:
            return

        gu_ids = self.mtgtools.ids['U']
        """
        #tool long with the DB because it is created at each time step....
        self.mtgtools.createDB()
        self.mtgtools.connectDB()
        for gu_id in gu_ids:
            counter = len(self.mtgtools.select(select="id", label='I', complex=gu_id))
            self.mtg.property('GrowthUnit')[gu_id].internode_counter = counter

            length = sum(self.mtgtools.select_attribute(attribute="length", select="id", label='I', complex=gu_id))
            self.mtgtools.mtg.property('GrowthUnit')[gu_id].length = length

            radius = self.mtgtools.select_attribute(attribute="radius", select="id", label='I', complex=gu_id)
            if len(radius) != 0:
                self.mtgtools.mtg.property('GrowthUnit')[gu_id].radius = max(radius)
        self.mtgtools.closeDB() 
        """

        # save the number of internodes in each GU using standard mtg code.
        for vid in gu_ids:
            counter = len([
                id for id in list(self.mtg.components_at_scale(vid, 4))
                if self.mtg.class_name(id) == 'I'
            ])
            self.mtg.property('GrowthUnit')[vid].internode_counter = counter

        # computes the length
        for vid in gu_ids:
            length = sum([
                self.mtg.property('Internode')[id].length
                for id in list(self.mtg.components_at_scale(vid, 4))
                if self.mtg.class_name(id) == 'I'
            ])
            self.mtg.property('GrowthUnit')[vid].length = length

        for vid in gu_ids:
            ids = self.mtg.components_at_scale_iter(vid, scale=4)
            try:
                first_id = ids.next()
                if self.mtg.class_name(first_id) == 'I':
                    self.mtg.property('GrowthUnit')[vid].radius = \
                        self.mtg.property('Internode')[first_id].radius
            except:
                # this GU is probably empty
                pass

    def branch_update(self, fast=True):
        """update branch  information.

        #. recompute the number of growth unit in a branch
        #. recompute the number of internode unit in a branch
        #. recompute the branch length
        #. recompute the branch base radius (e.g., first internode radius)

        """

        if fast == True:
            return
        from openalea.mtg.aml import Activate, Components, Class, VtxList
        Activate(self.mtg)
        try:
            branch_ids = VtxList(2)
            if len(branch_ids) == 0:
                return
        except:
            return

        for vid in branch_ids:
            counter = len([
                id for id in list(self.mtg.components_at_scale(vid, 4))
                if self.mtg.class_name(id) == 'I'
            ])
            self.mtg.property('Branch')[vid].internode_counter = counter
            counter = len([
                id for id in list(self.mtg.components_at_scale(vid, 3))
                if self.mtg.class_name(id) == 'U'
            ])
            self.mtg.property('Branch')[vid].growthunit_counter = counter

        # calculate the branches total length
        internode_ids = [Components(x, Scale=4) for x in branch_ids]
        length = [[
            sum([
                self.mtg.property('Internode')[id].length for id in y
                if self.mtg.class_name(id) == 'I'
            ])
        ] for y in internode_ids]
        for vid, length in zip(branch_ids, length):
            self.mtg.property('Branch')[vid].length = length[0]

        #compute the branches radius
        for vid in branch_ids:
            try:
                first_id = self.mtg.components_at_scale_iter(vid,
                                                             scale=4).next()
                if self.mtg.class_name(first_id) == 'I':
                    self.mtg.property(
                        'Branch')[vid].radius = self.mtg.property(
                            'Internode')[first_id].radius
            except:
                #this is probably an empty branch
                pass
            #else:
            #    self.mtg.property('Branch')[vid].radius = 0.

    def _get_options(self):
        return self._options

    options = property(fget=_get_options,
                       doc="getter to the options form the configuration file")

    def _get_revision(self):
        return self._revision

    revision = property(
        fget=_get_revision,
        doc=
        "getter to the SVN :attr:`revision` of the lsystem used within the simulation"
    )

    def _get_dt(self):
        return self._dt

    dt = property(fget=_get_dt, doc="getter to time step :attr:`dt`")

    revision = property(
        fget=_get_revision,
        doc="the revision of the lsystem used within the simulation")

    def _get_mtg(self):
        return self.mtgtools.mtg

    mtg = property(
        fget=_get_mtg,
        doc=
        "getter/alias to :attr:`mtgtools`.mtg. to set it, use :attr:`mtgtools`.mtg instead"
    )

    def _get_time(self):
        return self._time

    time = property(fget=_get_time, doc="getter for the time array")

    def _get_filename(self):
        return self._filename

    filename = property(fget=_get_filename,
                        fset=None,
                        doc="getter to prefix filename")

    def _set_filename(self, ifilename, tag):
        """convert ifilename and tag into a filename string without extension"""
        # set the filename
        if ifilename == None:
            filename = 'plant'
        else:
            filename = ifilename

        # add a tag
        if tag != None: filename += '_' + tag
        return filename
Exemplo n.º 6
0
class Leaf(ComponentInterface):
    r"""Specialised version of :class:`~openalea.plantik.biotik.component.ComponentInterface`
    dedicated to Leaves.

    .. warning:: 

        If the parameter store_data is True, then the :attr:`variables` attributes will store 
        the area, resource and age at each time step, which may be costly. It is set to false by
        default.

    :Example:

    >>> from vplants.plantik.biotik.leaf import *
    >>> i = Leaf(store_data=True, resource_per_day=1)
    >>> i.age.days
    0
    >>> i.variables.age.values
    [0]

    :Notation:

    * age of a leaf is denoted :math:`t_a^{(l)}`

    .. csv-table:: **Notation related to :class:`~openalea.plantik.biotik.leaf.Leaf`**
        :header: Name, symbol, default value
        :widths: 15,20,20
        
        optimal resource    , :math:`r_0^{(l)}`,
        area                , :math:`\mathcal{A}(t_a)`       , a user :class:`~openalea.plantik.biotik.growth.GrowthFunction`
        petiole_radius      , :math:`r_p`                    , 0.0005    meters
        area min            , :math:`S_{min}`                , 1. 10e-4  square meter    
        area max            , :math:`S_{max}`                , 30. 10e-4 square meter
        mass per area       , :math:`a`                       , 200 g/m^2 g per square meter
        efficiency          , ":math:`\mathcal{E}(t,t_a)`"     , see :meth:`leaf_efficiency`
        efficiency parameter, ":math:`t_1`"      , see :meth:`leaf_efficiency`
        efficiency parameter, :math:`t_2`"      , see :meth:`leaf_efficiency`
        growth rate         , :math:`\lambda^{(l)}`    , 1
        nu (logistic)       , :math:`\nu`       , 1
        maturation          , :math:`T_m^{(l)}`        , 21 days
    
    .. todo:: move area_min, area_max, mass_per_area as arguments
    
    """
    petiole_radius = 0.0005
    area_min = 1. * 0.01 * 0.01  # 1 cm^2 changed into  m^2
    area_max = 30 * 0.01 * 0.01
    mass_per_area = 220  # g/m^2

    def __init__(self,
                 resource_per_day,
                 birthdate=None,
                 id=None,
                 maturation=21,
                 internode_vigor=1.,
                 livingcost=0.,
                 growth_rate=1,
                 area_optimal=30 * 0.01 * 0.01,
                 growth_function='sigmoid',
                 efficiency_method='unity',
                 store_data=False,
                 nu=1,
                 t1=15,
                 t2=150,
                 angle=0.):
        """**Constructor**

        

        :param float resource_per_day: stricly positive , notation :math:`r_0`. 

        :param datetime.datetime birthdate:
        :param int id:
        :param float maturation: leaf maturation in days
        :param float internode_vigor: a leaf area is proportional to the internode length. 
            intenode_vigor of 1 means the internode had full length and therefore leaf has
            a potential for maximum area as well.
        :param float livingcost: zero by default
        :param float growth_rate: the :math:`lambda` parameter of the growth function
        :param str growth_function: a GrowthFunction method in ['linear', 'sigmoid', 'logistic']
        :param nu: shape of the logistic function is :attr:`growth_function` is logisitic
        :param efficiency_method: 'unity' or 'sigmoid' (default is unity)
        :param int store_data:
        :param float t1: parameter of the leaf_efficiency method
        :param float t2: parameter of the leaf_efficiency method
        
        
        :attributes:

            * :attr:`area`: the leaf area, a :meth:`~vplants.plantik.biotik.growth.GrowthFunction` 
              instance (read-only). The input parameters **growth_function**, **growth_rate**, **nu**,
              and **maturation** are used by this function.
            * Those inherited by :class:`~vplants.plantik.biotik.component.ComponentInterface`: 
              :attr:`age`,
              :attr:`allocated`,
              :attr:`demand`,
              :attr:`livingcost`,
              :attr:`~vplants.plantik.biotik.component.ComponentInterface.resource`.
            * :attr:`variables` is a :class:`CollectionVariables` instance containing the :attr:`age`, 
              :attr:`demand`, :attr:`resource`, :attr:`area`.
        """
        assert internode_vigor >= 0 and internode_vigor <= 1, \
            "internode vifor must be in [0,1]"
        assert resource_per_day > 0, \
            'resource_per_day must be positive otherwise noting will happen...'

        self.context = Context()
        ComponentInterface.__init__(self,
                                    label='Leaf',
                                    birthdate=birthdate,
                                    id=id,
                                    state='growing')

        # leaf min must correspond to internode length min so that
        # leafmaxarea>-leafminarea
        self._radius = Leaf.petiole_radius  # for geometry purpose only

        # set the area growth function
        self.area_max = Leaf.area_max * internode_vigor
        self._area = GrowthFunction(Leaf.area_min,
                                    self.area_max,
                                    maturation=maturation,
                                    growth_rate=growth_rate,
                                    growth_function=growth_function,
                                    nu=nu)
        self._r0 = resource_per_day
        self.angle = angle

        # used for bookeeping only
        self.internode_vigor = internode_vigor  # in %
        self.maturation = maturation  # in days

        # used by update()
        self.store_data = store_data

        # some variables to store.
        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='age', unit='days', values=[self.age.days]))
        self.variables.add(
            SingleVariable(name='resource',
                           unit='biomass unit',
                           values=[self.resource]))
        self.variables.add(
            SingleVariable(name='demand',
                           unit='biomass unit',
                           values=[self.demand]))
        self.variables.add(
            SingleVariable(name='area',
                           unit='square meters',
                           values=[self.area]))

        # parameters
        self.livingcost = livingcost  # cost to maintain leaf alive !!
        self.efficiency_method = efficiency_method

        # other attributes.
        # radius used by the interpretation of the lsystem.
        #todo: move it elsewhere outsitde this class.
        self.father_radius = 0

        #others
        self.lg = 0.  # light interception

        # related to leaf efficiency
        self._leaf_efficiency = 1.
        self.t1 = t1
        self.t2 = t2
        self.growth_rate = growth_rate

    def update(self, dt):
        """Update function

        This update function performs the following tasks:

        #. increment age by dt
        #. calls :meth:`demandCalculation`, :meth:`resourceCalculation` and 
           :meth:`computeLivingCost`
        #. store age into variables.age
        #. store resource into variables.resource
        #. store demand into variables.demand
        #. store area into variables.area if age< maturation

        :param float dt: in days

        
        """
        super(Leaf, self).update(dt)
        self.demandCalculation()
        self.resourceCalculation()
        self.computeLivingcost()

        if self.store_data is True:
            self.variables.age.append(self.age.days)
            self.variables.resource.append(self.resource)
            self.variables.demand.append(self.demand)
            if self.age.days < self.maturation:
                self.variables.area.append(self.area)

    def __str__(self):
        res = super(Leaf, self).__str__()
        res += self.context.__str__()
        res += self.variables.__str__()
        res += title('other leaf attributes')

        res += 'area                = %s\n' % self.area
        res += 'leaf_efficiency     = %s\n' % self.leaf_efficiency
        res += 'r0                  = %s\n' % self.r0
        res += 'radius              = %s\n' % self.radius
        res += 'area_max            = %s\n' % self.area_max
        res += 'efficiency_method   = %s\n' % self.efficiency_method
        res += 'father_radius       = %s\n' % self.father_radius
        res += 'internode_vigor     = %s\n' % self.internode_vigor
        res += 'lg                  = %s\n' % self.lg
        res += 't1                  = %s\n' % self.t1
        res += 't2                  = %s\n' % self.t2
        res += 'maturation          = %s\n' % self.maturation
        res += 'store_data          = %s\n' % self.store_data

        res += self._area.__str__()

        return res

    def demandCalculation(self, **kargs):
        """leaf has no demand"""
        self.demand = 0.
        return self.demand

    def resourceCalculation(self):
        r"""leaf resource is computed as follows:

        .. math::

            r(t,t_a) = r_0  \frac{ \mathcal{A}(t_a) }{ A_{\textrm{max}}}  \mathcal{E}(t,t_a)

        where :math:`\mathcal{E}(t)` is the leaf efficiency, :math:`\mathcal{A}`, the leaf
        area and :math:`\mathcal{A}_{\textrm{max}}` the area of a standard leaf and :math:`r_0`
        the resource of a standard leaf per day.. See :meth:`~vplants.plantik.biotik.leaf.leaf_efficiency`
        method. Since :math:`r_0` is a unit of resource per day, :math:`r(t)` is in unit of biomass per day

        """
        self.resource = self.r0 * self.area / Leaf.area_max
        self.resource *= self.leaf_efficiency()
        return self.resource

    def leaf_efficiency(self):
        r"""simple senescence method made of two sigmoid of parameter t1, t2
        
        There are currently two methods: unity and sigmoid. The former returns
        1 whatsoever, while the latter returns the following quantity:
        
        .. math::
        
            \frac{1}{\left(1+\exp^{\lambda (t_1 - t_a)}\right) 
                \left(1+\exp^{\lambda (t_a - t_2)}\right)}

        where :math:`t_1` and :math:`t_2` are the sigmoids parameter and :math:`\lambda`
        is the growth rate parameter. 
        
        .. plot::            
            :include-source:

            from pylab import *
            from vplants.plantik.biotik.leaf import *
            b = Leaf(resource_per_day=1, store_data=True, efficiency_method='sigmoid')
            for v in range(1,200):
                b.update(1)
            b.plot('resource')
            grid(True)
            axvline(15,linewidth=3, color='red', alpha=0.5)
            axvline(150,linewidth=3, color='red', alpha=0.5)
        """
        if self.efficiency_method == 'unity':
            return 1.
        elif self.efficiency_method in ['logistic', 'sigmoid']:
            from pylab import exp
            return 1./(1+exp(self.growth_rate*(self.t1-self.age.days))) /\
                (1.+exp(self.growth_rate*(self.age.days-self.t2)))
        else:
            raise ValueError(
                'efficiency_method must be either unity or sigmoid (config.ini file)'
            )

    def computeLivingcost(self):
        return self.livingcost

    def _getMass(self):
        return Leaf.mass_per_area * self.area

    mass = property(_getMass,
                    None,
                    None,
                    doc="returns the leaf mass in g per m^2")

    # the petiole radius does not change, so no setter
    def _getRadius(self):
        return self._radius

    radius = property(_getRadius, None, None, doc="petiole radius")

    def _getArea(self):
        return self._area.growthValue(self.age.days)

    area = property(_getArea, None, None, doc="getter to the leaf area.")

    def _getR0(self):
        return self._r0

    r0 = property(_getR0, None, None, doc="optimal resource per day")

    def plot(self,
             variables=['demand', 'resource', 'area'],
             show=True,
             grid=True,
             **args):
        """plot demand, resource and area over time

        :param list variables: plot results related to the variables provided
        :param bool show: create but do not show the plot (useful for test, saving)
        :param  args: any parameters that pylab.plot would accept.

        .. plot::
            :include-source:

            from vplants.plantik.biotik.leaf import *
            b = Leaf(resource_per_day=1, store_data=True)
            for v in range(1,30):
                b.update(1)
            b.plot()

        """
        self.variables.plot(variables=variables, show=show, grid=grid, **args)
Exemplo n.º 7
0
    def __init__(self,
                 final_length=0.03,
                 length_max=0.03,
                 length_min=0.001,
                 cambial_fraction=0.,
                 birthdate=None,
                 id=None,
                 maturation=10,
                 radius_min=0.001,
                 growth_rate=1,
                 growth_function='logistic',
                 store_data=False,
                 nu=1):
        r"""**Constructor**


        :param float final_length: final length :math:`l^{(i)_{\rm{final}}}`
        :param float length_max: maximum internode length, :math:`l^{(i)}_{max}` (default is 3cm)
        :param float length_min: maximum internode length, :math:`l^{(i)}_{min}` (default is 0.1cm)
        :param float cambial_fraction: :math:`p_c` (default is 0.) 
        :param datetime.datetime birthdate:
        :param int id:
        :param float maturation: :math:`T^{(i)}_m` (default is 10) 
        :param float radius_min: starting radius of an internode, :math:`r^{(i)}_{min}` (in meters) (default is 0.001)        
        :param str growth_function: linear or logistic (default is logistic) 
        :param float growth_rate: :math:`\lambda` (default is 1)
        :param float nu: :math:`\nu^{(i)}` parameter of the logistic function (default is 1)
        :param int store_data: store length, radius, demand at each time step (default is False)

        .. note:: the growth function is logistic by default, which is identical 
            to a sigmoid function isnce :math:`\nu=1` and :math:`\lambda=1`.
            
        
        :attributes:
            * :attr:`length`: internode length
            * :attr:`radius`:  internode radius (supposed the same from base to top)
            * :attr:`target_radius`:  at each time step, a pipe model may be computed indicating what the new radius should be.
            *  those inherited by :class:`~vplants.plantik.biotik.component.ComponentInterface`: 
                * :attr:`age`
                * :attr:`demand`
                * :attr:`birthdate`, ...
            * :attr:`mass`
            * :attr:`volume`
    
        """
        self.radius_min = radius_min
        self.length_max = length_max
        self.length_min = length_min
        self.final_length = final_length
        assert final_length >= length_min, 'final length must be greater or equal to min length'
        assert length_max >= length_min, 'max length must be greater or equal to min length'

        self.volume_standard = 3.14159 * self.radius_min**2 * self.length_max
        self.cost_per_metamer = 1. / (self.radius_min * self.radius_min *
                                      self.length_max * pi)

        self.context = Context()
        ComponentInterface.__init__(self,
                                    label='Internode',
                                    birthdate=birthdate,
                                    id=id)

        self._radius = self.radius_min
        self._target_radius = self.radius_min
        self._length = GrowthFunction(self.length_min,
                                      self.final_length,
                                      maturation=maturation,
                                      growth_rate=growth_rate,
                                      nu=nu,
                                      growth_function=growth_function)

        self.store_data = store_data

        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='age', unit='days', values=[self.age.days]))
        self.variables.add(
            SingleVariable(name='length', unit='meters', values=[self.length]))
        self.variables.add(
            SingleVariable(name='radius', unit='meters', values=[self.radius]))
        #self.variables.add(SingleVariable(name='demand',
        #    unit='biomass unit', values=[self.demand]))
        self.variables.add(
            SingleVariable(name='living_cost',
                           unit='biomass unit',
                           values=[self.livingcost]))

        self.demand_coeff = 0.
        self.density = 1.

        assert cambial_fraction <= 1 and cambial_fraction >= 0
        self._mu = cambial_fraction  # % of cambial layer alive
Exemplo n.º 8
0
class Internode(ComponentInterface):
    r"""Internode. Specialised version of :class:`~openalea.plantik.biotik.component.ComponentInterface`
    
    :Example:

    >>> from vplants.plantik.biotik.internode import *
    >>> i = Internode(store_data=True, growth_rate=1)
    >>> i.radius
    0.001

    Use the :meth:`update` to increment the age of a component. This method
    will take care of updating relevant properties such as the internode length. 

    :plotting:

    If the argument store_data is set to True, attributes such as length are
    stored over time in the :attr:`variables`, which you can plot using either 
    the :meth:`plot` method or from the variables stored in :attr:`variables`. 
    
    .. seealso:: :mod:`~openalea.plantik.biotik.collection` module

    :Notation:

    * the age of the internode is denoted :math:`t_a`

    .. csv-table:: **Notation related to** :class:`~openalea.plantik.biotik.internode.Internode`
        :header: Name, symbol, default value
        :widths: 15,20,20
        
        radius_min          , :math:`r^{(i)}_{min}`       , 0.001 meters
        radius              , :math:`r^{(i)}`             , output
        radius target       , :math:`r^{(i)}_{target}`    , internal
        final length        , :math:`l^{(i)}`             , user input
        length              , :math:`l^{(i)}`             , output
        length_min          , :math:`l^{(i)}_{min}`       , 0.001 meters
        length_max          , :math:`l^{(i)}_{max}`       , 0.03 meters
        maturation          , :math:`T^{(i)}_m`           , 10 days
        growth rate         , :math:`\lambda^{(i)}`       , 1
        nu (logistic)       , :math:`\nu^{(i)}`           , 1
        cambial fraction    , :math:`p_c`                 , 0 %
        volume              , :math:`V^{(i)}`             , output
        density             , :math:`\rho^{(i)}`          , internal
        mass                , :math:`m^{(i)}`             , output
     
    """
    def __init__(self,
                 final_length=0.03,
                 length_max=0.03,
                 length_min=0.001,
                 cambial_fraction=0.,
                 birthdate=None,
                 id=None,
                 maturation=10,
                 radius_min=0.001,
                 growth_rate=1,
                 growth_function='logistic',
                 store_data=False,
                 nu=1):
        r"""**Constructor**


        :param float final_length: final length :math:`l^{(i)_{\rm{final}}}`
        :param float length_max: maximum internode length, :math:`l^{(i)}_{max}` (default is 3cm)
        :param float length_min: maximum internode length, :math:`l^{(i)}_{min}` (default is 0.1cm)
        :param float cambial_fraction: :math:`p_c` (default is 0.) 
        :param datetime.datetime birthdate:
        :param int id:
        :param float maturation: :math:`T^{(i)}_m` (default is 10) 
        :param float radius_min: starting radius of an internode, :math:`r^{(i)}_{min}` (in meters) (default is 0.001)        
        :param str growth_function: linear or logistic (default is logistic) 
        :param float growth_rate: :math:`\lambda` (default is 1)
        :param float nu: :math:`\nu^{(i)}` parameter of the logistic function (default is 1)
        :param int store_data: store length, radius, demand at each time step (default is False)

        .. note:: the growth function is logistic by default, which is identical 
            to a sigmoid function isnce :math:`\nu=1` and :math:`\lambda=1`.
            
        
        :attributes:
            * :attr:`length`: internode length
            * :attr:`radius`:  internode radius (supposed the same from base to top)
            * :attr:`target_radius`:  at each time step, a pipe model may be computed indicating what the new radius should be.
            *  those inherited by :class:`~vplants.plantik.biotik.component.ComponentInterface`: 
                * :attr:`age`
                * :attr:`demand`
                * :attr:`birthdate`, ...
            * :attr:`mass`
            * :attr:`volume`
    
        """
        self.radius_min = radius_min
        self.length_max = length_max
        self.length_min = length_min
        self.final_length = final_length
        assert final_length >= length_min, 'final length must be greater or equal to min length'
        assert length_max >= length_min, 'max length must be greater or equal to min length'

        self.volume_standard = 3.14159 * self.radius_min**2 * self.length_max
        self.cost_per_metamer = 1. / (self.radius_min * self.radius_min *
                                      self.length_max * pi)

        self.context = Context()
        ComponentInterface.__init__(self,
                                    label='Internode',
                                    birthdate=birthdate,
                                    id=id)

        self._radius = self.radius_min
        self._target_radius = self.radius_min
        self._length = GrowthFunction(self.length_min,
                                      self.final_length,
                                      maturation=maturation,
                                      growth_rate=growth_rate,
                                      nu=nu,
                                      growth_function=growth_function)

        self.store_data = store_data

        self.variables = CollectionVariables()
        self.variables.add(
            SingleVariable(name='age', unit='days', values=[self.age.days]))
        self.variables.add(
            SingleVariable(name='length', unit='meters', values=[self.length]))
        self.variables.add(
            SingleVariable(name='radius', unit='meters', values=[self.radius]))
        #self.variables.add(SingleVariable(name='demand',
        #    unit='biomass unit', values=[self.demand]))
        self.variables.add(
            SingleVariable(name='living_cost',
                           unit='biomass unit',
                           values=[self.livingcost]))

        self.demand_coeff = 0.
        self.density = 1.

        assert cambial_fraction <= 1 and cambial_fraction >= 0
        self._mu = cambial_fraction  # % of cambial layer alive

    def __str__(self):
        res = super(Internode, self).__str__()
        res += self.context.__str__()
        res += self.variables.__str__()
        res += title('other attributes')
        return res

    def resourceCalculation(self):
        """No resource in an internode. 

        For now, it is not used (r=0)
        """
        self.resource = 0.
        return self.resource

    def demandCalculation(self, **kargs):
        """demand of the internode

        For now, this is not used (i.e., d=0)
        """
        #demand_coeff is zero, so d=0
        self.demand = self.volume * self.demand_coeff
        return self.demand

    def _getVolume(self):
        """Returns total internode volume

        .. math::

            \pi r^2 \\times l^{(i)}
        """
        return pi * self.radius * self.radius * self.length

    volume = property(_getVolume, None, None)

    def _getdVolume(self):
        r"""Retuns the volume that is not yet built 
        
        .. seealso:: :attr:`target_radius`
        
        .. math::
    
            dV = \pi l^{(i)} \left( r_{\rm{target}}^2  -  r^2  \right)
        
        """
        return pi * (self._target_radius * self._target_radius -
                     self.radius * self.radius) * self.length

    dvolume = property(_getdVolume, None, None)

    def _getMass(self):
        r"""returns the mass of the internode
        
        .. math:: 
        
            m^{(i)} = V^{(i)}  \rho^{(i)}
            
        """
        return self.density * self.volume

    mass = property(_getMass, None, None)

    def _getRadius(self):
        return self._radius

    def _setRadius(self, radius):
        if radius < self._radius:
            raise ValueError("radius decreased in internode !!")
        self._radius = radius

    radius = property(_getRadius,
                      _setRadius,
                      None,
                      doc="internode radius. Cannot decrease !")

    def _getTargetRadius(self):
        return self._target_radius

    def _setTargetRadius(self, target_radius):
        self._target_radius = target_radius

    target_radius = property(_getTargetRadius,
                             _setTargetRadius,
                             None,
                             doc=r"""
    Target radius :math:`r_{\rm{target}}` that the internode tend to reach
    
    This is used by the pipe model.
    
    
    """)

    def _getLength(self):
        return self._length.growthValue(self.age.days)

    length = property(_getLength,
                      None,
                      None,
                      doc="internode length :math:`l^{(i)}`")

    def update(self, dt):
        """Update function

        This update function performs the following tasks:

        #. increment age by dt
        #. calls :meth:`demandCalculation`, :meth:`resourceCalculation` and 
           :meth:`computeLivingCost`
        #. store age :attr:`variables`
        #. store length in :attr:`variables`
        #. store radius in :attr:`variables`
        

        :param float dt: 

        .. note:: when maturation is reached, length is not stored any more
        """

        super(Internode, self).update(dt)
        self._compute_livingcost(dt)
        self.demandCalculation()
        self.resourceCalculation()
        if self.store_data is True:
            self.variables.age.append(self.age.days)
            # once maturation is reached, there is not point is storing the
            # length, which ha reached a maximum
            if self.age.days <= self._length.maturation:
                self.variables.length.append(self.length)
            self.variables.radius.append(self.radius)

    def _compute_livingcost(self, dt):
        """
        Within an internode, only the external layer requires a livingcost. The external
        layer is comprise between the max radisu R and the min raidus r.

        So, the  cambial volume is equivalent to the volume of an empty cylinder

        This is a volume that equals :math:`\pi h R^2 (2\mu -\mu^2)`.

        where `\mu` is a fraction of R
        """
        self.livingcost = self.volume * self._mu * (2. - self._mu)
        self.livingcost *= self.cost_per_metamer * dt

    def plot(self,
             variables=['radius', 'length', 'living_cost'],
             show=True,
             grid=True,
             **args):
        """plot radius, length and living cost over time

        :param list variables: plot results related to the variables provided
        :param bool show: create but do not show the plot (useful for test, saving)
        :param  args: any parameters that pylab.plot would accept.
        
        
        .. plot:: 
            :include-source:
            
            from pylab import *
            from vplants.plantik.biotik.internode import Internode
            i = Internode(store_data=True)
            for v in range(1,30):
                i.update(1)
            i.plot('length')
            
        .. note:: Although, the iteration went over 30 days, the plot shows only 10
            days because the internode reaced maturity after 10 days. 
        """
        self.variables.plot(variables=variables, show=show, grid=grid, **args)
Exemplo n.º 9
0
    def __init__(self, birthdate=None, 
                 demand=2, metamer_cost=2, livingcost=0.,
                 height=0., id=0, plastochron=3., growth_threshold = 0.2,
                 vigor=0.1, store_data=False):
        """**Constructor**

        :param datetime.datetime birthdate: (default is None)
        :param float demand:  (default is 2)
        :param float metamer_cost:    (default is 2)
        :param float livingcost: (default is 0)
        :param float height:   (default is 0)
        :param int id: (default is 0)
        :param plastochron: (default is 3)
        :param float growth_threshold: a value between  0 and 1 allowing a 
            production once allocation is larger than this value
        :param vigor: (default is 0.1)
        :param store_data: used by :meth:`save_data_product` to save data at each 
           time step (default is False).

        Additional attributes 

        :attributes:
            * :attr:`current_plastochron`:
            * :attr:`radius`:  (default is 0)
            * :attr:`growth_potential`:  (default is 1)
            * :attr:`interruption`: time step during which an apex is not growing (default is 0.)
            * :attr:`growing` (default is False)
            * :attr:`father_radius`  (default is 0.)
            * :attr:`lg`: used to store light interception
            * :attr:`internodes_created`  keep track of the number of internodes created by this
                apex (default is 0.).

        """
        self.context = Context()
        ComponentInterface.__init__(self, label='Apex', birthdate=birthdate, 
                                    id=id)


        # read-only attribute
        self.plastochron = plastochron      # time interval at which production of new biological object is possible
        self.metamer_cost = metamer_cost    # cost to produce new object (internode + new bud)
        self.demand = demand                # demand of the metamer in biomass unit/per time  unit


        self.store_data = store_data

        self._height = height
        self._vigor = vigor
        self._growth_threshold = growth_threshold

        self.lg = 0.14
        self.variables = CollectionVariables()
        self.variables.add(SingleVariable(name='age', unit='days', 
                                          values=[self.age.days]))
        self.variables.add(SingleVariable(name='height', unit='meters', 
                                          values=[self.height]))
        self.variables.add(SingleVariable(name='demand', unit='biomass unit', 
                                          values=[self.demand]))
        self.variables.add(SingleVariable(name='allocated', unit='biomass unit', 
                                          values=[self.allocated]))
        self.variables.add(SingleVariable(name='vigor', unit='biomass unit', 
                                          values=[self.vigor]))
        self.variables.add(SingleVariable(name='lg', unit='arbitrary', 
                                          values=[self.lg]))


        #read-write attributes
        self.livingcost = livingcost      # cost to maintain the apex alive
        self.initial_demand = demand

        # additional attributes
        self.current_plastochron = 0.   # keep track of the plastochron
        self.radius = 0.00 # for the pipe model
        self.growth_potential = 1   #?



        self.father_radius = 0.
        self.interruption = 0.   # time step during which an apex is not growing
        self.growing = False
        self.internodes_created = 0.        # count number of internodes created by this apex

        self.lg = 0.14

        self.type = 'Apex' #apex or meristem
Exemplo n.º 10
0
class Apex(ComponentInterface):
    r"""class dedicated to apex component.

    An apex is a biological objects that can produce other biological objects.
    As a biological component, it inherits attributes and methods
    from :class:`~vplants.plantik.biotik.component.ComponentInterface`.

    :Example:

    >>> from vplants.plantik.biotik.apex import Apex
    >>> i = Apex(store_data=True)
    >>> i.age.days
    0
    
    :Notation:

    * age of an apex is denoted :math:`t_a`

    .. csv-table:: **Notation related to** :class:`~openalea.plantik.biotik.apex.Apex`
        :header: Name, symbol, default value
        :widths: 15,20,20
        
        optimal demand      , :math:`d^{(a)_0}`     , 2 UR
        plastochron         , :math:`T^{(a)}_p`     , 3 days
        growth threshold    , :math:`\lambda`       , 0.2
        metamer cost        , :math:`m_{\rm cost}`  , 2 UR
        living cost         ,-                      , 0
        vigor               ,-                      , 0.1

    """
    def __init__(self, birthdate=None, 
                 demand=2, metamer_cost=2, livingcost=0.,
                 height=0., id=0, plastochron=3., growth_threshold = 0.2,
                 vigor=0.1, store_data=False):
        """**Constructor**

        :param datetime.datetime birthdate: (default is None)
        :param float demand:  (default is 2)
        :param float metamer_cost:    (default is 2)
        :param float livingcost: (default is 0)
        :param float height:   (default is 0)
        :param int id: (default is 0)
        :param plastochron: (default is 3)
        :param float growth_threshold: a value between  0 and 1 allowing a 
            production once allocation is larger than this value
        :param vigor: (default is 0.1)
        :param store_data: used by :meth:`save_data_product` to save data at each 
           time step (default is False).

        Additional attributes 

        :attributes:
            * :attr:`current_plastochron`:
            * :attr:`radius`:  (default is 0)
            * :attr:`growth_potential`:  (default is 1)
            * :attr:`interruption`: time step during which an apex is not growing (default is 0.)
            * :attr:`growing` (default is False)
            * :attr:`father_radius`  (default is 0.)
            * :attr:`lg`: used to store light interception
            * :attr:`internodes_created`  keep track of the number of internodes created by this
                apex (default is 0.).

        """
        self.context = Context()
        ComponentInterface.__init__(self, label='Apex', birthdate=birthdate, 
                                    id=id)


        # read-only attribute
        self.plastochron = plastochron      # time interval at which production of new biological object is possible
        self.metamer_cost = metamer_cost    # cost to produce new object (internode + new bud)
        self.demand = demand                # demand of the metamer in biomass unit/per time  unit


        self.store_data = store_data

        self._height = height
        self._vigor = vigor
        self._growth_threshold = growth_threshold

        self.lg = 0.14
        self.variables = CollectionVariables()
        self.variables.add(SingleVariable(name='age', unit='days', 
                                          values=[self.age.days]))
        self.variables.add(SingleVariable(name='height', unit='meters', 
                                          values=[self.height]))
        self.variables.add(SingleVariable(name='demand', unit='biomass unit', 
                                          values=[self.demand]))
        self.variables.add(SingleVariable(name='allocated', unit='biomass unit', 
                                          values=[self.allocated]))
        self.variables.add(SingleVariable(name='vigor', unit='biomass unit', 
                                          values=[self.vigor]))
        self.variables.add(SingleVariable(name='lg', unit='arbitrary', 
                                          values=[self.lg]))


        #read-write attributes
        self.livingcost = livingcost      # cost to maintain the apex alive
        self.initial_demand = demand

        # additional attributes
        self.current_plastochron = 0.   # keep track of the plastochron
        self.radius = 0.00 # for the pipe model
        self.growth_potential = 1   #?



        self.father_radius = 0.
        self.interruption = 0.   # time step during which an apex is not growing
        self.growing = False
        self.internodes_created = 0.        # count number of internodes created by this apex

        self.lg = 0.14

        self.type = 'Apex' #apex or meristem

    def _getHeight(self):
        return self._height
    def _setHeight(self, height):
        self._height = height
    height = property(_getHeight, _setHeight, None, doc="getter/setter to distance between apex and root")

    def _getGrowthThreshold(self):
        return self._growth_threshold
    growth_threshold = property(_getGrowthThreshold, None, None, doc="getter to growth threshold")

    def _getVigor(self):
        return self._vigor
    def _setVigor(self, vigor):
        self._vigor = vigor
    vigor = property(_getVigor, _setVigor, None, doc="getter/setter to distance between apex and root")

    def update(self, dt):
        """Update function

        This update function performs the following tasks:

        #. increment age by dt
        #. increment plastochron by dt
        #. increment interruption time by dt

        #. calls :meth:`demandCalculation`, :meth:`resourceCalculation` and 
           :meth:`computeLivingCost`

        #. store age into variables.age
        #. store height into variables.height
        #. store demand into variables.demand
        #. store allocated into variables.allocated
        #. store vigor into variables.vigor
        #. store d2a into variables.d2a

        :param float dt: 
        """
        super(Apex, self).update(dt)
        self.current_plastochron += dt
        self.interruption += dt

        if self.store_data is True:
            self.variables.age.append(self.age.days)
            self.variables.height.append(self.height)
            self.variables.demand.append(self.demand)
            self.variables.allocated.append(self.allocated)
            self.variables.vigor.append(self.vigor)
            self.variables.lg.append(self.lg)

        """if self.allocated > 0 and self.current_plastochron==self.plastochron:
            if self.growing == True:
                self.vigor += 0.05
                if self.vigor>=1:
                    self.vigor = 1.
            else:
                self.vigor -=0.05
                if self.vigor <=0:
                    self.vigor =0.05
        """

        if self.current_plastochron == self.plastochron:
            #check if the apex got some resource even tough it does not grow.
            if self.allocated>0:
                self.vigor += 0.05
                if self.vigor>=1:
                    self.vigor = 1.
            else:
                self.vigor -=0.05
                if self.vigor <=0:
                    self.vigor =0.05
    



    def demandCalculation(self,  **kargs):
        r"""Compute the demand of an apex according to its context


        :param float context: order_height :math:`\alpha_o`
        :param float context: height_height :math:`\alpha_h`
        :param float context: rank_height :math:`\alpha_r`
        :param float context: age_height :math:`\alpha_a`
        :param float context: vigor_height :math:`\alpha_v`

        The order is denoted :math:`o`.
        The height is :math`h`.
        The rank is :math`r`.
        The age is :math`a`.
        The satisfaction is :math`v`.

        .. math::

                d = d_0 \mathcal{O} \mathcal{H} \mathcal{R} \mathcal{A} \mathcal{V}

        .. math::

            \mathcal{O} = \frac{1}{(1 + o)^{\alpha_o}}

        .. math::

            \mathcal{H} = \frac{1}{(1+h)^{\alpha_h}}

        .. math::

            \mathcal{R} = \frac{1}{(1+r)^{\alpha_r}}

        .. math::

            \mathcal{A} = (\frac{1}{1+exp^{+(0.03*(age-90.))}})^{\alpha_a}

        .. math::

            \mathcal{V} = (vigor)^{\alpha_v}
        """

        order_coeff = kargs.get("order_coeff", 0)
        height_coeff = kargs.get("height_coeff", 0)
        rank_coeff = kargs.get("rank_coeff", 0)
        age_coeff = kargs.get("age_coeff", 0)
        vigor_coeff = kargs.get("vigor_coeff", 0)
        d2a_coeff = kargs.get("d2a_coeff", 0)
        context = kargs.get("context", "order_height")

        #todo refactoering switch model to context
        model = context
        assert model in ["none", "order_height", "additive", "multiplicative"],\
            'check your config.ini file (model field) %s provided ' % model
        order = self.context.order
        height = self.context.height
        rank = self.context.rank
        d2a = self.context.d2a

        if model=="order_height":
            self.demand = self.initial_demand / float(order+1)**order_coeff / float(height)**height_coeff
            return self.demand
        elif model=='none':
            # nothing to be done in the simple model
            return self.initial_demand
        elif model =='additive' or model == 'multiplicative':
            self.demand = self.initial_demand
            weight = self.context.get_context_weight(model=model, order_coeff=order_coeff,
                height_coeff=height_coeff, rank_coeff=rank_coeff, d2a_coeff=d2a_coeff)

            #if age_coeff>=0:
            w1 = (2 - 2./(1.+exp(-age_coeff * self.age.days)))
            #else:
            #     w1 =  2./(1.+exp(age_coeff*self.age.days))-1.
            #assert w1<=1
            #if vigor_coeff>=0:
            w2 = (2 - 2./(1.+exp(-vigor_coeff * self.age.days)))
            #else:
            #     w2 =  2./(1.+exp(vigor_coeff*self.age.days))-1.
            #assert w2<=1

            if model == 'additive':
                weight = (weight +w1 +w2)/12.
                assert weight >=0 and weight<=1.
            elif model == 'multiplicative':
                weight = weight * w1 * w2
                assert weight >=0 and weight<=1.

            self.demand =  self.initial_demand * (weight)
            self.demand *= min(1., self.lg/0.04)
            return self.demand
 


    def computeLivingcost(self):
        pass

    def resourceCalculation(self):
        """Apices returns resource equals zero"""
        assert self.resource == 0, "how come apex have some resource ? "
        return self.resource


    def plot(self, clf=True, show=True, symbol='-o'):
        """Plot internal state variables such as demand and allocated resource

        .. plot::

            from pylab import *
            from openalea.plantik.biotik.apex import Apex
            a = Apex(store_data=True)
            [a.update(1) for x in range(10)]
            a.plot()

        """
        import pylab

        height = self.variables.height.plot(show=False)[0]
        demand = self.variables.demand.plot(show=False)[0]
        allocated = self.variables.allocated.plot(show=False)[0]
        vigor = self.variables.vigor.plot(show=False)[0]

        pylab.plot(height.get_xdata(), height.get_ydata(), marker='o', 
                   color='b', label='Height')
        pylab.hold(True)
        pylab.plot(demand.get_xdata(), demand.get_ydata(), marker='o', 
                   color='g', label='Demand')
        pylab.plot(allocated.get_xdata(), allocated.get_ydata(), marker='o', 
                   color='r', label='allocated')
        pylab.plot(vigor.get_xdata(), vigor.get_ydata(), marker='o', 
                   color='c', label='vigor')
        pylab.plot([min(self.variables.age.values), max(self.variables.age.values)], 
            [self.growth_threshold, self.growth_threshold], color='m', 
            label='threshold')
        pylab.legend()
        if show is True: pylab.show()


    def __str__(self):
        res = super(Apex, self).__str__()
        res += self.context.__str__()
        res += self.variables.__str__()
        res += title('other apex attributes. to be done')

        return res


    def plot_variables(self, variables=['demand', 'allocated'],
                       show=True, grid=True, **args):
        """plot some results

        :param list variables: plot results related to the variables provided
        :param bool show: show plot or not (default is True). Useful for testing, saving
        :param bool grid: set grid on or off (default True)
        :param args: any parameters that pylab.plot would accept.
        """
        self.variables.plot(variables=variables, show=show, grid=grid, **args)