Subversion Repositories public iLand

Rev

Rev 1104 | Rev 1160 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1104 Rev 1157
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/speciesset.cpp':
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/speciesset.cpp':
2
/********************************************************************************************
2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
6
**
7
**    This program is free software: you can redistribute it and/or modify
7
**    This program is free software: you can redistribute it and/or modify
8
**    it under the terms of the GNU General Public License as published by
8
**    it under the terms of the GNU General Public License as published by
9
**    the Free Software Foundation, either version 3 of the License, or
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
10
**    (at your option) any later version.
11
**
11
**
12
**    This program is distributed in the hope that it will be useful,
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
15
**    GNU General Public License for more details.
16
**
16
**
17
**    You should have received a copy of the GNU General Public License
17
**    You should have received a copy of the GNU General Public License
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
19
********************************************************************************************/
20
20
21
#include <QtCore>
21
#include <QtCore>
22
#include <QtSql>
22
#include <QtSql>
23
#include "global.h"
23
#include "global.h"
24
#include "globalsettings.h"
24
#include "globalsettings.h"
25
#include "xmlhelper.h"
25
#include "xmlhelper.h"
26
#include "speciesset.h"
26
#include "speciesset.h"
27
#include "species.h"
27
#include "species.h"
28
#include "model.h"
28
#include "model.h"
29
#include "seeddispersal.h"
29
#include "seeddispersal.h"
30
#include "modelsettings.h"
30
#include "modelsettings.h"
31
#include "debugtimer.h"
31
#include "debugtimer.h"
32
32
33
/** @class SpeciesSet
33
/** @class SpeciesSet
34
    A SpeciesSet acts as a container for individual Species objects. In iLand, theoretically,
34
    A SpeciesSet acts as a container for individual Species objects. In iLand, theoretically,
35
    multiple species sets can be used in parallel.
35
    multiple species sets can be used in parallel.
36
  */
36
  */
37
37
38
SpeciesSet::SpeciesSet()
38
SpeciesSet::SpeciesSet()
39
{
39
{
40
    mSetupQuery = 0;
40
    mSetupQuery = 0;
41
}
41
}
42
42
43
SpeciesSet::~SpeciesSet()
43
SpeciesSet::~SpeciesSet()
44
{
44
{
45
   clear();
45
   clear();
46
}
46
}
47
47
48
void SpeciesSet::clear()
48
void SpeciesSet::clear()
49
{
49
{
50
    qDeleteAll(mSpecies.values());
50
    qDeleteAll(mSpecies.values());
51
    qDeleteAll(mSeedDispersal);
51
    qDeleteAll(mSeedDispersal);
52
    mSpecies.clear();
52
    mSpecies.clear();
53
    mActiveSpecies.clear();
53
    mActiveSpecies.clear();
54
}
54
}
55
55
56
const Species *SpeciesSet::species(const int &index)
56
const Species *SpeciesSet::species(const int &index)
57
{
57
{
58
    foreach(Species *s, mSpecies)
58
    foreach(Species *s, mSpecies)
59
        if (s->index() == index)
59
        if (s->index() == index)
60
            return s;
60
            return s;
61
    return NULL;
61
    return NULL;
62
}
62
}
63
63
64
/** loads active species from a database table and creates/setups the species.
64
/** loads active species from a database table and creates/setups the species.
65
    The function uses the global database-connection.
65
    The function uses the global database-connection.
66
  */
66
  */
