Subversion Repositories public iLand

Rev

Rev 1221 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/********************************************************************************************
**    iLand - an individual based forest landscape and disturbance model
**    http://iland.boku.ac.at
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
**
**    This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
********************************************************************************************/


/** @class Species
  @ingroup core
  The behavior and general properties of tree species.
  Because the individual trees are designed as leightweight as possible, lots of stuff is done by the Species.
  Inter alia, Species do:
  - store all the precalcualted patterns for light competition (LIP, stamps)
  - do most of the growth (3PG) calculation
  */



#include <QtCore>
#include "globalsettings.h"

#include "species.h"
#include "speciesset.h"
#include "stampcontainer.h"
#include "exception.h"
#include "seeddispersal.h"
#include "tree.h"


Species::~Species()
{
    if (mSeedDispersal)
        delete mSeedDispersal;
    mSeedDispersal = 0;
}

/** main setup routine for tree species.
  Data is fetched from the open query (or file, ...) in the parent SpeciesSet using xyzVar() functions.
  This is called
*/

void Species::setup()
{
    Q_ASSERT(mSet != 0);
    // setup general information
    mId = stringVar("shortName");
    mName = stringVar("name");
#ifdef ILAND_GUI
    QString col_name = '#' + stringVar("displayColor");
    mDisplayColor.setNamedColor(col_name);
#else
    mDisplayColor = 0;
#endif
    QString stampFile = stringVar("LIPFile");
    // load stamps
    mLIPs.load( GlobalSettings::instance()->path(stampFile, "lip") );
    // attach writer stamps to reader stamps
    mLIPs.attachReaderStamps(mSet->readerStamps());
    if (GlobalSettings::instance()->settings().paramValueBool("debugDumpStamps", false) )
        qDebug() << mLIPs.dump();

    // general properties
    mConiferous = boolVar("isConiferous");
    mEvergreen = boolVar("isEvergreen");

    // setup allometries
    mFoliage_a = doubleVar("bmFoliage_a");
    mFoliage_b = doubleVar("bmFoliage_b");

    mWoody_a = doubleVar("bmWoody_a");
    mWoody_b = doubleVar("bmWoody_b");

    mRoot_a = doubleVar("bmRoot_a");
    mRoot_b = doubleVar("bmRoot_b");

    mBranch_a = doubleVar("bmBranch_a");
    mBranch_b = doubleVar("bmBranch_b");

    mSpecificLeafArea = doubleVar("specificLeafArea");
    mFinerootFoliageRatio = doubleVar("finerootFoliageRatio");

    mBarkThicknessFactor = doubleVar("barkThickness");

    // cn-ratios
    mCNFoliage = doubleVar("cnFoliage");
    mCNFineroot = doubleVar("cnFineRoot");
    mCNWood = doubleVar("cnWood");
    if (mCNFineroot*mCNFoliage*mCNWood == 0.) {
        throw IException( QString("Error setting up species %1: CN ratio is 0.").arg(id()));
    }


    // turnover rates
    mTurnoverLeaf = doubleVar("turnoverLeaf");
    mTurnoverRoot = doubleVar("turnoverRoot");

    // hd-relations
    mHDlow.setAndParse(stringVar("HDlow"));
    mHDhigh.setAndParse(stringVar("HDhigh"));
    mHDlow.linearize(0., 100.); // input: dbh (cm). above 100cm the formula will be directly executed
    mHDhigh.linearize(0., 100.);

    // form/density
    mWoodDensity = doubleVar("woodDensity");
    mFormFactor = doubleVar("formFactor");
    // volume = formfactor*pi/4 *d^2*h -> volume = volumefactor * d^2 * h
    mVolumeFactor = mFormFactor * M_PI_4;

    // snags
    mSnagKSW = doubleVar("snagKSW"); // decay rate of SWD
    mSnagHalflife = doubleVar("snagHalfLife");
    mSnagKYL = doubleVar("snagKYL"); // decay rate labile
    mSnagKYR = doubleVar("snagKYR"); // decay rate refractory matter

    if (mFoliage_a*mFoliage_b*mRoot_a*mRoot_b*mWoody_a*mWoody_b*mBranch_a*mBranch_b*mWoodDensity*mFormFactor*mSpecificLeafArea*mFinerootFoliageRatio == 0.) {
        throw IException( QString("Error setting up species %1: one value is NULL in database.").arg(id()));
    }
    // Aging
    mMaximumAge = doubleVar("maximumAge");
    mMaximumHeight = doubleVar("maximumHeight");
    mAging.setAndParse(stringVar("aging"));
    mAging.linearize(0.,1.); // input is harmonic mean of relative age and relative height
    if (mMaximumAge*mMaximumHeight==0.)
        throw IException( QString("Error setting up species %1:invalid aging parameters.").arg(id()));

    // mortality
    // the probabilites (mDeathProb_...) are the yearly prob. of death.
    // from a population a fraction of p_lucky remains after ageMax years. see wiki: base+mortality
    double p_lucky = doubleVar("probIntrinsic");
    double p_lucky_stress = doubleVar("probStress");

    if (p_lucky * mMaximumAge * p_lucky_stress == 0.) {
        throw IException( QString("Error setting up species %1: invalid mortality parameters.").arg(id()));
    }

    mDeathProb_intrinsic = 1. - pow(p_lucky, 1. / mMaximumAge);
    mDeathProb_stress = p_lucky_stress;

    if (logLevelInfo()) qDebug() << "species" << name() << "probStress" << p_lucky_stress << "resulting probability:" << mDeathProb_stress;

    // envirionmental responses
    mRespVpdExponent = doubleVar("respVpdExponent");
    mRespTempMin  =doubleVar("respTempMin");
    mRespTempMax  =doubleVar("respTempMax");
    if (mRespVpdExponent>=0) throw IException( QString("Error: vpd exponent >=0 for species (must be a negative value).").arg(id()));
    if (mRespTempMax==0. || mRespTempMin>=mRespTempMax) throw IException( QString("temperature response parameters invalid for species").arg(id()));

    mRespNitrogenClass = doubleVar("respNitrogenClass");
    if (mRespNitrogenClass<1 || mRespNitrogenClass>3) throw IException( QString("nitrogen class invalid (must be >=1 and <=3) for species").arg(id()));

    // phenology
    mPhenologyClass = intVar("phenologyClass");

    // water
    mMaxCanopyConductance = doubleVar("maxCanopyConductance");
    mPsiMin = -fabs(doubleVar("psiMin")); // force a negative value

    // light
    mLightResponseClass = doubleVar("lightResponseClass");
    if (mLightResponseClass<1. || mLightResponseClass>5.)
        throw IException( QString("invalid light response class for species %1. Allowed: 1..5.").arg(id()));

    // regeneration
    int seed_year_interval = intVar("seedYearInterval");
    if (seed_year_interval==0)
        throw IException(QString("seedYearInterval = 0 for %1").arg(id()));
    mSeedYearProbability = 1 / static_cast<double>(seed_year_interval);
    mMaturityYears = intVar("maturityYears");
    mTM_as1 = doubleVar("seedKernel_as1");
    mTM_as2 = doubleVar("seedKernel_as2");
    mTM_ks = doubleVar("seedKernel_ks0");
    mFecundity_m2 = doubleVar("fecundity_m2");
    mNonSeedYearFraction = doubleVar("nonSeedYearFraction");
    // special case for serotinous trees (US)
    mSerotiny.setExpression(stringVar("serotinyFormula"));
    mSerotinyFecundity = doubleVar("serotinyFecundity");

    // establishment parameters
    mEstablishmentParams.min_temp = doubleVar("estMinTemp");
    mEstablishmentParams.chill_requirement = intVar("estChillRequirement");
    mEstablishmentParams.GDD_min = intVar("estGDDMin");
    mEstablishmentParams.GDD_max = intVar("estGDDMax");
    mEstablishmentParams.GDD_baseTemperature = doubleVar("estGDDBaseTemp");
    mEstablishmentParams.bud_birst = intVar("estBudBirstGDD");
    mEstablishmentParams.frost_free = intVar("estFrostFreeDays");
    mEstablishmentParams.frost_tolerance = doubleVar("estFrostTolerance");
    mEstablishmentParams.psi_min = -fabs(doubleVar("estPsiMin")); // force negative value

    // sapling and sapling growth parameters
    mSaplingGrowthParams.heightGrowthPotential.setAndParse(stringVar("sapHeightGrowthPotential"));
    mSaplingGrowthParams.heightGrowthPotential.linearize(0., 4.);
    mSaplingGrowthParams.hdSapling = static_cast<float>( doubleVar("sapHDSapling") );
    mSaplingGrowthParams.stressThreshold = doubleVar("sapStressThreshold");
    mSaplingGrowthParams.maxStressYears = intVar("sapMaxStressYears");
    mSaplingGrowthParams.referenceRatio = doubleVar("sapReferenceRatio");
    mSaplingGrowthParams.ReinekesR = doubleVar("sapReinekesR");
    mSaplingGrowthParams.browsingProbability = doubleVar("browsingProbability");
    mSaplingGrowthParams.sproutGrowth = doubleVar("sapSproutGrowth");
    if (mSaplingGrowthParams.sproutGrowth>0.)
        if (mSaplingGrowthParams.sproutGrowth<1. || mSaplingGrowthParams.sproutGrowth>10)
            qDebug() << "Value of 'sapSproutGrowth' dubious for species" << name() << "(value: " << mSaplingGrowthParams.sproutGrowth << ")";
    mSaplingGrowthParams.setupReinekeLookup();
}


