Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 :
50 : #include <EnergyPlus/Construction.hh>
51 : #include <EnergyPlus/Data/EnergyPlusData.hh>
52 : #include <EnergyPlus/DataDaylighting.hh>
53 : #include <EnergyPlus/DataEnvironment.hh>
54 : #include <EnergyPlus/DataHVACGlobals.hh>
55 : #include <EnergyPlus/DataHeatBalSurface.hh>
56 : #include <EnergyPlus/DataHeatBalance.hh>
57 : #include <EnergyPlus/DataIPShortCuts.hh>
58 : #include <EnergyPlus/DataSurfaces.hh>
59 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
60 : #include <EnergyPlus/DataZoneEquipment.hh>
61 : #include <EnergyPlus/DaylightingManager.hh>
62 : #include <EnergyPlus/EMSManager.hh>
63 : #include <EnergyPlus/General.hh>
64 : #include <EnergyPlus/GeneralRoutines.hh>
65 : #include <EnergyPlus/HeatBalanceInternalHeatGains.hh>
66 : #include <EnergyPlus/IndoorGreen.hh>
67 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
68 : #include <EnergyPlus/OutputProcessor.hh>
69 : #include <EnergyPlus/Psychrometrics.hh>
70 : #include <EnergyPlus/ScheduleManager.hh>
71 : #include <EnergyPlus/UtilityRoutines.hh>
72 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
73 : #include <EnergyPlus/api/datatransfer.h>
74 :
75 : namespace EnergyPlus {
76 :
77 : namespace IndoorGreen {
78 : // Module containing the routines dealing with the Indoor Living Walls
79 : static constexpr std::array<std::string_view, static_cast<int>(ETCalculationMethod::Num)> etCalculationMethodsUC = {"PENMAN-MONTEITH",
80 : "STANGHELLINI"};
81 : static constexpr std::array<std::string_view, static_cast<int>(LightingMethod::Num)> lightingMethodsUC = {"LED", "DAYLIGHT", "LED-DAYLIGHT"};
82 :
83 248593 : void SimIndoorGreen(EnergyPlusData &state)
84 : {
85 : // PURPOSE OF THIS SUBROUTINE:
86 : // This subroutine simulates the thermal performance of indoor living walls including the grow lights.
87 : // This subroutine interacts with inside surface heat balance, zone air heat balance and zone air moisture balance in EnergyPlus.
88 248593 : auto const &lw = state.dataIndoorGreen;
89 248593 : if (lw->getInputFlag) {
90 102 : bool ErrorsFound(false);
91 102 : const char *RoutineName("IndoorLivingWall: "); // include trailing blank space
92 102 : GetIndoorGreenInput(state, ErrorsFound);
93 102 : if (ErrorsFound) {
94 0 : ShowFatalError(state, format("{}Errors found in input. Program terminates.", RoutineName));
95 : }
96 102 : SetIndoorGreenOutput(state);
97 102 : lw->getInputFlag = false;
98 : }
99 248593 : if (lw->NumIndoorGreen > 0) {
100 0 : InitIndoorGreen(state);
101 : // Simulate evapotranspiration from indoor living walls
102 0 : ETModel(state);
103 : }
104 248593 : }
105 :
106 103 : void GetIndoorGreenInput(EnergyPlusData &state, bool &ErrorsFound)
107 : {
108 : // PURPOSE OF THIS SUBROUTINE:
109 : // Get the input for the indoor living wall objects and store the input data in the indoorGreens array.
110 :
111 103 : auto &s_lw = state.dataIndoorGreen;
112 103 : auto &s_ip = state.dataInputProcessing->inputProcessor;
113 103 : auto &s_ipsc = state.dataIPShortCut;
114 :
115 : static constexpr std::string_view RoutineName("GetIndoorLivingWallInput: ");
116 103 : std::string_view cCurrentModuleObject = "IndoorLivingWall"; // match the idd
117 : int NumNums; // Number of real numbers returned by GetObjectItem
118 : int NumAlphas; // Number of alphanumerics returned by GetObjectItem
119 : int IOStat; // Status flag from GetObjectItem
120 :
121 103 : s_lw->NumIndoorGreen = s_ip->getNumObjectsFound(state, cCurrentModuleObject);
122 103 : if (s_lw->NumIndoorGreen > 0) s_lw->indoorGreens.allocate(s_lw->NumIndoorGreen); // Allocate the IndoorGreen input data array
123 104 : for (int IndoorGreenNum = 1; IndoorGreenNum <= s_lw->NumIndoorGreen; ++IndoorGreenNum) {
124 1 : auto &ig = s_lw->indoorGreens(IndoorGreenNum);
125 2 : s_ip->getObjectItem(state,
126 : cCurrentModuleObject,
127 : IndoorGreenNum,
128 1 : s_ipsc->cAlphaArgs,
129 : NumAlphas,
130 1 : s_ipsc->rNumericArgs,
131 : NumNums,
132 : IOStat,
133 1 : s_ipsc->lNumericFieldBlanks,
134 1 : s_ipsc->lAlphaFieldBlanks,
135 1 : s_ipsc->cAlphaFieldNames,
136 1 : s_ipsc->cNumericFieldNames);
137 1 : ErrorObjectHeader eoh{RoutineName, cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
138 1 : Util::IsNameEmpty(state, s_ipsc->cAlphaArgs(1), cCurrentModuleObject, ErrorsFound);
139 1 : ig.Name = s_ipsc->cAlphaArgs(1);
140 1 : ig.SurfName = s_ipsc->cAlphaArgs(2);
141 1 : ig.SurfPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(2), state.dataSurface->Surface);
142 1 : if (ig.SurfPtr <= 0) {
143 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
144 0 : ErrorsFound = true;
145 : } else {
146 1 : if (state.dataSurface->Surface(ig.SurfPtr).insideHeatSourceTermSched != nullptr) {
147 0 : ShowSevereError(state,
148 0 : format("The indoor green surface {} has an Inside Face Heat Source Term Schedule defined. This surface cannot "
149 : "also be used for indoor green.",
150 0 : s_ipsc->cAlphaArgs(2)));
151 0 : ErrorsFound = true;
152 : }
153 1 : ig.ZonePtr = state.dataSurface->Surface(ig.SurfPtr).Zone;
154 1 : ig.SpacePtr = state.dataSurface->Surface(ig.SurfPtr).spaceNum;
155 :
156 1 : if (ig.ZonePtr <= 0 || ig.SpacePtr <= 0) {
157 0 : ShowSevereError(state,
158 0 : format("{}=\"{}\", invalid {} entered={}, {} is not assoicated with a thermal zone or space",
159 : RoutineName,
160 0 : s_ipsc->cAlphaArgs(1),
161 0 : s_ipsc->cAlphaFieldNames(2),
162 0 : s_ipsc->cAlphaArgs(2),
163 0 : s_ipsc->cAlphaArgs(2)));
164 0 : ErrorsFound = true;
165 2 : } else if (state.dataSurface->Surface(ig.SurfPtr).ExtBoundCond < 0 ||
166 1 : state.dataSurface->Surface(ig.SurfPtr).HeatTransferAlgorithm != DataSurfaces::HeatTransferModel::CTF) {
167 0 : ShowSevereError(state,
168 0 : format("{}=\"{}\", invalid {} entered={}, not a valid surface for indoor green module",
169 : RoutineName,
170 0 : s_ipsc->cAlphaArgs(1),
171 0 : s_ipsc->cAlphaFieldNames(2),
172 0 : s_ipsc->cAlphaArgs(2)));
173 0 : ErrorsFound = true;
174 : }
175 : }
176 :
177 1 : if ((ig.sched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(3))) == nullptr) {
178 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3));
179 0 : ErrorsFound = true;
180 1 : } else if (!ig.sched->checkMinVal(state, Clusive::In, 0.0)) {
181 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3), Clusive::In, 0.0);
182 0 : ErrorsFound = true;
183 : }
184 :
185 1 : ig.etCalculationMethod = ETCalculationMethod::PenmanMonteith; // default
186 1 : ig.etCalculationMethod = static_cast<ETCalculationMethod>(getEnumValue(etCalculationMethodsUC, s_ipsc->cAlphaArgs(4)));
187 1 : ig.lightingMethod = LightingMethod::LED; // default
188 1 : ig.lightingMethod = static_cast<LightingMethod>(getEnumValue(lightingMethodsUC, s_ipsc->cAlphaArgs(5)));
189 :
190 1 : switch (ig.lightingMethod) {
191 1 : case LightingMethod::LED: {
192 1 : if ((ig.ledSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(6))) == nullptr) {
193 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
194 0 : ErrorsFound = true;
195 1 : } else if (!ig.ledSched->checkMinVal(state, Clusive::In, 0.0)) {
196 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6), Clusive::In, 0.0);
197 0 : ErrorsFound = true;
198 : }
199 1 : } break;
200 0 : case LightingMethod::Daylighting: {
201 0 : ig.LightRefPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
202 0 : state.dataDayltg->DaylRefPt,
203 : &EnergyPlus::Dayltg::RefPointData::Name); // Field: Daylighting Reference Point Name
204 0 : ig.LightControlPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
205 0 : state.dataDayltg->daylightControl,
206 : &EnergyPlus::Dayltg::DaylightingControl::Name); // Field: Daylighting Control Name
207 0 : if (ig.LightControlPtr == 0) {
208 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
209 0 : ErrorsFound = true;
210 0 : continue;
211 : }
212 0 : } break;
213 0 : case LightingMethod::LEDDaylighting: {
214 0 : ig.LightRefPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
215 0 : state.dataDayltg->DaylRefPt,
216 : &EnergyPlus::Dayltg::RefPointData::Name); // Field: Daylighting Reference Point Name
217 0 : ig.LightControlPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
218 0 : state.dataDayltg->daylightControl,
219 : &EnergyPlus::Dayltg::DaylightingControl::Name); // Field: Daylighting Control Name
220 0 : if (ig.LightControlPtr == 0) {
221 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
222 0 : ErrorsFound = true;
223 0 : continue;
224 : }
225 :
226 0 : if ((ig.ledDaylightTargetSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(8))) == nullptr) {
227 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaArgs(8));
228 0 : ErrorsFound = true;
229 0 : } else if (!ig.ledDaylightTargetSched->checkMinVal(state, Clusive::In, 0.0)) {
230 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaArgs(8), Clusive::In, 0.0);
231 0 : ErrorsFound = true;
232 : }
233 0 : } break;
234 :
235 0 : default:
236 0 : break;
237 : }
238 :
239 1 : ig.LeafArea = s_ipsc->rNumericArgs(1);
240 1 : if (ig.LeafArea < 0) {
241 0 : ShowSevereError(state,
242 0 : format("{}=\"{}\", invalid {} entered={}",
243 : RoutineName,
244 0 : s_ipsc->cAlphaArgs(1),
245 0 : s_ipsc->cNumericFieldNames(1),
246 0 : s_ipsc->rNumericArgs(1)));
247 0 : ErrorsFound = true;
248 : }
249 1 : ig.LEDNominalPPFD = s_ipsc->rNumericArgs(2);
250 1 : if (ig.LEDNominalPPFD < 0) {
251 0 : ShowSevereError(state,
252 0 : format("{}=\"{}\", invalid {} entered={}",
253 : RoutineName,
254 0 : s_ipsc->cAlphaArgs(1),
255 0 : s_ipsc->cNumericFieldNames(2),
256 0 : s_ipsc->rNumericArgs(2)));
257 0 : ErrorsFound = true;
258 : }
259 1 : ig.LEDNominalEleP = s_ipsc->rNumericArgs(3);
260 1 : if (ig.LEDNominalEleP < 0) {
261 0 : ShowSevereError(state,
262 0 : format("{}=\"{}\", invalid {} entered={}",
263 : RoutineName,
264 0 : s_ipsc->cAlphaArgs(1),
265 0 : s_ipsc->cNumericFieldNames(3),
266 0 : s_ipsc->rNumericArgs(3)));
267 0 : ErrorsFound = true;
268 : }
269 1 : ig.LEDRadFraction = s_ipsc->rNumericArgs(4);
270 1 : if (ig.LEDRadFraction < 0 || ig.LEDRadFraction > 1.0) {
271 0 : ShowSevereError(state,
272 0 : format("{}=\"{}\", invalid {} entered={}",
273 : RoutineName,
274 0 : s_ipsc->cAlphaArgs(1),
275 0 : s_ipsc->cNumericFieldNames(4),
276 0 : s_ipsc->rNumericArgs(4)));
277 0 : ErrorsFound = true;
278 : }
279 1 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
280 0 : SetupEMSActuator(state, "IndoorLivingWall", ig.Name, "Evapotranspiration Rate", "[kg_m2s]", ig.EMSETCalOverrideOn, ig.EMSET);
281 : } // EMS and API
282 : }
283 103 : }
284 :
285 102 : void SetIndoorGreenOutput(EnergyPlusData &state)
286 : {
287 : // Set up output variables
288 102 : auto &lw = state.dataIndoorGreen;
289 102 : for (int IndoorGreenNum = 1; IndoorGreenNum <= lw->NumIndoorGreen; ++IndoorGreenNum) {
290 0 : auto &ig = lw->indoorGreens(IndoorGreenNum);
291 0 : SetupZoneInternalGain(state,
292 : ig.ZonePtr,
293 : ig.Name,
294 : DataHeatBalance::IntGainType::IndoorGreen,
295 : &ig.SensibleRate,
296 : nullptr,
297 : nullptr,
298 : &ig.LatentRate,
299 : nullptr,
300 : nullptr,
301 : nullptr);
302 :
303 0 : SetupOutputVariable(state,
304 : "Indoor Living Wall Plant Surface Temperature",
305 : Constant::Units::C,
306 0 : state.dataHeatBalSurf->SurfTempIn(ig.SurfPtr),
307 : OutputProcessor::TimeStepType::Zone,
308 : OutputProcessor::StoreType::Average,
309 0 : ig.Name);
310 0 : SetupOutputVariable(state,
311 : "Indoor Living Wall Sensible Heat Gain Rate",
312 : Constant::Units::W,
313 0 : ig.SensibleRate,
314 : OutputProcessor::TimeStepType::Zone,
315 : OutputProcessor::StoreType::Average,
316 0 : ig.Name);
317 0 : SetupOutputVariable(state,
318 : "Indoor Living Wall Latent Heat Gain Rate",
319 : Constant::Units::W,
320 0 : ig.LatentRate,
321 : OutputProcessor::TimeStepType::Zone,
322 : OutputProcessor::StoreType::Average,
323 0 : ig.Name);
324 0 : SetupOutputVariable(state,
325 : "Indoor Living Wall Evapotranspiration Rate",
326 : Constant::Units::kg_m2s,
327 0 : ig.ETRate,
328 : OutputProcessor::TimeStepType::Zone,
329 : OutputProcessor::StoreType::Average,
330 0 : ig.Name);
331 0 : SetupOutputVariable(state,
332 : "Indoor Living Wall Energy Rate Required For Evapotranspiration Per Unit Area",
333 : Constant::Units::W_m2,
334 0 : ig.LambdaET,
335 : OutputProcessor::TimeStepType::Zone,
336 : OutputProcessor::StoreType::Average,
337 0 : ig.Name);
338 0 : SetupOutputVariable(state,
339 : "Indoor Living Wall LED Operational PPFD",
340 : Constant::Units::umol_m2s,
341 0 : ig.LEDActualPPFD,
342 : OutputProcessor::TimeStepType::Zone,
343 : OutputProcessor::StoreType::Average,
344 0 : ig.Name);
345 0 : SetupOutputVariable(state,
346 : "Indoor Living Wall PPFD",
347 : Constant::Units::umol_m2s,
348 0 : ig.ZPPFD,
349 : OutputProcessor::TimeStepType::Zone,
350 : OutputProcessor::StoreType::Average,
351 0 : ig.Name);
352 0 : SetupOutputVariable(state,
353 : "Indoor Living Wall Vapor Pressure Deficit",
354 : Constant::Units::Pa,
355 0 : ig.ZVPD,
356 : OutputProcessor::TimeStepType::Zone,
357 : OutputProcessor::StoreType::Average,
358 0 : ig.Name);
359 0 : SetupOutputVariable(state,
360 : "Indoor Living Wall LED Sensible Heat Gain Rate",
361 : Constant::Units::W,
362 0 : ig.SensibleRateLED,
363 : OutputProcessor::TimeStepType::Zone,
364 : OutputProcessor::StoreType::Average,
365 0 : ig.Name);
366 0 : SetupOutputVariable(state,
367 : "Indoor Living Wall LED Operational Power",
368 : Constant::Units::W,
369 0 : ig.LEDActualEleP,
370 : OutputProcessor::TimeStepType::Zone,
371 : OutputProcessor::StoreType::Average,
372 0 : ig.Name);
373 0 : SetupOutputVariable(state,
374 : "Indoor Living Wall LED Electricity Energy",
375 : Constant::Units::J,
376 0 : ig.LEDActualEleCon,
377 : OutputProcessor::TimeStepType::Zone,
378 : OutputProcessor::StoreType::Sum,
379 0 : ig.Name,
380 : Constant::eResource::Electricity,
381 : OutputProcessor::Group::Building,
382 : OutputProcessor::EndUseCat::InteriorLights,
383 : "IndoorLivingWall", // End Use subcategory
384 0 : state.dataHeatBal->Zone(ig.ZonePtr).Name,
385 0 : state.dataHeatBal->Zone(ig.ZonePtr).Multiplier,
386 0 : state.dataHeatBal->Zone(ig.ZonePtr).ListMultiplier,
387 0 : state.dataHeatBal->space(ig.SpacePtr).spaceType);
388 : }
389 102 : }
390 :
391 0 : void InitIndoorGreen(EnergyPlusData const &state)
392 : {
393 : // Set the reporting variables to zero at each timestep.
394 0 : for (auto &ig : state.dataIndoorGreen->indoorGreens) {
395 0 : ig.SensibleRate = 0.0;
396 0 : ig.SensibleRateLED = 0.0;
397 0 : ig.LatentRate = 0.0;
398 0 : ig.ZCO2 = 400;
399 0 : ig.ZPPFD = 0;
400 : }
401 0 : }
402 :
403 0 : void ETModel(EnergyPlusData &state)
404 : {
405 : // PURPOSE OF THIS SUBROUTINE:
406 : // This subroutine is for the calculation of evapotranspiration effects from the Indoor Greenery System objects.
407 : // SUBROUTINE PARAMETER DEFINITIONS:
408 : static constexpr std::string_view RoutineName("ETModel: ");
409 0 : auto &lw = state.dataIndoorGreen;
410 : Real64 ZonePreTemp; // Indoor air temprature (C)
411 : Real64 ZonePreHum; // Indoor humidity ratio (kg moisture / kg dry air)
412 : Real64 ZoneNewTemp; // Indoor air temprature (C) after ET
413 : Real64 ZoneNewHum; // Indoor humidity ratio (kg moisture / kg dry air) after ET
414 : Real64 ZoneSatHum; // Saturated humidity ratio
415 : Real64 ZoneCO2; // Indoor zone co2 concentration (ppm)
416 : Real64 ZonePPFD; // Indoor net radiation (PPFD)
417 : Real64 ZoneVPD; // vapor pressure deficit (kpa); local variable
418 : Real64 Timestep; // s
419 : Real64 ETTotal; // kg
420 : Real64 rhoair; // kg/m3
421 : Real64 Tdp; // dew point temperature
422 : Real64 Twb; // wet bulb temperature
423 : Real64 HCons; // enthalpy (J/kg)
424 : Real64 HMid; // enthalpy 3rd point (J/kg)
425 : Real64 ZoneAirVol; // zone air volume (m3)
426 : Real64 LAI; // leaf area index, the ratio of one-side leaf area per unit plant growing area, maximum LAI =2 if LAI_cal>2.0
427 : Real64 LAI_Cal; // calculated leaf area index based on users's input on total leaf area
428 : Real64 OutPb; // outdoor pressure (kPa)
429 : Real64 vp; // actual vapor pressure of the air (kpa)
430 : Real64 vpSat; // saturated vapor pressure at air temperature (kpa)
431 0 : Timestep = state.dataHVACGlobal->TimeStepSysSec; // unit s
432 0 : for (int IndoorGreenNum = 1; IndoorGreenNum <= lw->NumIndoorGreen; ++IndoorGreenNum) {
433 0 : auto &ig = lw->indoorGreens(IndoorGreenNum);
434 0 : ZonePreTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ig.ZonePtr).ZT;
435 0 : ZonePreHum = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ig.ZonePtr).airHumRat;
436 0 : ZoneCO2 = 400;
437 0 : OutPb = state.dataEnvrn->OutBaroPress / 1000;
438 0 : Tdp = Psychrometrics::PsyTdpFnWPb(state, ZonePreHum, OutPb * 1000);
439 0 : vp = Psychrometrics::PsyPsatFnTemp(state, Tdp, RoutineName) / 1000;
440 0 : vpSat = Psychrometrics::PsyPsatFnTemp(state, ZonePreTemp, RoutineName) / 1000;
441 0 : ig.ZVPD = (vpSat - vp) * 1000; // Pa
442 0 : LAI_Cal = ig.LeafArea / state.dataSurface->Surface(ig.SurfPtr).Area;
443 0 : LAI = LAI_Cal;
444 0 : if (LAI_Cal > 2.0) {
445 0 : LAI = 2.0; // maximum LAI=2.0 in the surface heat balance
446 0 : ShowSevereError(state, format("Maximum indoor living wall leaf area index (LAI) =2.0 is used,calculated LAI is {}", LAI_Cal));
447 : }
448 0 : switch (ig.lightingMethod) {
449 0 : case LightingMethod::LED: {
450 0 : ig.ZPPFD = ig.ledSched->getCurrentVal() * ig.LEDNominalPPFD; // PPFD
451 0 : ig.LEDActualPPFD = ig.ZPPFD;
452 0 : ig.LEDActualEleP = ig.ledSched->getCurrentVal() * ig.LEDNominalEleP;
453 0 : ig.LEDActualEleCon = ig.LEDActualEleP * Timestep;
454 0 : } break;
455 0 : case LightingMethod::Daylighting: {
456 0 : ig.ZPPFD = 0;
457 0 : ig.LEDActualPPFD = 0;
458 0 : ig.LEDActualEleP = 0;
459 0 : ig.LEDActualEleCon = 0;
460 0 : if (!state.dataDayltg->CalcDayltghCoefficients_firstTime && state.dataEnvrn->SunIsUp) {
461 0 : ig.ZPPFD = state.dataDayltg->daylightControl(ig.LightControlPtr).refPts(1).lums[DataSurfaces::iLum_Illum] /
462 : 77; // To be updated currently only take one reference point; 77 conversion factor from Lux to PPFD
463 : }
464 0 : } break;
465 :
466 0 : case LightingMethod::LEDDaylighting: {
467 0 : Real64 a = ig.ledDaylightTargetSched->getCurrentVal();
468 0 : Real64 b = 0;
469 0 : if (!state.dataDayltg->CalcDayltghCoefficients_firstTime && state.dataEnvrn->SunIsUp) {
470 0 : b = state.dataDayltg->daylightControl(ig.LightControlPtr).refPts(1).lums[DataSurfaces::iLum_Illum] /
471 : 77; // To be updated currently only take one reference point; 77 conversion factor from Lux to PPFD
472 : }
473 0 : ig.LEDActualPPFD = max((a - b), 0.0);
474 0 : if (ig.LEDActualPPFD >= ig.LEDNominalPPFD) {
475 0 : ig.ZPPFD = ig.LEDNominalPPFD + b; // LED Nominal + Daylight
476 0 : ig.LEDActualEleP = ig.LEDNominalEleP;
477 0 : ig.LEDActualEleCon = ig.LEDNominalEleP * Timestep;
478 : } else {
479 0 : ig.ZPPFD = a; // Targeted PPFD
480 0 : ig.LEDActualEleP = ig.LEDNominalEleP * ig.LEDActualPPFD / ig.LEDNominalPPFD;
481 0 : ig.LEDActualEleCon = ig.LEDActualEleP * Timestep;
482 : }
483 0 : } break;
484 0 : default:
485 0 : break;
486 : }
487 0 : ZonePPFD = ig.ZPPFD;
488 0 : ZoneVPD = ig.ZVPD / 1000; // kPa
489 : // ET Calculation
490 0 : if (ig.EMSETCalOverrideOn) {
491 0 : ig.ETRate = ig.EMSET;
492 : } else {
493 0 : Real64 SwitchF = ig.etCalculationMethod == ETCalculationMethod::PenmanMonteith ? 1.0 : 2 * LAI;
494 0 : ig.ETRate = ETBaseFunction(state, ZonePreTemp, ZonePreHum, ZonePPFD, ZoneVPD, LAI, SwitchF);
495 : }
496 0 : Real64 effectivearea = std::min(ig.LeafArea, LAI * state.dataSurface->Surface(ig.SurfPtr).Area);
497 0 : ETTotal = ig.ETRate * Timestep * effectivearea *
498 0 : ig.sched->getCurrentVal(); // kg; this unit area should be surface area instead of total leaf area
499 0 : Real64 hfg = Psychrometrics::PsyHfgAirFnWTdb(ZonePreHum, ZonePreTemp) / std::pow(10, 6); // Latent heat of vaporization (MJ/kg)
500 0 : ig.LambdaET = ETTotal * hfg * std::pow(10, 6) / state.dataSurface->Surface(ig.SurfPtr).Area / Timestep; // (W/m2))
501 0 : rhoair = Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->OutBaroPress, ZonePreTemp, ZonePreHum);
502 0 : ZoneAirVol = state.dataHeatBal->Zone(ig.ZonePtr).Volume;
503 0 : ZoneNewHum = ZonePreHum + ETTotal / (rhoair * ZoneAirVol);
504 0 : Twb = Psychrometrics::PsyTwbFnTdbWPb(state, ZonePreTemp, ZonePreHum, state.dataEnvrn->OutBaroPress);
505 0 : ZoneSatHum = Psychrometrics::PsyWFnTdbRhPb(state, Twb, 1.0, state.dataEnvrn->OutBaroPress); // saturated humidity ratio
506 0 : HCons = Psychrometrics::PsyHFnTdbW(ZonePreTemp, ZonePreHum);
507 0 : if (ZoneNewHum <= ZoneSatHum) {
508 0 : ZoneNewTemp = Psychrometrics::PsyTdbFnHW(HCons, ZoneNewHum);
509 : } else {
510 0 : ZoneNewTemp = Twb;
511 0 : ZoneNewHum = ZoneSatHum;
512 : }
513 0 : HMid = Psychrometrics::PsyHFnTdbW(ZoneNewTemp, ZonePreHum);
514 0 : ig.LatentRate = ZoneAirVol * rhoair * (HCons - HMid) / Timestep; // unit W
515 0 : ig.SensibleRateLED = (1 - ig.LEDRadFraction) * ig.LEDActualEleP; // convective heat gain from LED lights when LED is on;
516 0 : ig.SensibleRate = -1.0 * ig.LatentRate + ig.SensibleRateLED;
517 0 : state.dataHeatBalSurf->SurfQAdditionalHeatSourceInside(ig.SurfPtr) =
518 0 : ig.LEDRadFraction * 0.9 * ig.LEDActualEleP /
519 0 : state.dataSurface->Surface(ig.SurfPtr).Area; // assume the energy from radiation for photosynthesis is only 10%.
520 : }
521 0 : }
522 :
523 1 : Real64 ETBaseFunction(EnergyPlusData &state, Real64 ZonePreTemp, Real64 ZonePreHum, Real64 ZonePPFD, Real64 ZoneVPD, Real64 LAI, Real64 SwitchF)
524 : {
525 : // This subroutine provides calculation for Penman-Monteith model and Stanghellini models to predict evapotranspiration rates of plants.
526 : // Reference: Monteith, J.L. Evaporation and environment. in Symposia of the society for experimental biology. 1965. Cambridge University
527 : // Press (CUP) Cambridge
528 : // Reference: Stanghellini, C., Transpiration of greenhouse crops: an aid to climate management, 1987, Institute of Agricultural Engineering,
529 : // Wageningen, The Netherlands
530 :
531 1 : Real64 hfg = Psychrometrics::PsyHfgAirFnWTdb(ZonePreHum, ZonePreTemp) / std::pow(10, 6); // Latent heat of vaporization (MJ/kg)
532 : Real64 slopepat =
533 1 : 0.200 * std::pow((0.00738 * ZonePreTemp + 0.8072), 7) - 0.000116; // Slope of the saturation vapor pressure-temperature curve (kPa/°C)
534 1 : Real64 CpAir = Psychrometrics::PsyCpAirFnW(ZonePreHum) / std::pow(10, 6); // specific heat of air at constant pressure (MJ kg−1 °C−1)
535 1 : Real64 OutPb = state.dataEnvrn->OutBaroPress / 1000; // outdoor pressure (kPa)
536 1 : Real64 constexpr mw(0.622); // ratio molecular weight of water vapor / dry air = 0.622.
537 1 : Real64 psyconst = CpAir * OutPb / (hfg * mw); // Psychrometric constant (kPa/°C)
538 1 : Real64 In = ZonePPFD * 0.327 / std::pow(10, 6); // net radiation MW/m2
539 1 : Real64 G = 0.0; // soil heat flux (MJ/(m2s))
540 1 : Real64 rhoair = Psychrometrics::PsyRhoAirFnPbTdbW(state, OutPb * 1000, ZonePreTemp, ZonePreHum); // kg/m3
541 : Real64 ETRate; // mm/s; kg/(m2s)
542 1 : Real64 rs = 60 * (1500 + ZonePPFD) / (200 + ZonePPFD); // stomatal resistance s/m
543 1 : Real64 ra = 350 * std::pow((0.1 / 0.1), 0.5) * (1 / (LAI + 1e-10)); // aerodynamic resistance s/m
544 1 : ETRate = (1 / hfg) * (slopepat * (In - G) + (SwitchF * rhoair * CpAir * ZoneVPD) / ra) /
545 1 : (slopepat + psyconst * (1 + rs / ra)); // Penman-Monteith ET model
546 1 : return ETRate; // mm/s; kg/(m2s)
547 : }
548 :
549 : } // namespace IndoorGreen
550 :
551 : } // namespace EnergyPlus
|