67
int SpeciesSet::setup()
67
int SpeciesSet::setup()
68
{
68
{
69
    const XmlHelper &xml = GlobalSettings::instance()->settings();
69
    const XmlHelper &xml = GlobalSettings::instance()->settings();
70
    QString tableName = xml.value("model.species.source", "species");
70
    QString tableName = xml.value("model.species.source", "species");
71
    mName = tableName;
71
    mName = tableName;
72
    QString readerFile = xml.value("model.species.reader", "reader.bin");
72
    QString readerFile = xml.value("model.species.reader", "reader.bin");
73
    readerFile = GlobalSettings::instance()->path(readerFile, "lip");
73
    readerFile = GlobalSettings::instance()->path(readerFile, "lip");
74
    mReaderStamp.load(readerFile);
74
    mReaderStamp.load(readerFile);
75
    if (GlobalSettings::instance()->settings().paramValueBool("debugDumpStamps", false) )
75
    if (GlobalSettings::instance()->settings().paramValueBool("debugDumpStamps", false) )
76
        qDebug() << mReaderStamp.dump();
76
        qDebug() << mReaderStamp.dump();
77
77
78
78
79
    QSqlQuery query(GlobalSettings::instance()->dbin());
79
    QSqlQuery query(GlobalSettings::instance()->dbin());
80
    mSetupQuery = &query;
80
    mSetupQuery = &query;
81
    QString sql = QString("select * from %1").arg(tableName);
81
    QString sql = QString("select * from %1").arg(tableName);
82
    query.exec(sql);
82
    query.exec(sql);
83
    if (query.lastError().isValid()){
83
    if (query.lastError().isValid()){
84
        throw IException(QString("Error loading species set: %1 \n %2").arg(sql, query.lastError().text()) );
84
        throw IException(QString("Error loading species set: %1 \n %2").arg(sql, query.lastError().text()) );
85
    }
85
    }
86
86
87
    clear();
87
    clear();
88
    qDebug() << "attempting to load a species set from" << tableName;
88
    qDebug() << "attempting to load a species set from" << tableName;
89
    while (query.next()) {
89
    while (query.next()) {
90
        if (var("active").toInt()==0)
90
        if (var("active").toInt()==0)
91
            continue;
91
            continue;
92
92
93
        Species *s = new Species(this); // create
93
        Species *s = new Species(this); // create
94
        // call setup routine (which calls SpeciesSet::var() to retrieve values
94
        // call setup routine (which calls SpeciesSet::var() to retrieve values
95
        s->setup();
95
        s->setup();
96
96
97
        mSpecies.insert(s->id(), s); // store
97
        mSpecies.insert(s->id(), s); // store
98
        if (s->active())
98
        if (s->active())
99
            mActiveSpecies.append(s);
99
            mActiveSpecies.append(s);
100
100
101
        Expression::addConstant(s->id(), s->index());
101
        Expression::addConstant(s->id(), s->index());
102
    } // while query.next()
102
    } // while query.next()
103
    qDebug() << "loaded" << mSpecies.count() << "active species:";
103
    qDebug() << "loaded" << mSpecies.count() << "active species:";
104
    qDebug() << "index, id, name";
104
    qDebug() << "index, id, name";
105
    foreach(const Species *s, mActiveSpecies)
105
    foreach(const Species *s, mActiveSpecies)
106
        qDebug() << s->index() << s->id() << s->name();
106
        qDebug() << s->index() << s->id() << s->name();
107
107
108
    mSetupQuery = 0;
108
    mSetupQuery = 0;
109
109
110
    // setup nitrogen response
110
    // setup nitrogen response
111
    XmlHelper resp(xml.node("model.species.nitrogenResponseClasses"));
111
    XmlHelper resp(xml.node("model.species.nitrogenResponseClasses"));
112
    if (!resp.isValid())
112
    if (!resp.isValid())
113
        throw IException("model.species.nitrogenResponseClasses not present!");
113
        throw IException("model.species.nitrogenResponseClasses not present!");
114
    mNitrogen_1a = resp.valueDouble("class_1_a");
114
    mNitrogen_1a = resp.valueDouble("class_1_a");
115
    mNitrogen_1b = resp.valueDouble("class_1_b");
115
    mNitrogen_1b = resp.valueDouble("class_1_b");
116
    mNitrogen_2a = resp.valueDouble("class_2_a");
116
    mNitrogen_2a = resp.valueDouble("class_2_a");
117
    mNitrogen_2b = resp.valueDouble("class_2_b");
117
    mNitrogen_2b = resp.valueDouble("class_2_b");
118
    mNitrogen_3a = resp.valueDouble("class_3_a");
118
    mNitrogen_3a = resp.valueDouble("class_3_a");
119
    mNitrogen_3b = resp.valueDouble("class_3_b");
119
    mNitrogen_3b = resp.valueDouble("class_3_b");
120
    if (mNitrogen_1a*mNitrogen_1b*mNitrogen_2a*mNitrogen_2b*mNitrogen_3a*mNitrogen_3b == 0)
120
    if (mNitrogen_1a*mNitrogen_1b*mNitrogen_2a*mNitrogen_2b*mNitrogen_3a*mNitrogen_3b == 0)
121
        throw IException("at least one parameter of model.species.nitrogenResponseClasses is not valid (value=0)!");
121
        throw IException("at least one parameter of model.species.nitrogenResponseClasses is not valid (value=0)!");
122
122
123
    // setup CO2 response
123
    // setup CO2 response
124
    XmlHelper co2(xml.node("model.species.CO2Response"));
124
    XmlHelper co2(xml.node("model.species.CO2Response"));
125
    mCO2base = co2.valueDouble("baseConcentration");
125
    mCO2base = co2.valueDouble("baseConcentration");
126
    mCO2comp = co2.valueDouble("compensationPoint");
126
    mCO2comp = co2.valueDouble("compensationPoint");
127
    mCO2beta0 = co2.valueDouble("beta0");
127
    mCO2beta0 = co2.valueDouble("beta0");
128
    mCO2p0 = co2.valueDouble("p0");
128
    mCO2p0 = co2.valueDouble("p0");
129
    if (mCO2base*mCO2comp*(mCO2base-mCO2comp)*mCO2beta0*mCO2p0==0)
129
    if (mCO2base*mCO2comp*(mCO2base-mCO2comp)*mCO2beta0*mCO2p0==0)
130
        throw IException("at least one parameter of model.species.CO2Response is not valid!");
130
        throw IException("at least one parameter of model.species.CO2Response is not valid!");
131
131
132
    // setup Light responses
132
    // setup Light responses
133
    XmlHelper light(xml.node("model.species.lightResponse"));
133
    XmlHelper light(xml.node("model.species.lightResponse"));
134
    mLightResponseTolerant.setAndParse(light.value("shadeTolerant"));
134
    mLightResponseTolerant.setAndParse(light.value("shadeTolerant"));
135
    mLightResponseIntolerant.setAndParse(light.value("shadeIntolerant"));
135
    mLightResponseIntolerant.setAndParse(light.value("shadeIntolerant"));
136
    mLightResponseTolerant.linearize(0., 1.);
136
    mLightResponseTolerant.linearize(0., 1.);
137
    mLightResponseIntolerant.linearize(0., 1.);
137
    mLightResponseIntolerant.linearize(0., 1.);
138
    if (mLightResponseTolerant.expression().isEmpty() || mLightResponseIntolerant.expression().isEmpty())
138
    if (mLightResponseTolerant.expression().isEmpty() || mLightResponseIntolerant.expression().isEmpty())
139
        throw IException("at least one parameter of model.species.lightResponse is empty!");
139
        throw IException("at least one parameter of model.species.lightResponse is empty!");
140
    // lri-correction
140
    // lri-correction
141
    mLRICorrection.setAndParse(light.value("LRImodifier","1"));
141
    mLRICorrection.setAndParse(light.value("LRImodifier","1"));
142
    // x: LRI, y: relative heigth
142
    // x: LRI, y: relative heigth
143
    mLRICorrection.linearize2d(0., 1., 0., 1.);
143
    mLRICorrection.linearize2d(0., 1., 0., 1.);
144
    return mSpecies.count();
144
    return mSpecies.count();
145
145
146
}
146
}
147
147
148
void SpeciesSet::setupRegeneration()
148
void SpeciesSet::setupRegeneration()
149
{
149
{
150
    SeedDispersal::setupExternalSeeds();
150
    SeedDispersal::setupExternalSeeds();
151
    foreach(Species *s, mActiveSpecies) {
151
    foreach(Species *s, mActiveSpecies) {
152
        SeedDispersal *sd = new SeedDispersal(s);
152
        SeedDispersal *sd = new SeedDispersal(s);
153
        sd->setup(); // setup memory for the seed map (grid)
153
        sd->setup(); // setup memory for the seed map (grid)
154
        s->setSeedDispersal(sd); // establish the link between species and the map
154
        s->setSeedDispersal(sd); // establish the link between species and the map
155
    }
155
    }
156
    SeedDispersal::finalizeExternalSeeds();
156
    SeedDispersal::finalizeExternalSeeds();
157
    qDebug() << "Setup of seed dispersal maps finished.";
157
    qDebug() << "Setup of seed dispersal maps finished.";
158
}
158
}
159
159
160
Species *nc_seed_distribution(Species *species)
-
 