/** calculate fraction of stem wood increment base on dbh.
    allometric equation: a*d^b -> first derivation: a*b*d^(b-1)
    the ratio for stem is 1 minus the ratio of twigs to total woody increment at current "dbh". */

double Species::allometricFractionStem(const double dbh) const
{
    double fraction_stem = 1. - (mBranch_a*mBranch_b*pow(dbh, mBranch_b-1.)) / (mWoody_a*mWoody_b*pow(dbh, mWoody_b-1));
    return fraction_stem;
}

/** Aging formula.
   calculates a relative "age" by combining a height- and an age-related term using a harmonic mean,
   and feeding this into the Landsberg and Waring formula.
   see http://iland.boku.ac.at/primary+production#respiration_and_aging
   @param useAge set to true if "real" tree age is available. If false, only the tree height is used.
  */

double Species::aging(const float height, const int age) const
{
    double rel_height = qMin(height/mMaximumHeight, 0.999999); // 0.999999 -> avoid div/0
    double rel_age = qMin(age/mMaximumAge, 0.999999);

    // harmonic mean: http://en.wikipedia.org/wiki/Harmonic_mean
    double x = 1. - 2. / (1./(1.-rel_height) + 1./(1.-rel_age)); // Note:

    double aging_factor = mAging.calculate(x);

    return limit(aging_factor, 0., 1.); // limit to [0..1]
}

