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