-
 
160
void nc_seed_distribution(Species *species)
161
{
161
{
162
    species->seedDispersal()->execute();
162
    species->seedDispersal()->execute();
163
    return species;
-
 
164
}
163
}
-
 
164
165
void SpeciesSet::regeneration()
165
void SpeciesSet::regeneration()
166
{
166
{
167
    if (!GlobalSettings::instance()->model()->settings().regenerationEnabled)
167
    if (!GlobalSettings::instance()->model()->settings().regenerationEnabled)
168
        return;
168
        return;
169
    DebugTimer t("seed dispersal (all species)");
169
    DebugTimer t("seed dispersal (all species)");
170
170
171
    ThreadRunner runner(mActiveSpecies); // initialize a thread runner object with all active species
171
    ThreadRunner runner(mActiveSpecies); // initialize a thread runner object with all active species
172
    runner.run(nc_seed_distribution);
172
    runner.run(nc_seed_distribution);
173
173
174
    if (logLevelDebug())
174
    if (logLevelDebug())
175
        qDebug() << "seed dispersal finished.";
175
        qDebug() << "seed dispersal finished.";
176
}
176
}
177
177
178
/** newYear is called by Model::runYear at the beginning of a year before any growth occurs.
178
/** newYear is called by Model::runYear at the beginning of a year before any growth occurs.
179
  This is used for various initializations, e.g. to clear seed dispersal maps
179
  This is used for various initializations, e.g. to clear seed dispersal maps
180
  */