int Species::estimateAge(const float height) const
{
    int age_rel = int( mMaximumAge * height / mMaximumHeight );
    return age_rel;
}

/** Seed production.
   This function produces seeds if the tree is older than a species-specific age ("maturity")
   If seeds are produced, this information is stored in a "SeedMap"
  */

void Species::seedProduction(const Tree *tree)
{
    if (!mSeedDispersal)
        return; // regeneration is disabled

    // if the tree is considered as serotinous (i.e. seeds need external trigger such as fire)
    if (isTreeSerotinous(tree->age()))
        return;

    // no seed production if maturity age is not reached (species parameter) or if tree height is below 4m.
    if (tree->age() > mMaturityYears && tree->height() > 4.f) {
        mSeedDispersal->setMatureTree(tree->positionIndex(), tree->leafArea());
    }
}


bool Species::isTreeSerotinous(const int age) const
{
    if (mSerotiny.isEmpty())
        return false;
    // the function result (e.g. from a logistic regression model, e.g. Schoennagel 2013) is interpreted as probability
    double p_serotinous = mSerotiny.calculate(age);
    if (drandom()<p_serotinous)
        return true;
    else
        return false;
}


/** newYear is called by the SpeciesSet at the beginning of a year before any growth occurs.
  This is used for various initializations, e.g. to clear seed dispersal maps
  */

void Species::newYear()
{
    if (seedDispersal()) {
        // decide whether current year is a seed year
        mIsSeedYear = (drandom() < mSeedYearProbability);
        if (mIsSeedYear && logLevelDebug())
            qDebug() << "species" << id() << "has a seed year.";
        // clear seed map
        seedDispersal()->clear();
    }
}


void SaplingGrowthParameters::setupReinekeLookup()
{
    mRepresentedClasses.clear();
    for (int i=0;i<41;i++) {
        double h = i/10. + 0.05; // 0.05, 0.15, 0.25, ... 4.05
        double dbh = h / hdSapling  * 100.;
        mRepresentedClasses.push_back(representedStemNumber(dbh));
    }
}