LCOV - code coverage report
Current view: top level - EnergyPlus - IndoorGreen.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 195 337 57.9 %
Date: 2024-08-23 23:50:59 Functions: 6 6 100.0 %

          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

Generated by: LCOV version 1.14