180
  */
181
void SpeciesSet::newYear()
181
void SpeciesSet::newYear()
182
{
182
{
183
    if (!GlobalSettings::instance()->model()->settings().regenerationEnabled)
183
    if (!GlobalSettings::instance()->model()->settings().regenerationEnabled)
184
        return;
184
        return;
185
    foreach(Species *s, mActiveSpecies) {
185
    foreach(Species *s, mActiveSpecies) {
186
        s->newYear();
186
        s->newYear();
187
    }
187
    }
188
}
188
}
189
189
190
/** retrieves variables from the datasource available during the setup of species.
190
/** retrieves variables from the datasource available during the setup of species.
191
  */
191
  */
192
QVariant SpeciesSet::var(const QString& varName)
192
QVariant SpeciesSet::var(const QString& varName)
193
{
193
{
194
    Q_ASSERT(mSetupQuery!=0);
194
    Q_ASSERT(mSetupQuery!=0);
195
195
196
    int idx = mSetupQuery->record().indexOf(varName);
196
    int idx = mSetupQuery->record().indexOf(varName);
197
    if (idx>=0)
197
    if (idx>=0)
198
        return mSetupQuery->value(idx);
198
        return mSetupQuery->value(idx);
199
    throw IException(QString("SpeciesSet: variable not set: %1").arg(varName));
199
    throw IException(QString("SpeciesSet: variable not set: %1").arg(varName));
200
    //throw IException(QString("load species parameter: field %1 not found!").arg(varName));
200
    //throw IException(QString("load species parameter: field %1 not found!").arg(varName));
201
    // lookup in defaults
201
    // lookup in defaults
202
    //qDebug() << "variable" << varName << "not found - using default.";
202
    //qDebug() << "variable" << varName << "not found - using default.";
203
    //return GlobalSettings::instance()->settingDefaultValue(varName);
203
    //return GlobalSettings::instance()->settingDefaultValue(varName);
204
}
204
}
205
205
206
inline double SpeciesSet::nitrogenResponse(const double &availableNitrogen, const double &NA, const double &NB) const
206
inline double SpeciesSet::nitrogenResponse(const double &availableNitrogen, const double &NA, const double &NB) const
207
{
207
{
208
    if (availableNitrogen<=NB)
208
    if (availableNitrogen<=NB)
209
        return 0;
209
        return 0;
210
    double x = 1. - exp(NA * (availableNitrogen-NB));
210
    double x = 1. - exp(NA * (availableNitrogen-NB));
211
    return x;
211
    return x;
212
}
212
}
213
213
214
/// calculate nitrogen response for a given amount of available nitrogen and a respone class
214
/// calculate nitrogen response for a given amount of available nitrogen and a respone class
215
/// for fractional values, the response value is interpolated between the fixedly defined classes (1,2,3)
215
/// for fractional values, the response value is interpolated between the fixedly defined classes (1,2,3)
216
double SpeciesSet::nitrogenResponse(const double availableNitrogen, const double &responseClass) const
216
double SpeciesSet::nitrogenResponse(const double availableNitrogen, const double &responseClass) const
217
{
217
{
218
    double value1, value2, value3;
218
    double value1, value2, value3;
219
    if (responseClass>2.) {
219
    if (responseClass>2.) {
220
        if (responseClass==3.)
220
        if (responseClass==3.)
221
            return nitrogenResponse(availableNitrogen, mNitrogen_3a, mNitrogen_3b);
221
            return nitrogenResponse(availableNitrogen, mNitrogen_3a, mNitrogen_3b);
222
        else {
222
        else {
223
            // interpolate between 2 and 3
223
            // interpolate between 2 and 3
224
            value2 = nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
224
            value2 = nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
225
            value3 = nitrogenResponse(availableNitrogen, mNitrogen_3a, mNitrogen_3b);
225
            value3 = nitrogenResponse(availableNitrogen, mNitrogen_3a, mNitrogen_3b);
226
            return value2 + (responseClass-2)*(value3-value2);
226
            return value2 + (responseClass-2)*(value3-value2);
227
        }
227
        }
228
    }
228
    }
229
    if (responseClass==2)
229
    if (responseClass==2)
230
        return nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
230
        return nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
231
    if (responseClass==1)
231
    if (responseClass==1)
232
        return nitrogenResponse(availableNitrogen, mNitrogen_1a, mNitrogen_1b);
232
        return nitrogenResponse(availableNitrogen, mNitrogen_1a, mNitrogen_1b);
233
    // last ressort: interpolate between 1 and 2
233
    // last ressort: interpolate between 1 and 2
234
    value1 = nitrogenResponse(availableNitrogen, mNitrogen_1a, mNitrogen_1b);
234
    value1 = nitrogenResponse(availableNitrogen, mNitrogen_1a, mNitrogen_1b);
235
    value2 = nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
235
    value2 = nitrogenResponse(availableNitrogen, mNitrogen_2a, mNitrogen_2b);
236
    return value1 + (responseClass-1)*(value2-value1);
236
    return value1 + (responseClass-1)*(value2-value1);
237
}
237
}
238
238
239
/** calculation for the CO2 response for the ambientCO2 for the water- and nitrogen responses given.
239
/** calculation for the CO2 response for the ambientCO2 for the water- and nitrogen responses given.
240
    The calculation follows Friedlingsstein 1995 (see also links to equations in code)
240
    The calculation follows Friedlingsstein 1995 (see also links to equations in code)
241
    see also: http://iland.boku.ac.at/CO2+response
241
    see also: http://iland.boku.ac.at/CO2+response
242
    @param ambientCO2 current CO2 concentration (ppm)
242
    @param ambientCO2 current CO2 concentration (ppm)
243
    @param nitrogenResponse (yearly) nitrogen response of the species
243
    @param nitrogenResponse (yearly) nitrogen response of the species
244
    @param soilWaterReponse soil water response (mean value for a month)
244
    @param soilWaterReponse soil water response (mean value for a month)
245
*/
245
*/
246
double SpeciesSet::co2Response(const double ambientCO2, const double nitrogenResponse, const double soilWaterResponse) const
246
double SpeciesSet::co2Response(const double ambientCO2, const double nitrogenResponse, const double soilWaterResponse) const
247
{
247
{
248
    if (nitrogenResponse==0)
248
    if (nitrogenResponse==0)
249
        return 0.;
249
        return 0.;
250
250
251
    double co2_water = 2. - soilWaterResponse;
251
    double co2_water = 2. - soilWaterResponse;
252
    double beta = mCO2beta0 * co2_water * nitrogenResponse;
252
    double beta = mCO2beta0 * co2_water * nitrogenResponse;
253
253
254
    double r =1. +  M_LN2 * beta; // NPP increase for a doubling of atmospheric CO2 (Eq. 17)
254
    double r =1. +  M_LN2 * beta; // NPP increase for a doubling of atmospheric CO2 (Eq. 17)
255
255
256
    // fertilization function (cf. Farquhar, 1980) based on Michaelis-Menten expressions
256
    // fertilization function (cf. Farquhar, 1980) based on Michaelis-Menten expressions
257
    double deltaC = mCO2base - mCO2comp;
257
    double deltaC = mCO2base - mCO2comp;
258
    double K2 = ((2*mCO2base - mCO2comp) - r*deltaC ) / ((r-1.)*deltaC*(2*mCO2base - mCO2comp)); // Eq. 16
258
    double K2 = ((2*mCO2base - mCO2comp) - r*deltaC ) / ((r-1.)*deltaC*(2*mCO2base - mCO2comp)); // Eq. 16
259
    double K1 = (1. + K2*deltaC) / deltaC;
259
    double K1 = (1. + K2*deltaC) / deltaC;
260
260
261
    double response = mCO2p0 * K1*(ambientCO2 - mCO2comp) / (1 + K2*(ambientCO2-mCO2comp)); // Eq. 16
261
    double response = mCO2p0 * K1*(ambientCO2 - mCO2comp) / (1 + K2*(ambientCO2-mCO2comp)); // Eq. 16
262
    return response;
262
    return response;
263
263
264
}
264
}
265
265
266
/** calculates the lightResponse based on a value for LRI and the species lightResponseClass.
266
/** calculates the lightResponse based on a value for LRI and the species lightResponseClass.
267
    LightResponse is classified from 1 (very shade inolerant) and 5 (very shade tolerant) and interpolated for values between 1 and 5.
267
    LightResponse is classified from 1 (very shade inolerant) and 5 (very shade tolerant) and interpolated for values between 1 and 5.
268
    Returns a value between 0..1
268
    Returns a value between 0..1
269
    @sa http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth */
269
    @sa http://iland.boku.ac.at/allocation#reserve_and_allocation_to_stem_growth */
270
double SpeciesSet::lightResponse(const double lightResourceIndex, const double lightResponseClass) const
270
double SpeciesSet::lightResponse(const double lightResourceIndex, const double lightResponseClass) const
271
{
271
{
272
    double low = mLightResponseIntolerant.calculate(lightResourceIndex);
272
    double low = mLightResponseIntolerant.calculate(lightResourceIndex);
273
    double high = mLightResponseTolerant.calculate(lightResourceIndex);
273
    double high = mLightResponseTolerant.calculate(lightResourceIndex);
274
    double result = low + 0.25*(lightResponseClass-1.)*(high-low);
274
    double result = low + 0.25*(lightResponseClass-1.)*(high-low);
275
    return limit(result, 0., 1.);
275
    return limit(result, 0., 1.);
276
276
277
}
277
}
278
278
279
279
280
280
281
 
281