-
Notifications
You must be signed in to change notification settings - Fork 3
/
PP-GrowthOptimizer.js
455 lines (449 loc) · 24.3 KB
/
PP-GrowthOptimizer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
var UserDefinedProtocol = {
// -turbidostat settings
turbidostatODMin: 0.4,
turbidostatODMax: 0.425,
turbidostatODType: 720,
ODReadoutInterval: 60,
// -optimizer parameters
controlledParameter: 'none',
controlledParameterSteps: [[ 1100, 25 ], [ 440, 25 ], [ 55, 25 ]],
// -optimizer stability check
growthStatistics: true,
regressionODType: 680,
regressionCoDMin: 75,
stabilizationTimeMin: 12,
stabilizationTimeMax: 36,
growthRateEvalFrac: 2 / 3,
analyzedSteps: 6,
growthTrendMax: 1.5,
intervalOfConfidenceMax: 3.5,
// -peristaltic pump settings
peristalticPumpID: 5,
peristalticPumpSpeed: 100,
peristalticPumpSlowDownRange: 25,
peristalticPumpSlowDownFactor: 75,
// -advanced options
growthRateEvalDelay: 420,
groupGMS: theGroup
}
/* global
importPackage, java, Packages, theGroup, theAccessory, theExperiment, theLogger, ProtoConfig, ETrendFunction, result: true
*/
/**
* OD Regulator Using External/Additional Pump
*
* @script Peristaltic Pump - Automatic Growth Characterization
* @author CzechGlobe - Department of Adaptive Biotechnologies (JaCe)
* @copyright Jan Červený 2020(c)
* @license MIT
* @version 3.3.0
* @modified 14.8.2020 (JaCe)
*
* @notes For proper functionality of the script "OD Regulator" protocol has to be disabled as well as chosen
* controlled accessory protocols (i.e. Lights, Thermoregulation, GMS, Stirrer).
* The controlled pump has to be set to ID 5 to allow compatibility with other scripts
*
* -turbidostat settings
* @param {number} turbidostatODMin [AU] - Minimum OD/lower bound for OD regulator/turbidostat
* @param {number} turbidostatODMax [AU] - Maximum OD/upper bound for OD regulator/turbidostat
* @param {number} turbidostatODType [680/720/735] - OD sensor used for turbidostat control
* @param {number} ODReadoutInterval [s] - Defines how often is the OD measured
* -optimizer parameters
* @param {string} controlledParameter ['none'/'temperature'/'lights'/'GMS'/'stirrer'/'ODRange'] - Supported parameters to control by the script
* @param {array} controlledParameterSteps - List of values for the controlled parameter. Examples:
* temperature = [ 28, 32, 34, 30, 26, 22 ]; // [oC]
* lights = [[ 55, 25 ], [ 110, 25 ], [ 220, 25 ], [ 440, 25 ], [ 880,25 ]]; // [uE]
* GMS = [[ 9.6, 990 ], [ 19.6, 1880 ], [ 39.6, 1940 ]]; // [ml/min]
* stirrer = [ 30, 50, 65, 80, 95 ]; // [%] !!! works only with SW version 0.7.14 and later
* ODRange = [[0.4, 0.425], [0.2, 0.215], [0.1, 0.113]]; // [AU]
* -optimizer stability check
* @param {number} growthStatistics [true/false] - Enable or disable calculation of growth statistics. Note that the doubling time (Dt) calculation also includes information about the fit coefficient of determination (CoD in %), known as R-squared
* @param {number} regressionODType [680/720/735] - OD sensor used for doubling time determination
* @param {number} regressionCoDMin [%] - Minimum accpeted coefficient of determination for staility check evaluation (values below are ignored)
* @param {number} stabilizationTimeMin [h] - Minimum duration of each characterization step
* @param {number} stabilizationTimeMax [h] - Maximum duration of each characterization step
* @param {number} growthRateEvalFrac [0-1] - Defines whether to use particular fraction of the data points for doubling time determination.
* @param {number} analyzedSteps [-] - Number of steps to be analyzed for stability check
* @param {number} growthTrendMax [%] - Maximum growth speed trend in time
* @param {number} intervalOfConfidenceMax [%] - Maximum allowed percents of 95% Confidence Interval
* -peristaltic pump settings
* @param {number} peristalticPumpID [3-7] - Defines peristaltic pump ID set to the pump that is used for fresh media supply (quasi-continuous mode)
* @param {number} peristalticPumpSpeed [%] - Nominal pump speed used for dilution of the suspension
* @param {number} peristalticPumpSlowDownRange [%] - Lower range where the pump slows down
* @param {number} peristalticPumpSlowDownFactor [%] - Slow down factor for the pump
* -advanced options
* @param {number} growthRateEvalDelay [s] - Time after dilution where data for doubling time determination are ignored. By default growthRateEvalFrac, i.e. only limited fraction of the data points is used for calculations.
* This is to prevent influence of post dilution effect on doubling time evaluation. If 0 or false, growthRateEvalDelay is used instead. Note that to completely disable data limitation you need to set both growthRateEvalFrac and growthRateEvalDelay to 0.
* @param {string} groupGMS - Identifies the group that contains Gas Mixing System. System value - do not change unless sure what you are doing!
*
* @return Flow of external/additional pump
*
*/
// Libraries import
importPackage(java.util)
importPackage(java.lang)
importPackage(Packages.psi.bioreactor.core.protocol)
importPackage(Packages.psi.bioreactor.core.regression)
// Inicialization of the control script
if (!theAccessory.context().getInt('initiated', 0)) {
try {
theAccessory.context().clear()
theAccessory.context().put('stabilizedTimeMax', theExperiment.getDurationSec() + UserDefinedProtocol.stabilizationTimeMax * 3600)
var light1String
if (theGroup.getAccessory('actinic-lights.light-Blue') === null) {
light1String = 'actinic-lights.light-White'
} else {
light1String = 'actinic-lights.light-Blue'
}
theAccessory.context().put('light1String', light1String)
switch (UserDefinedProtocol.controlledParameter) {
case 'lights':
if (theGroup.getAccessory('actinic-lights.light-Red').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable RED LIGHT protocol')
}
if (light1String === 'actinic-lights.light-Blue') {
if (theGroup.getAccessory('actinic-lights.light-Blue').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable BLUE LIGHT protocol')
}
} else if (theGroup.getAccessory('actinic-lights.light-White').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable WHITE LIGHT protocol')
}
break
case 'temperature':
if (theGroup.getAccessory('thermo.thermo-reg').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable THERMOREGULATOR protocol')
}
break
case 'GMS':
if (UserDefinedProtocol.groupGMS.getAccessory('gas-mixer.valve-0-reg').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable GMS CO2 protocol')
}
if (UserDefinedProtocol.groupGMS.getAccessory('gas-mixer.valve-1-reg').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable GMS Air/N2 protocol')
}
break
case 'stirrer':
if (theGroup.getAccessory('pwm.stirrer').getProtoConfigValue()) {
theExperiment.addEvent('!!! Disable STIRRER protocol')
}
break
case 'ODRange':
break
case 'none':
break
default:
theExperiment.addEvent('!!! Unknown parameter set for control - check controlledParameter setting')
}
if (UserDefinedProtocol.turbidostatODType === 680 || UserDefinedProtocol.regressionODType === 680) {
if (Number(theGroup.getAccessory('od-sensors.od-680').getProtoConfigValue()) !== UserDefinedProtocol.ODReadoutInterval) {
theExperiment.addEvent('!!! OD680 measurement protocol is set to wrong interval. Please correct it !!!')
}
}
if (UserDefinedProtocol.turbidostatODType === 720 || UserDefinedProtocol.regressionODType === 720 || UserDefinedProtocol.turbidostatODType === 735 || UserDefinedProtocol.regressionODType === 735 ) {
var OD7XYString
if (theGroup.getAccessory('od-sensors.od-720') === null) {
OD7XYString = 'od-sensors.od-735'
if (Number(theGroup.getAccessory(OD7XYString).getProtoConfigValue()) !== UserDefinedProtocol.ODReadoutInterval) {
theExperiment.addEvent('!!! OD735 measurement protocol is set to wrong interval. Please correct it !!!')
}
} else {
OD7XYString = 'od-sensors.od-720'
if (Number(theGroup.getAccessory(OD7XYString).getProtoConfigValue()) !== UserDefinedProtocol.ODReadoutInterval) {
theExperiment.addEvent('!!! OD720 measurement protocol is set to wrong interval. Please correct it !!!')
}
}
theAccessory.context().put('OD7XYString', OD7XYString)
}
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[0])
theAccessory.context().put('initiated', 1)
debugLogger('Peristaltic Pump - Growth Optimizer initialization successful.')
} catch (error) {
debugLogger('Initialization ERROR. ' + error.name + ' : ' + error.message)
}
}
// Set the pump
try {
var pumpState = !isNaN(theAccessory.getValue())
// Check whether O2 evolution and respiration measurement mode is active
if (theGroup.getAccessory('probes.o2').context().getInt('modeO2EvolResp', 0)) {
if (pumpState) {
theAccessory.context().put('pumpSuspended', 1)
result = theAccessory.getMin()
}
} else if (theAccessory.context().getInt('pumpSuspended', 0)) {
theAccessory.context().put('pumpSuspended', 0)
result = theAccessory.getMax() * UserDefinedProtocol.peristalticPumpSpeed / 100
} else {
result = controlPump()
}
} catch (error) {
debugLogger('O2 evol./resp. activity check ERROR. ' + error.name + ' : ' + error.message)
}
/**
function setODSensorString (ODType) {
// Set ODtype = [turbidostat, regression]
switch (UserDefinedProtocol[ODType+'ODType']) {
case 680:
odString = 'od-sensors.od-680'
break
case 720:
odString = 'od-sensors.od-720'
break
case 735:
odString = 'od-sensors.od-735'
break
default:
odString = 'od-sensors.od-680'
}
switch (ODType) {
case 'turbidostat':
text = 'Sensor'
break
case 'regression':
text = 'SensorRegression'
break
default:
text = 'Sensor'
}
eval('od' + text + 'String = ' + odString)
debugLogger('OD sensor string set')
}
*/
// Common functions
function round (number, decimals) {
// Rounding specific decimal point number
return +(Math.round(number + 'e+' + decimals) + 'e-' + decimals)
}
function debugLogger (message, status) {
if ((status === undefined) || (status === 1) || (status === 'on')) {
theLogger.info('[' + theGroup.getName() + '] ' + message)
} else {
return null
}
}
function controlParameter (parameter, values) {
// Control accessory functions
if ((parameter === undefined) || (parameter === 'none') || (values === undefined)) {
return null
}
var unit
switch (parameter) {
case 'lights':
var light0 = theGroup.getAccessory('actinic-lights.light-Red')
var light1 = theGroup.getAccessory(theAccessory.context().get('light1String', 'actinic-lights.light-Blue'))
unit = ' uE'
light0.setRunningProtoConfig(new ProtoConfig(Number(values[0]))) // Red
light1.setRunningProtoConfig(new ProtoConfig(Number(values[1]))) // Blue || White
debugLogger('Lights changed.')
break
case 'temperature':
var thermoreg = theGroup.getAccessory('thermo.thermo-reg')
unit = String.fromCharCode(176) + 'C'
thermoreg.setRunningProtoConfig(new ProtoConfig(Number(values)))
debugLogger('Temperature changed.')
break
case 'GMS':
var valve0 = UserDefinedProtocol.groupGMS.getAccessory('gas-mixer.valve-0-reg') // CO2
var valve1 = UserDefinedProtocol.groupGMS.getAccessory('gas-mixer.valve-1-reg') // Air
unit = ' ml/min'
valve0.setRunningProtoConfig(new ProtoConfig(Number(values[0])))
valve1.setRunningProtoConfig(new ProtoConfig(Number(values[1])))
var flowAir = valve0.getProtoConfigValue()
var flowCO2 = valve1.getProtoConfigValue()
debugLogger('GMS settings changed. Gas Mixing set to Air flow ' + round(flowAir, 2) + ' ml/min and CO2 flow ' + round(flowCO2, 2) + ' ml/min (' + round((flowCO2 / (flowCO2 + flowAir) + 400 / 1e6) * 100, 1) + '%)')
break
case 'stirrer':
var stirrer = theGroup.getAccessory('pwm.stirrer')
unit = '%'
stirrer.setRunningProtoConfig(new ProtoConfig(Number(values)))
debugLogger('Stirrer changed.')
break
case 'ODRange':
theAccessory.context().put('odMinModifier', Number(values[0]) / UserDefinedProtocol.turbidostatODMin)
theAccessory.context().put('odMaxModifier', Number(values[1]) / UserDefinedProtocol.turbidostatODMax)
unit = ' AU'
debugLogger('Turbidostat OD range changed.')
break
default:
return null
}
theAccessory.context().put('controlledParameterText', parameter + ' ' + (Array.isArray(values) ? values.join(' and ') : values) + unit)
theExperiment.addEvent(parameter[0].toUpperCase() + parameter.slice(1) + ' changed to ' + (Array.isArray(values) ? values.join(' and ') : values) + unit)
}
// Control activity of the peristaltic pump
function controlPump () {
// Following code ready for functional implementation
// setODSensorString("turbidostat");
// setODSensorString("regression");
var odSensorString, odSensorRegressionString
switch (UserDefinedProtocol.turbidostatODType) {
case 680:
odSensorString = 'od-sensors.od-680'
break
default:
odSensorString = theAccessory.context().get('OD7XYString', 'od-sensors.od-720')
}
switch (UserDefinedProtocol.regressionODType) {
case 680:
odSensorRegressionString = 'od-sensors.od-680'
break
default:
odSensorRegressionString = theAccessory.context().get('RegOD7XYString', 'od-sensors.od-720')
}
var odSensor = theGroup.getAccessory(odSensorString)
var odSensorRegression = theGroup.getAccessory(odSensorRegressionString)
if (odSensor === null || odSensor.hasError()) {
return null // pump not influenced
}
var odValue = odSensor.getValue()
var odLast = theAccessory.context().getDouble('odLast', 0.0)
var odNoise = theAccessory.context().getInt('odNoise', 1)
var odMinModifier = theAccessory.context().getDouble('odMinModifier', 1.0)
var odMaxModifier = theAccessory.context().getDouble('odMaxModifier', 1.0)
var changeCounter = theAccessory.context().getInt('changeCounter', 0)
var stepCounter = theAccessory.context().getInt('stepCounter', 0)
// Check for OD noise/overshots and primitive OD averaging
if (!isNaN(odValue) && (round(odValue, 3) !== round(odLast, 3))) {
if (odNoise) {
theAccessory.context().put('odNoise', 0)
theAccessory.context().put('odLast', odValue)
return null
}
if (pumpState || (Math.abs(1 - odValue / odLast) < 0.04)) {
odValue = (odValue + odLast) / 2
theAccessory.context().put('odLast', odValue)
} else {
theAccessory.context().put('odNoise', 1)
theAccessory.context().put('odLast', odValue)
return null
}
} else {
return null
}
// Check for reversed OD range
if (UserDefinedProtocol.turbidostatODMin > UserDefinedProtocol.turbidostatODMax) {
UserDefinedProtocol.turbidostatODMin = (UserDefinedProtocol.turbidostatODMax - UserDefinedProtocol.turbidostatODMin) + (UserDefinedProtocol.turbidostatODMax = UserDefinedProtocol.turbidostatODMin)
debugLogger('OD range reversed.', 0)
}
if (theAccessory.context().getInt('stabilizedTimeMax', 0) <= Number(theExperiment.getDurationSec()) && (stepCounter !== 0)) {
theAccessory.context().put('stabilizedTimeMax', theExperiment.getDurationSec() + UserDefinedProtocol.stabilizationTimeMax * 3600)
if (UserDefinedProtocol.controlledParameterSteps.length > 1) {
if (changeCounter < (UserDefinedProtocol.controlledParameterSteps.length - 1)) {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[++changeCounter])
theAccessory.context().put('changeCounter', changeCounter)
} else if (changeCounter < 2 * (UserDefinedProtocol.controlledParameterSteps.length - 1)) {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[2 * (UserDefinedProtocol.controlledParameterSteps.length - 1) - (++changeCounter)])
theAccessory.context().put('changeCounter', changeCounter)
} else {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[1])
theAccessory.context().put('changeCounter', 1)
}
theAccessory.context().remove('stepCounter')
theAccessory.context().remove('expDuration')
theAccessory.context().remove('stepDoublingTime')
theAccessory.context().remove('stabilizedTime')
}
}
// Start step growth rate evaluation
if (((odValue > (UserDefinedProtocol.turbidostatODMax * odMaxModifier)) && !pumpState)) {
theAccessory.context().put('modeDilution', 1)
theAccessory.context().put('modeStabilized', 0)
// var stepCounter = theAccessory.context().getInt('stepCounter', 0)
var expDuration = theAccessory.context().get('expDuration', 0.0)
var stepDuration = theAccessory.context().get('stepDuration', 0.0)
var stepDoublingTime = theAccessory.context().get('stepDoublingTime', 0.0)
var stabilizedTime = theAccessory.context().getInt('stabilizedTime', 0)
// var stabilizedTimeMax = theAccessory.context().getInt('stabilizedTimeMax', 0)
if (!Array.isArray(expDuration)) {
stepCounter = 0
expDuration = []; stepDuration = []; stepDoublingTime = []
theAccessory.context().put('expDuration', expDuration)
theAccessory.context().put('stepDuration', stepDuration)
theAccessory.context().put('stepDoublingTime', stepDoublingTime)
theAccessory.context().put('stabilizedTime', theExperiment.getDurationSec() + UserDefinedProtocol.stabilizationTimeMin * 3600)
theAccessory.context().put('stabilizedTimeMax', theExperiment.getDurationSec() + UserDefinedProtocol.stabilizationTimeMax * 3600)
odSensorRegression.getDataHistory().setCapacity(600)
}
expDuration[stepCounter] = theExperiment.getDurationSec()
stepDuration[stepCounter] = expDuration[stepCounter] - theAccessory.context().getInt('lastPumpStop', expDuration[stepCounter])
if ((stepDuration[stepCounter] > 0) && UserDefinedProtocol.growthStatistics) {
var DHCapacity = (Math.floor(stepDuration[stepCounter] / UserDefinedProtocol.ODReadoutInterval) - 3) > 0 ? (Math.floor(stepDuration[stepCounter] / UserDefinedProtocol.ODReadoutInterval) - 3) : 60
var regCoefExp = odSensorRegression.getDataHistory().regression(ETrendFunction.EXP, Math.ceil(DHCapacity - (UserDefinedProtocol.growthRateEvalFrac ? DHCapacity * (UserDefinedProtocol.growthRateEvalFrac / 100) : UserDefinedProtocol.growthRateEvalDelay / UserDefinedProtocol.ODReadoutInterval)))
debugLogger('Growth parameters: A=' + regCoefExp[0] +', B=' + regCoefExp[1] + ', R2=' + regCoefExp[2])
if (Number(regCoefExp[2]) >= UserDefinedProtocol.regressionCoDMin / 100) {
stepDoublingTime[stepCounter] = (1 / (Number(regCoefExp[1]) * 3600 * 10)) * Math.LN2
theAccessory.context().put('stepCounter', ++stepCounter)
}
theExperiment.addEvent('Doubling time of the step was ' + round((1 / (Number(regCoefExp[1]) * 3600 * 10)) * Math.LN2, 2) + ' h (CoD ' + round(Number(regCoefExp[2]) * 100, 1) + '%)')
if (stepCounter >= UserDefinedProtocol.analyzedSteps) {
var stepDoublingTimeAvg = 0
var stepDoublingTimeSD = 0
var stepDoublingTimeIC95 = 0
var stepTrend = 0
// var stepCoD = 0
var sumXY = 0
var sumX = 0
var sumY = 0
var sumX2 = 0
// var sumY2 = 0
// Average of steps doubling time
for (var i = (stepCounter - 1); i >= (stepCounter - UserDefinedProtocol.analyzedSteps); i--) {
stepDoublingTimeAvg += Number(stepDoublingTime[i])
}
stepDoublingTimeAvg /= UserDefinedProtocol.analyzedSteps
// IC95 of steps doubling time
for (i = (stepCounter - 1); i >= (stepCounter - UserDefinedProtocol.analyzedSteps); i--) {
stepDoublingTimeSD += Math.pow(stepDoublingTime[i] - stepDoublingTimeAvg, 2)
}
stepDoublingTimeSD = Math.sqrt(stepDoublingTimeSD / UserDefinedProtocol.analyzedSteps)
stepDoublingTimeIC95 = stepDoublingTimeSD / Math.sqrt(UserDefinedProtocol.analyzedSteps) * 1.96
// Trend of steps doubling time
for (i = (stepCounter - 1); i >= (stepCounter - UserDefinedProtocol.analyzedSteps); i--) {
sumX += Number(expDuration[i])
sumX2 += Math.pow(expDuration[i], 2)
sumY += Number(stepDoublingTime[i])
// sumY2 += Math.pow(stepDoublingTime[i], 2)
sumXY += Number(expDuration[i]) * Number(stepDoublingTime[i])
}
stepTrend = (UserDefinedProtocol.analyzedSteps * sumXY - sumX * sumY) / (UserDefinedProtocol.analyzedSteps * sumX2 - Math.pow(sumX, 2)) * 3600
// stepCoD = (UserDefinedProtocol.analyzedSteps * sumXY - sumX * sumY) / (Math.sqrt((UserDefinedProtocol.analyzedSteps * sumX2 - Math.pow(sumX, 2)) * (UserDefinedProtocol.analyzedSteps * sumY2 - Math.pow(sumY, 2))))
theExperiment.addEvent('Steps doubling time Avg: ' + round(stepDoublingTimeAvg, 2) + ' h, IC95 ' + round(stepDoublingTimeIC95, 2) + ' h (' + round(stepDoublingTimeIC95 / stepDoublingTimeAvg * 100, 1) + '%) with ' + round(stepTrend, 2) + ' h/h trend (' + round(stepTrend / stepDoublingTimeAvg * 100, 1) + '%)')
// Growth stability test and parameters control
if (((stepDoublingTimeIC95 / stepDoublingTimeAvg) <= (UserDefinedProtocol.intervalOfConfidenceMax / 100) && (Math.abs(stepTrend / stepDoublingTimeAvg) <= (UserDefinedProtocol.growthTrendMax / 100)) && (stabilizedTime <= Number(theExperiment.getDurationSec())))) {
theAccessory.context().put('modeStabilized', 1)
// changeCounter = theAccessory.context().getInt('changeCounter', 0)
theExperiment.addEvent('*** Stabilized doubling time Dt (' + theGroup.getAccessory('thermo.thermo-reg').getValue() + String.fromCharCode(176) + 'C, ' + theAccessory.context().getString('controlledParameterText', 'no parameter') + ') is ' + round(stepDoublingTimeAvg, 2) + String.fromCharCode(177) + round(stepDoublingTimeIC95, 2) + ' h (IC95)')
if (UserDefinedProtocol.controlledParameterSteps.length > 1) {
if (changeCounter < (UserDefinedProtocol.controlledParameterSteps.length - 1)) {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[++changeCounter])
theAccessory.context().put('changeCounter', changeCounter)
} else if (changeCounter < 2 * (UserDefinedProtocol.controlledParameterSteps.length - 1)) {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[2 * (UserDefinedProtocol.controlledParameterSteps.length - 1) - (++changeCounter)])
theAccessory.context().put('changeCounter', changeCounter)
} else {
controlParameter(UserDefinedProtocol.controlledParameter, UserDefinedProtocol.controlledParameterSteps[1])
theAccessory.context().put('changeCounter', 1)
}
theAccessory.context().put('stabilizedTimeMax', theExperiment.getDurationSec() + UserDefinedProtocol.stabilizationTimeMax * 3600)
theAccessory.context().remove('stepCounter')
theAccessory.context().remove('expDuration')
theAccessory.context().remove('stepDoublingTime')
theAccessory.context().remove('stabilizedTime')
}
}
}
}
debugLogger('Pump max speed.')
return theAccessory.getMax() * UserDefinedProtocol.peristalticPumpSpeed / 100 // fast
} else if ((odValue <= (UserDefinedProtocol.turbidostatODMin * odMinModifier)) && pumpState) {
theAccessory.context().put('modeDilution', 0)
theAccessory.context().put('lastPumpStop', theExperiment.getDurationSec())
debugLogger('Pump stopped.')
return ProtoConfig.OFF // pump off
} else if ((odValue <= (UserDefinedProtocol.turbidostatODMin * odMinModifier + ((UserDefinedProtocol.turbidostatODMax * odMaxModifier) - (UserDefinedProtocol.turbidostatODMin * odMinModifier)) * UserDefinedProtocol.peristalticPumpSlowDownRange / 100)) && pumpState) {
debugLogger('Pump low speed.', 0)
return theAccessory.getMax() * UserDefinedProtocol.peristalticPumpSpeed / 100 * UserDefinedProtocol.peristalticPumpSlowDownFactor / 100 // slow down the pump
} else {
return null // pump not influenced
}
}