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 2828212 : 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 2828212 : auto const &lw = state.dataIndoorGreen;
89 2828212 : if (lw->getInputFlag) {
90 801 : bool ErrorsFound(false);
91 801 : const char *RoutineName("IndoorLivingWall: "); // include trailing blank space
92 801 : GetIndoorGreenInput(state, ErrorsFound);
93 801 : if (ErrorsFound) {
94 0 : ShowFatalError(state, format("{}Errors found in input. Program terminates.", RoutineName));
95 : }
96 801 : SetIndoorGreenOutput(state);
97 801 : lw->getInputFlag = false;
98 : }
99 2828212 : if (lw->NumIndoorGreen > 0) {
100 1353 : InitIndoorGreen(state);
101 : // Simulate evapotranspiration from indoor living walls
102 1353 : ETModel(state);
103 : }
104 2828212 : }
105 :
106 801 : 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 801 : auto &s_lw = state.dataIndoorGreen;
112 801 : auto &s_ip = state.dataInputProcessing->inputProcessor;
113 801 : auto &s_ipsc = state.dataIPShortCut;
114 :
115 : static constexpr std::string_view RoutineName("GetIndoorLivingWallInput: ");
116 801 : 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 801 : s_lw->NumIndoorGreen = s_ip->getNumObjectsFound(state, cCurrentModuleObject);
122 801 : if (s_lw->NumIndoorGreen > 0) {
123 1 : s_lw->indoorGreens.allocate(s_lw->NumIndoorGreen); // Allocate the IndoorGreen input data array
124 : }
125 802 : for (int IndoorGreenNum = 1; IndoorGreenNum <= s_lw->NumIndoorGreen; ++IndoorGreenNum) {
126 1 : auto &ig = s_lw->indoorGreens(IndoorGreenNum);
127 2 : s_ip->getObjectItem(state,
128 : cCurrentModuleObject,
129 : IndoorGreenNum,
130 1 : s_ipsc->cAlphaArgs,
131 : NumAlphas,
132 1 : s_ipsc->rNumericArgs,
133 : NumNums,
134 : IOStat,
135 1 : s_ipsc->lNumericFieldBlanks,
136 1 : s_ipsc->lAlphaFieldBlanks,
137 1 : s_ipsc->cAlphaFieldNames,
138 1 : s_ipsc->cNumericFieldNames);
139 1 : ErrorObjectHeader eoh{RoutineName, cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
140 1 : Util::IsNameEmpty(state, s_ipsc->cAlphaArgs(1), cCurrentModuleObject, ErrorsFound);
141 1 : ig.Name = s_ipsc->cAlphaArgs(1);
142 1 : ig.SurfName = s_ipsc->cAlphaArgs(2);
143 1 : ig.SurfPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(2), state.dataSurface->Surface);
144 1 : if (ig.SurfPtr <= 0) {
145 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2));
146 0 : ErrorsFound = true;
147 : } else {
148 1 : if (state.dataSurface->Surface(ig.SurfPtr).insideHeatSourceTermSched != nullptr) {
149 0 : ShowSevereError(state,
150 0 : format("The indoor green surface {} has an Inside Face Heat Source Term Schedule defined. This surface cannot "
151 : "also be used for indoor green.",
152 0 : s_ipsc->cAlphaArgs(2)));
153 0 : ErrorsFound = true;
154 : }
155 1 : ig.ZonePtr = state.dataSurface->Surface(ig.SurfPtr).Zone;
156 1 : ig.SpacePtr = state.dataSurface->Surface(ig.SurfPtr).spaceNum;
157 :
158 1 : if (ig.ZonePtr <= 0 || ig.SpacePtr <= 0) {
159 0 : ShowSevereError(state,
160 0 : format("{}=\"{}\", invalid {} entered={}, {} is not assoicated with a thermal zone or space",
161 : RoutineName,
162 0 : s_ipsc->cAlphaArgs(1),
163 0 : s_ipsc->cAlphaFieldNames(2),
164 0 : s_ipsc->cAlphaArgs(2),
165 0 : s_ipsc->cAlphaArgs(2)));
166 0 : ErrorsFound = true;
167 2 : } else if (state.dataSurface->Surface(ig.SurfPtr).ExtBoundCond < 0 ||
168 1 : state.dataSurface->Surface(ig.SurfPtr).HeatTransferAlgorithm != DataSurfaces::HeatTransferModel::CTF) {
169 0 : ShowSevereError(state,
170 0 : format("{}=\"{}\", invalid {} entered={}, not a valid surface for indoor green module",
171 : RoutineName,
172 0 : s_ipsc->cAlphaArgs(1),
173 0 : s_ipsc->cAlphaFieldNames(2),
174 0 : s_ipsc->cAlphaArgs(2)));
175 0 : ErrorsFound = true;
176 : }
177 : }
178 :
179 1 : if ((ig.sched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(3))) == nullptr) {
180 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3));
181 0 : ErrorsFound = true;
182 1 : } else if (!ig.sched->checkMinVal(state, Clusive::In, 0.0)) {
183 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3), Clusive::In, 0.0);
184 0 : ErrorsFound = true;
185 : }
186 :
187 1 : ig.etCalculationMethod = ETCalculationMethod::PenmanMonteith; // default
188 1 : ig.etCalculationMethod = static_cast<ETCalculationMethod>(getEnumValue(etCalculationMethodsUC, s_ipsc->cAlphaArgs(4)));
189 1 : ig.lightingMethod = LightingMethod::LED; // default
190 1 : ig.lightingMethod = static_cast<LightingMethod>(getEnumValue(lightingMethodsUC, s_ipsc->cAlphaArgs(5)));
191 :
192 1 : switch (ig.lightingMethod) {
193 0 : case LightingMethod::LED: {
194 0 : if ((ig.ledSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(6))) == nullptr) {
195 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
196 0 : ErrorsFound = true;
197 0 : } else if (!ig.ledSched->checkMinVal(state, Clusive::In, 0.0)) {
198 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6), Clusive::In, 0.0);
199 0 : ErrorsFound = true;
200 : }
201 0 : } break;
202 0 : case LightingMethod::Daylighting: {
203 0 : ig.LightRefPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
204 0 : state.dataDayltg->DaylRefPt,
205 : &EnergyPlus::Dayltg::RefPointData::Name); // Field: Daylighting Reference Point Name
206 0 : ig.LightControlPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
207 0 : state.dataDayltg->daylightControl,
208 : &EnergyPlus::Dayltg::DaylightingControl::Name); // Field: Daylighting Control Name
209 0 : if (ig.LightControlPtr == 0) {
210 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
211 0 : ErrorsFound = true;
212 0 : continue;
213 : }
214 0 : } break;
215 1 : case LightingMethod::LEDDaylighting: {
216 1 : ig.LightRefPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
217 1 : state.dataDayltg->DaylRefPt,
218 : &EnergyPlus::Dayltg::RefPointData::Name); // Field: Daylighting Reference Point Name
219 1 : ig.LightControlPtr = Util::FindItemInList(s_ipsc->cAlphaArgs(7),
220 1 : state.dataDayltg->daylightControl,
221 : &EnergyPlus::Dayltg::DaylightingControl::Name); // Field: Daylighting Control Name
222 1 : if (ig.LightControlPtr == 0) {
223 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
224 0 : ErrorsFound = true;
225 0 : continue;
226 : }
227 :
228 1 : if ((ig.ledDaylightTargetSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(8))) == nullptr) {
229 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaArgs(8));
230 0 : ErrorsFound = true;
231 1 : } else if (!ig.ledDaylightTargetSched->checkMinVal(state, Clusive::In, 0.0)) {
232 0 : Sched::ShowSevereBadMin(state, eoh, s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaArgs(8), Clusive::In, 0.0);
233 0 : ErrorsFound = true;
234 : }
235 1 : } break;
236 :
237 0 : default:
238 0 : break;
239 : }
240 :
241 1 : ig.LeafArea = s_ipsc->rNumericArgs(1);
242 1 : if (ig.LeafArea < 0) {
243 0 : ShowSevereError(state,
244 0 : format("{}=\"{}\", invalid {} entered={}",
245 : RoutineName,
246 0 : s_ipsc->cAlphaArgs(1),
247 0 : s_ipsc->cNumericFieldNames(1),
248 0 : s_ipsc->rNumericArgs(1)));
249 0 : ErrorsFound = true;
250 : }
251 1 : ig.LEDNominalPPFD = s_ipsc->rNumericArgs(2);
252 1 : if (ig.LEDNominalPPFD < 0) {
253 0 : ShowSevereError(state,
254 0 : format("{}=\"{}\", invalid {} entered={}",
255 : RoutineName,
256 0 : s_ipsc->cAlphaArgs(1),
257 0 : s_ipsc->cNumericFieldNames(2),
258 0 : s_ipsc->rNumericArgs(2)));
259 0 : ErrorsFound = true;
260 : }
261 1 : ig.LEDNominalEleP = s_ipsc->rNumericArgs(3);
262 1 : if (ig.LEDNominalEleP < 0) {
263 0 : ShowSevereError(state,
264 0 : format("{}=\"{}\", invalid {} entered={}",
265 : RoutineName,
266 0 : s_ipsc->cAlphaArgs(1),
267 0 : s_ipsc->cNumericFieldNames(3),
268 0 : s_ipsc->rNumericArgs(3)));
269 0 : ErrorsFound = true;
270 : }
271 1 : ig.LEDRadFraction = s_ipsc->rNumericArgs(4);
272 1 : if (ig.LEDRadFraction < 0 || ig.LEDRadFraction > 1.0) {
273 0 : ShowSevereError(state,
274 0 : format("{}=\"{}\", invalid {} entered={}",
275 : RoutineName,
276 0 : s_ipsc->cAlphaArgs(1),
277 0 : s_ipsc->cNumericFieldNames(4),
278 0 : s_ipsc->rNumericArgs(4)));
279 0 : ErrorsFound = true;
280 : }
281 1 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
282 0 : SetupEMSActuator(state, "IndoorLivingWall", ig.Name, "Evapotranspiration Rate", "[kg_m2s]", ig.EMSETCalOverrideOn, ig.EMSET);
283 : } // EMS and API
284 : }
285 801 : }
286 :
287 801 : void SetIndoorGreenOutput(EnergyPlusData &state)
288 : {
289 : // Set up output variables
290 801 : auto &lw = state.dataIndoorGreen;
291 802 : for (int IndoorGreenNum = 1; IndoorGreenNum <= lw->NumIndoorGreen; ++IndoorGreenNum) {
292 1 : auto &ig = lw->indoorGreens(IndoorGreenNum);
293 1 : SetupZoneInternalGain(state,
294 : ig.ZonePtr,
295 : ig.Name,
296 : DataHeatBalance::IntGainType::IndoorGreen,
297 : &ig.SensibleRate,
298 : nullptr,
299 : nullptr,
300 : &ig.LatentRate,
301 : nullptr,
302 : nullptr,
303 : nullptr);
304 :
305 2 : SetupOutputVariable(state,
306 : "Indoor Living Wall Plant Surface Temperature",
307 : Constant::Units::C,
308 1 : state.dataHeatBalSurf->SurfTempIn(ig.SurfPtr),
309 : OutputProcessor::TimeStepType::Zone,
310 : OutputProcessor::StoreType::Average,
311 1 : ig.Name);
312 2 : SetupOutputVariable(state,
313 : "Indoor Living Wall Sensible Heat Gain Rate",
314 : Constant::Units::W,
315 1 : ig.SensibleRate,
316 : OutputProcessor::TimeStepType::Zone,
317 : OutputProcessor::StoreType::Average,
318 1 : ig.Name);
319 2 : SetupOutputVariable(state,
320 : "Indoor Living Wall Latent Heat Gain Rate",
321 : Constant::Units::W,
322 1 : ig.LatentRate,
323 : OutputProcessor::TimeStepType::Zone,
324 : OutputProcessor::StoreType::Average,
325 1 : ig.Name);
326 2 : SetupOutputVariable(state,
327 : "Indoor Living Wall Evapotranspiration Rate",
328 : Constant::Units::kg_m2s,
329 1 : ig.ETRate,
330 : OutputProcessor::TimeStepType::Zone,
331 : OutputProcessor::StoreType::Average,
332 1 : ig.Name);
333 2 : SetupOutputVariable(state,
334 : "Indoor Living Wall Energy Rate Required For Evapotranspiration Per Unit Area",
335 : Constant::Units::W_m2,
336 1 : ig.LambdaET,
337 : OutputProcessor::TimeStepType::Zone,
338 : OutputProcessor::StoreType::Average,
339 1 : ig.Name);
340 2 : SetupOutputVariable(state,
341 : "Indoor Living Wall LED Operational PPFD",
342 : Constant::Units::umol_m2s,
343 1 : ig.LEDActualPPFD,
344 : OutputProcessor::TimeStepType::Zone,
345 : OutputProcessor::StoreType::Average,
346 1 : ig.Name);
347 2 : SetupOutputVariable(state,
348 : "Indoor Living Wall PPFD",
349 : Constant::Units::umol_m2s,
350 1 : ig.ZPPFD,
351 : OutputProcessor::TimeStepType::Zone,
352 : OutputProcessor::StoreType::Average,
353 1 : ig.Name);
354 2 : SetupOutputVariable(state,
355 : "Indoor Living Wall Vapor Pressure Deficit",
356 : Constant::Units::Pa,
357 1 : ig.ZVPD,
358 : OutputProcessor::TimeStepType::Zone,
359 : OutputProcessor::StoreType::Average,
360 1 : ig.Name);
361 2 : SetupOutputVariable(state,
362 : "Indoor Living Wall LED Sensible Heat Gain Rate",
363 : Constant::Units::W,
364 1 : ig.SensibleRateLED,
365 : OutputProcessor::TimeStepType::Zone,
366 : OutputProcessor::StoreType::Average,
367 1 : ig.Name);
368 2 : SetupOutputVariable(state,
369 : "Indoor Living Wall LED Operational Power",
370 : Constant::Units::W,
371 1 : ig.LEDActualEleP,
372 : OutputProcessor::TimeStepType::Zone,
373 : OutputProcessor::StoreType::Average,
374 1 : ig.Name);
375 2 : SetupOutputVariable(state,
376 : "Indoor Living Wall LED Electricity Energy",
377 : Constant::Units::J,
378 1 : ig.LEDActualEleCon,
379 : OutputProcessor::TimeStepType::Zone,
380 : OutputProcessor::StoreType::Sum,
381 1 : ig.Name,
382 : Constant::eResource::Electricity,
383 : OutputProcessor::Group::Building,
384 : OutputProcessor::EndUseCat::InteriorLights,
385 : "IndoorLivingWall", // End Use subcategory
386 1 : state.dataHeatBal->Zone(ig.ZonePtr).Name,
387 1 : state.dataHeatBal->Zone(ig.ZonePtr).Multiplier,
388 1 : state.dataHeatBal->Zone(ig.ZonePtr).ListMultiplier,
389 1 : state.dataHeatBal->space(ig.SpacePtr).spaceType);
390 : }
391 801 : }
392 :
393 1353 : void InitIndoorGreen(EnergyPlusData const &state)
394 : {
395 : // Set the reporting variables to zero at each timestep.
396 2706 : for (auto &ig : state.dataIndoorGreen->indoorGreens) {
397 1353 : ig.SensibleRate = 0.0;
398 1353 : ig.SensibleRateLED = 0.0;
399 1353 : ig.LatentRate = 0.0;
400 1353 : ig.ZCO2 = 400;
401 1353 : ig.ZPPFD = 0;
402 : }
403 1353 : }
404 :
405 1353 : void ETModel(EnergyPlusData &state)
406 : {
407 : // PURPOSE OF THIS SUBROUTINE:
408 : // This subroutine is for the calculation of evapotranspiration effects from the Indoor Greenery System objects.
409 : // SUBROUTINE PARAMETER DEFINITIONS:
410 : static constexpr std::string_view RoutineName("ETModel: ");
411 1353 : auto &lw = state.dataIndoorGreen;
412 : Real64 ZonePreTemp; // Indoor air temprature (C)
413 : Real64 ZonePreHum; // Indoor humidity ratio (kg moisture / kg dry air)
414 : Real64 ZoneNewTemp; // Indoor air temprature (C) after ET
415 : Real64 ZoneNewHum; // Indoor humidity ratio (kg moisture / kg dry air) after ET
416 : Real64 ZoneSatHum; // Saturated humidity ratio
417 : Real64 ZoneCO2; // Indoor zone co2 concentration (ppm)
418 : Real64 ZonePPFD; // Indoor net radiation (PPFD)
419 : Real64 ZoneVPD; // vapor pressure deficit (kpa); local variable
420 : Real64 Timestep; // s
421 : Real64 ETTotal; // kg
422 : Real64 rhoair; // kg/m3
423 : Real64 Tdp; // dew point temperature
424 : Real64 Twb; // wet bulb temperature
425 : Real64 HCons; // enthalpy (J/kg)
426 : Real64 HMid; // enthalpy 3rd point (J/kg)
427 : Real64 ZoneAirVol; // zone air volume (m3)
428 : 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
429 : Real64 LAI_Cal; // calculated leaf area index based on users's input on total leaf area
430 : Real64 OutPb; // outdoor pressure (kPa)
431 : Real64 vp; // actual vapor pressure of the air (kpa)
432 : Real64 vpSat; // saturated vapor pressure at air temperature (kpa)
433 1353 : Timestep = state.dataHVACGlobal->TimeStepSysSec; // unit s
434 2706 : for (int IndoorGreenNum = 1; IndoorGreenNum <= lw->NumIndoorGreen; ++IndoorGreenNum) {
435 1353 : auto &ig = lw->indoorGreens(IndoorGreenNum);
436 1353 : ZonePreTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ig.ZonePtr).ZT;
437 1353 : ZonePreHum = state.dataZoneTempPredictorCorrector->zoneHeatBalance(ig.ZonePtr).airHumRat;
438 1353 : ZoneCO2 = 400;
439 1353 : OutPb = state.dataEnvrn->OutBaroPress / 1000;
440 1353 : Tdp = Psychrometrics::PsyTdpFnWPb(state, ZonePreHum, OutPb * 1000);
441 1353 : vp = Psychrometrics::PsyPsatFnTemp(state, Tdp, RoutineName) / 1000;
442 1353 : vpSat = Psychrometrics::PsyPsatFnTemp(state, ZonePreTemp, RoutineName) / 1000;
443 1353 : ig.ZVPD = (vpSat - vp) * 1000; // Pa
444 1353 : LAI_Cal = ig.LeafArea / state.dataSurface->Surface(ig.SurfPtr).Area;
445 1353 : LAI = LAI_Cal;
446 1353 : if (LAI_Cal > 2.0) {
447 0 : LAI = 2.0; // maximum LAI=2.0 in the surface heat balance
448 0 : ShowSevereError(state, format("Maximum indoor living wall leaf area index (LAI) =2.0 is used,calculated LAI is {}", LAI_Cal));
449 : }
450 1353 : switch (ig.lightingMethod) {
451 0 : case LightingMethod::LED: {
452 0 : ig.ZPPFD = ig.ledSched->getCurrentVal() * ig.LEDNominalPPFD; // PPFD
453 0 : ig.LEDActualPPFD = ig.ZPPFD;
454 0 : ig.LEDActualEleP = ig.ledSched->getCurrentVal() * ig.LEDNominalEleP;
455 0 : ig.LEDActualEleCon = ig.LEDActualEleP * Timestep;
456 0 : } break;
457 0 : case LightingMethod::Daylighting: {
458 0 : ig.ZPPFD = 0;
459 0 : ig.LEDActualPPFD = 0;
460 0 : ig.LEDActualEleP = 0;
461 0 : ig.LEDActualEleCon = 0;
462 0 : if (!state.dataDayltg->CalcDayltghCoefficients_firstTime && state.dataEnvrn->SunIsUp) {
463 0 : ig.ZPPFD = state.dataDayltg->daylightControl(ig.LightControlPtr).refPts(1).lums[DataSurfaces::iLum_Illum] /
464 : 77; // To be updated currently only take one reference point; 77 conversion factor from Lux to PPFD
465 : }
466 0 : } break;
467 :
468 1353 : case LightingMethod::LEDDaylighting: {
469 1353 : Real64 a = ig.ledDaylightTargetSched->getCurrentVal();
470 1353 : Real64 b = 0;
471 1353 : if (!state.dataDayltg->CalcDayltghCoefficients_firstTime && state.dataEnvrn->SunIsUp) {
472 665 : b = state.dataDayltg->daylightControl(ig.LightControlPtr).refPts(1).lums[DataSurfaces::iLum_Illum] /
473 : 77; // To be updated currently only take one reference point; 77 conversion factor from Lux to PPFD
474 : }
475 1353 : ig.LEDActualPPFD = max((a - b), 0.0);
476 1353 : if (ig.LEDActualPPFD >= ig.LEDNominalPPFD) {
477 336 : ig.ZPPFD = ig.LEDNominalPPFD + b; // LED Nominal + Daylight
478 336 : ig.LEDActualEleP = ig.LEDNominalEleP;
479 336 : ig.LEDActualEleCon = ig.LEDNominalEleP * Timestep;
480 : } else {
481 1017 : ig.ZPPFD = a; // Targeted PPFD
482 1017 : ig.LEDActualEleP = ig.LEDNominalEleP * ig.LEDActualPPFD / ig.LEDNominalPPFD;
483 1017 : ig.LEDActualEleCon = ig.LEDActualEleP * Timestep;
484 : }
485 1353 : } break;
486 0 : default:
487 0 : break;
488 : }
489 1353 : ZonePPFD = ig.ZPPFD;
490 1353 : ZoneVPD = ig.ZVPD / 1000; // kPa
491 : // ET Calculation
492 1353 : if (ig.EMSETCalOverrideOn) {
493 0 : ig.ETRate = ig.EMSET;
494 : } else {
495 1353 : Real64 SwitchF = ig.etCalculationMethod == ETCalculationMethod::PenmanMonteith ? 1.0 : 2 * LAI;
496 1353 : ig.ETRate = ETBaseFunction(state, ZonePreTemp, ZonePreHum, ZonePPFD, ZoneVPD, LAI, SwitchF);
497 : }
498 1353 : Real64 effectivearea = std::min(ig.LeafArea, LAI * state.dataSurface->Surface(ig.SurfPtr).Area);
499 2706 : ETTotal = ig.ETRate * Timestep * effectivearea *
500 1353 : ig.sched->getCurrentVal(); // kg; this unit area should be surface area instead of total leaf area
501 1353 : Real64 hfg = Psychrometrics::PsyHfgAirFnWTdb(ZonePreHum, ZonePreTemp) / std::pow(10, 6); // Latent heat of vaporization (MJ/kg)
502 1353 : ig.LambdaET = ETTotal * hfg * std::pow(10, 6) / state.dataSurface->Surface(ig.SurfPtr).Area / Timestep; // (W/m2))
503 1353 : rhoair = Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->OutBaroPress, ZonePreTemp, ZonePreHum);
504 1353 : ZoneAirVol = state.dataHeatBal->Zone(ig.ZonePtr).Volume;
505 1353 : ZoneNewHum = ZonePreHum + ETTotal / (rhoair * ZoneAirVol);
506 1353 : Twb = Psychrometrics::PsyTwbFnTdbWPb(state, ZonePreTemp, ZonePreHum, state.dataEnvrn->OutBaroPress);
507 1353 : ZoneSatHum = Psychrometrics::PsyWFnTdbRhPb(state, Twb, 1.0, state.dataEnvrn->OutBaroPress); // saturated humidity ratio
508 1353 : HCons = Psychrometrics::PsyHFnTdbW(ZonePreTemp, ZonePreHum);
509 1353 : if (ZoneNewHum <= ZoneSatHum) {
510 1353 : ZoneNewTemp = Psychrometrics::PsyTdbFnHW(HCons, ZoneNewHum);
511 : } else {
512 0 : ZoneNewTemp = Twb;
513 0 : ZoneNewHum = ZoneSatHum;
514 : }
515 1353 : HMid = Psychrometrics::PsyHFnTdbW(ZoneNewTemp, ZonePreHum);
516 1353 : ig.LatentRate = ZoneAirVol * rhoair * (HCons - HMid) / Timestep; // unit W
517 1353 : ig.SensibleRateLED = (1 - ig.LEDRadFraction) * ig.LEDActualEleP; // convective heat gain from LED lights when LED is on;
518 1353 : ig.SensibleRate = -1.0 * ig.LatentRate + ig.SensibleRateLED;
519 1353 : state.dataHeatBalSurf->SurfQAdditionalHeatSourceInside(ig.SurfPtr) =
520 2706 : ig.LEDRadFraction * 0.9 * ig.LEDActualEleP /
521 1353 : state.dataSurface->Surface(ig.SurfPtr).Area; // assume the energy from radiation for photosynthesis is only 10%.
522 : }
523 1353 : }
524 :
525 1353 : Real64 ETBaseFunction(EnergyPlusData &state, Real64 ZonePreTemp, Real64 ZonePreHum, Real64 ZonePPFD, Real64 ZoneVPD, Real64 LAI, Real64 SwitchF)
526 : {
527 : // This subroutine provides calculation for Penman-Monteith model and Stanghellini models to predict evapotranspiration rates of plants.
528 : // Reference: Monteith, J.L. Evaporation and environment. in Symposia of the society for experimental biology. 1965. Cambridge University
529 : // Press (CUP) Cambridge
530 : // Reference: Stanghellini, C., Transpiration of greenhouse crops: an aid to climate management, 1987, Institute of Agricultural Engineering,
531 : // Wageningen, The Netherlands
532 :
533 1353 : Real64 hfg = Psychrometrics::PsyHfgAirFnWTdb(ZonePreHum, ZonePreTemp) / std::pow(10, 6); // Latent heat of vaporization (MJ/kg)
534 : // Slope of the saturation vapor pressure-temperature curve (kPa/°C)
535 1353 : Real64 slopepat = 0.200 * std::pow((0.00738 * ZonePreTemp + 0.8072), 7) - 0.000116;
536 1353 : Real64 CpAir = Psychrometrics::PsyCpAirFnW(ZonePreHum) / std::pow(10, 6); // specific heat of air at constant pressure (MJ kg−1 °C−1)
537 1353 : Real64 OutPb = state.dataEnvrn->OutBaroPress / 1000; // outdoor pressure (kPa)
538 1353 : Real64 constexpr mw(0.622); // ratio molecular weight of water vapor / dry air = 0.622.
539 1353 : Real64 psyconst = CpAir * OutPb / (hfg * mw); // Psychrometric constant (kPa/°C)
540 1353 : Real64 In = ZonePPFD * 0.327 / std::pow(10, 6); // net radiation MW/m2
541 1353 : Real64 G = 0.0; // soil heat flux (MJ/(m2s))
542 1353 : Real64 rhoair = Psychrometrics::PsyRhoAirFnPbTdbW(state, OutPb * 1000, ZonePreTemp, ZonePreHum); // kg/m3
543 : Real64 ETRate; // mm/s; kg/(m2s)
544 1353 : Real64 rs = 60 * (1500 + ZonePPFD) / (200 + ZonePPFD); // stomatal resistance s/m
545 1353 : Real64 ra = 350 * std::pow((0.1 / 0.1), 0.5) * (1 / (LAI + 1e-10)); // aerodynamic resistance s/m
546 1353 : ETRate = (1 / hfg) * (slopepat * (In - G) + (SwitchF * rhoair * CpAir * ZoneVPD) / ra) /
547 1353 : (slopepat + psyconst * (1 + rs / ra)); // Penman-Monteith ET model
548 1353 : return ETRate; // mm/s; kg/(m2s)
549 : }
550 :
551 : } // namespace IndoorGreen
552 :
553 : } // namespace EnergyPlus
|