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 : // Kiva Headers
51 : #include <libkiva/Errors.hpp>
52 : #ifdef GROUND_PLOT
53 : #include <EnergyPlus/DataStringGlobals.hh>
54 : #include <libgroundplot/GroundPlot.hpp>
55 : #endif
56 :
57 : // EnergyPlus Headers
58 : #include <EnergyPlus/Construction.hh>
59 : #include <EnergyPlus/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataHVACGlobals.hh>
62 : #include <EnergyPlus/DataHeatBalSurface.hh>
63 : #include <EnergyPlus/DataHeatBalance.hh>
64 : #include <EnergyPlus/DataSurfaces.hh>
65 : #include <EnergyPlus/DataSystemVariables.hh>
66 : #include <EnergyPlus/DataZoneControls.hh>
67 : #include <EnergyPlus/HeatBalanceKivaManager.hh>
68 : #include <EnergyPlus/InternalHeatGains.hh>
69 : #include <EnergyPlus/Material.hh>
70 : #include <EnergyPlus/ScheduleManager.hh>
71 : #include <EnergyPlus/SurfaceGeometry.hh>
72 : #include <EnergyPlus/ThermalComfort.hh>
73 : #include <EnergyPlus/UtilityRoutines.hh>
74 : #include <EnergyPlus/Vectors.hh>
75 : #include <EnergyPlus/WeatherManager.hh>
76 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
77 :
78 : namespace EnergyPlus::HeatBalanceKivaManager {
79 :
80 0 : void kivaErrorCallback(const int messageType, const std::string message, void *contextPtr)
81 : {
82 0 : if (!contextPtr) {
83 0 : throw FatalError(format("Unhandled Kiva Error: {}", message));
84 : }
85 0 : std::string fullMessage;
86 0 : std::pair<EnergyPlusData *, std::string> contextPair = *(std::pair<EnergyPlusData *, std::string> *)contextPtr;
87 0 : if (contextPair.second.size() > 0) {
88 0 : fullMessage = format("{}: {}", contextPair.second, message);
89 : } else {
90 0 : fullMessage = format("Kiva: {}", message);
91 : }
92 0 : if (messageType == Kiva::MSG_INFO) {
93 0 : ShowMessage(*contextPair.first, fullMessage);
94 0 : } else if (messageType == Kiva::MSG_WARN) {
95 0 : ShowWarningError(*contextPair.first, fullMessage);
96 : } else { // if (messageType == Kiva::MSG_ERR)
97 0 : ShowSevereError(*contextPair.first, fullMessage);
98 0 : ShowFatalError(*contextPair.first, "Kiva: Errors discovered, program terminates.");
99 : }
100 0 : }
101 :
102 27 : KivaInstanceMap::KivaInstanceMap(EnergyPlusData &state,
103 : Kiva::Foundation &foundation,
104 : int floorSurface,
105 : std::vector<int> wallSurfaces,
106 : int zoneNum,
107 : Real64 zoneAssumedTemperature,
108 : Real64 floorWeight,
109 : int constructionNum,
110 27 : KivaManager *kmPtr)
111 27 : : instance(foundation), floorSurface(floorSurface), wallSurfaces(wallSurfaces), zoneNum(zoneNum), zoneControlType(KIVAZONE_UNCONTROLLED),
112 27 : zoneControlNum(0), zoneAssumedTemperature(zoneAssumedTemperature), floorWeight(floorWeight), constructionNum(constructionNum), kmPtr(kmPtr)
113 : {
114 :
115 83 : for (int i = 1; i <= state.dataZoneCtrls->NumTempControlledZones; ++i) {
116 76 : if (state.dataZoneCtrls->TempControlledZone(i).ActualZoneNum == zoneNum) {
117 20 : zoneControlType = KIVAZONE_TEMPCONTROL;
118 20 : zoneControlNum = i;
119 20 : break;
120 : }
121 : }
122 27 : for (int i = 1; i <= state.dataZoneCtrls->NumComfortControlledZones; ++i) {
123 0 : if (state.dataZoneCtrls->ComfortControlledZone(i).ActualZoneNum == zoneNum) {
124 0 : zoneControlType = KIVAZONE_COMFORTCONTROL;
125 0 : zoneControlNum = i;
126 0 : break;
127 : }
128 : }
129 27 : for (size_t i = 1; i <= state.dataZoneCtrls->StageControlledZone.size(); ++i) {
130 0 : if (state.dataZoneCtrls->StageControlledZone(i).ActualZoneNum == zoneNum) {
131 0 : zoneControlType = KIVAZONE_STAGEDCONTROL;
132 0 : zoneControlNum = i;
133 0 : break;
134 : }
135 : }
136 27 : }
137 :
138 201 : void KivaInstanceMap::initGround(EnergyPlusData &state, const KivaWeatherData &kivaWeather)
139 : {
140 :
141 : #ifdef GROUND_PLOT
142 : std::string constructionName;
143 : if (constructionNum == 0) {
144 : constructionName = "Default Footing Wall Construction";
145 : } else {
146 : constructionName = state.dataConstruction->Construct(constructionNum).Name;
147 : }
148 :
149 : ss.dir = format("{}/{} {:.2R} {}",
150 : FileSystem::getAbsolutePath(state.dataStrGlobals->outDirPath),
151 : state.dataSurface->Surface(floorSurface).Name,
152 : instance.ground->foundation.foundationDepth,
153 : constructionName);
154 :
155 : debugDir = ss.dir;
156 : plotNum = 0;
157 : double &l = instance.ground->foundation.reductionLength2;
158 : constexpr double width = 0.0;
159 : const double depth = instance.ground->foundation.foundationDepth + width / 2.0;
160 : const double range = max(width, depth);
161 : ss.xRange = {l - range / 2.0, l + range / 2.0};
162 : ss.yRange = {0.5, 0.5};
163 : ss.zRange = {-range, instance.ground->foundation.wall.heightAboveGrade};
164 : ss.grid = true;
165 : ss.contours = false;
166 : ss.size = 1600;
167 : // ss.plotType = Kiva::SnapshotSettings::P_FLUX;
168 :
169 : if (ss.plotType == Kiva::SnapshotSettings::P_FLUX) {
170 : ss.minValue = 0.0;
171 : ss.maxValue = 100.0;
172 : }
173 :
174 : gp = Kiva::GroundPlot(ss, instance.ground->domain, instance.ground->foundation);
175 : #endif
176 :
177 201 : int numAccelaratedTimesteps = 3;
178 201 : int acceleratedTimestep = 30; // days
179 201 : int accDate = getAccDate(state, numAccelaratedTimesteps, acceleratedTimestep);
180 : // Initialize with steady state before accelerated timestepping
181 201 : instance.ground->foundation.numericalScheme = Kiva::Foundation::NS_STEADY_STATE;
182 201 : setInitialBoundaryConditions(state, kivaWeather, accDate, 24, state.dataGlobal->NumOfTimeStepInHour);
183 201 : instance.calculate();
184 201 : accDate += acceleratedTimestep;
185 201 : while (accDate > 365 + state.dataWeather->LeapYearAdd) {
186 0 : accDate = accDate - (365 + state.dataWeather->LeapYearAdd);
187 : }
188 :
189 : // Accelerated timestepping
190 201 : instance.ground->foundation.numericalScheme = Kiva::Foundation::NS_IMPLICIT;
191 804 : for (int i = 0; i < numAccelaratedTimesteps; ++i) {
192 603 : setInitialBoundaryConditions(state, kivaWeather, accDate, 24, state.dataGlobal->NumOfTimeStepInHour);
193 603 : instance.calculate(acceleratedTimestep * 24 * 60 * 60);
194 603 : accDate += acceleratedTimestep;
195 678 : while (accDate > 365 + state.dataWeather->LeapYearAdd) {
196 75 : accDate = accDate - (365 + state.dataWeather->LeapYearAdd);
197 : }
198 : }
199 :
200 201 : instance.calculate_surface_averages();
201 201 : instance.foundation->numericalScheme = Kiva::Foundation::NS_ADI;
202 201 : }
203 :
204 201 : int KivaInstanceMap::getAccDate(EnergyPlusData &state, const int numAccelaratedTimesteps, const int acceleratedTimestep)
205 : {
206 : // Determine accelerated intervals
207 : int accDate =
208 201 : state.dataEnvrn->DayOfYear - 1 - acceleratedTimestep * (numAccelaratedTimesteps + 1); // date time = last timestep from the day before
209 293 : while (accDate <= 0) {
210 92 : accDate = accDate + 365 + state.dataWeather->LeapYearAdd;
211 : }
212 201 : return accDate;
213 : }
214 :
215 804 : void KivaInstanceMap::setInitialBoundaryConditions(
216 : EnergyPlusData &state, const KivaWeatherData &kivaWeather, const int date, const int hour, const int timestep)
217 : {
218 :
219 : unsigned index, indexPrev;
220 804 : unsigned dataSize = kivaWeather.windSpeed.size();
221 : Real64 weightNow;
222 :
223 804 : if (kivaWeather.intervalsPerHour == 1) {
224 804 : index = (date - 1) * 24 + (hour - 1);
225 804 : weightNow = min(1.0, (double(timestep) / double(state.dataGlobal->NumOfTimeStepInHour)));
226 : } else {
227 0 : index = (date - 1) * 24 * state.dataGlobal->NumOfTimeStepInHour + (hour - 1) * state.dataGlobal->NumOfTimeStepInHour + (timestep - 1);
228 0 : weightNow = 1.0; // weather data interval must be the same as the timestep interval (i.e., no interpolation)
229 : }
230 804 : if (index == 0) {
231 0 : indexPrev = dataSize - 1;
232 : } else {
233 804 : indexPrev = index - 1;
234 : }
235 :
236 804 : instance.bcs = std::make_shared<Kiva::BoundaryConditions>();
237 :
238 804 : std::shared_ptr<Kiva::BoundaryConditions> bcs = instance.bcs;
239 :
240 804 : bcs->outdoorTemp = kivaWeather.dryBulb[index] * weightNow + kivaWeather.dryBulb[indexPrev] * (1.0 - weightNow) + Constant::Kelvin;
241 :
242 804 : bcs->localWindSpeed = (kivaWeather.windSpeed[index] * weightNow + kivaWeather.windSpeed[indexPrev] * (1.0 - weightNow)) *
243 804 : state.dataEnvrn->WeatherFileWindModCoeff *
244 804 : std::pow(instance.ground->foundation.grade.roughness / state.dataEnvrn->SiteWindBLHeight, state.dataEnvrn->SiteWindExp);
245 804 : bcs->skyEmissivity = kivaWeather.skyEmissivity[index] * weightNow + kivaWeather.skyEmissivity[indexPrev] * (1.0 - weightNow);
246 804 : bcs->solarAzimuth = 3.14;
247 804 : bcs->solarAltitude = 0.0;
248 804 : bcs->directNormalFlux = 0.0;
249 804 : bcs->diffuseHorizontalFlux = 0.0;
250 804 : bcs->slabAbsRadiation = 0.0;
251 804 : bcs->wallAbsRadiation = 0.0;
252 804 : bcs->deepGroundTemperature = kivaWeather.annualAverageDrybulbTemp + Constant::Kelvin;
253 :
254 : // Estimate indoor temperature
255 804 : constexpr Real64 defaultFlagTemp = -999; // default sets this below -999 at -9999 so uses value if entered
256 804 : constexpr Real64 standardTemp = 22; // degC
257 804 : Real64 assumedFloatingTemp = standardTemp; // degC (somewhat arbitrary assumption--not knowing anything else
258 : // about the building at this point)
259 :
260 : Real64 Tin;
261 804 : if (zoneAssumedTemperature > defaultFlagTemp) {
262 180 : Tin = zoneAssumedTemperature + Constant::Kelvin;
263 : } else {
264 624 : switch (zoneControlType) {
265 140 : case KIVAZONE_UNCONTROLLED: {
266 140 : Tin = assumedFloatingTemp + Constant::Kelvin;
267 140 : break;
268 : }
269 484 : case KIVAZONE_TEMPCONTROL: {
270 :
271 484 : int controlTypeSchId = state.dataZoneCtrls->TempControlledZone(zoneControlNum).CTSchedIndex;
272 : HVAC::ThermostatType controlType =
273 484 : static_cast<HVAC::ThermostatType>(ScheduleManager::LookUpScheduleValue(state, controlTypeSchId, hour, timestep));
274 :
275 484 : switch (controlType) {
276 0 : case HVAC::ThermostatType::Uncontrolled:
277 0 : Tin = assumedFloatingTemp + Constant::Kelvin;
278 0 : break;
279 36 : case HVAC::ThermostatType::SingleHeating: {
280 36 : int schNameId = state.dataZoneCtrls->TempControlledZone(zoneControlNum).SchIndx_SingleHeatSetPoint;
281 36 : Real64 setpoint = ScheduleManager::LookUpScheduleValue(state, schNameId, hour, timestep);
282 36 : Tin = setpoint + Constant::Kelvin;
283 36 : break;
284 : }
285 24 : case HVAC::ThermostatType::SingleCooling: {
286 24 : int schNameId = state.dataZoneCtrls->TempControlledZone(zoneControlNum).SchIndx_SingleCoolSetPoint;
287 24 : Real64 setpoint = ScheduleManager::LookUpScheduleValue(state, schNameId, hour, timestep);
288 24 : Tin = setpoint + Constant::Kelvin;
289 24 : break;
290 : }
291 0 : case HVAC::ThermostatType::SingleHeatCool: {
292 0 : int schNameId = state.dataZoneCtrls->TempControlledZone(zoneControlNum).SchIndx_SingleHeatCoolSetPoint;
293 0 : Real64 setpoint = ScheduleManager::LookUpScheduleValue(state, schNameId, hour, timestep);
294 0 : Tin = setpoint + Constant::Kelvin;
295 0 : break;
296 : }
297 424 : case HVAC::ThermostatType::DualSetPointWithDeadBand: {
298 424 : int schNameIdHeat = state.dataZoneCtrls->TempControlledZone(zoneControlNum).SchIndx_DualSetPointWDeadBandHeat;
299 424 : int schNameIdCool = state.dataZoneCtrls->TempControlledZone(zoneControlNum).SchIndx_DualSetPointWDeadBandCool;
300 424 : Real64 heatSetpoint = ScheduleManager::LookUpScheduleValue(state, schNameIdHeat, hour, timestep);
301 424 : Real64 coolSetpoint = ScheduleManager::LookUpScheduleValue(state, schNameIdCool, hour, timestep);
302 424 : constexpr Real64 heatBalanceTemp = 10.0 + Constant::Kelvin; // (assumed)
303 424 : constexpr Real64 coolBalanceTemp = 15.0 + Constant::Kelvin; // (assumed)
304 :
305 424 : if (bcs->outdoorTemp < heatBalanceTemp) {
306 201 : Tin = heatSetpoint + Constant::Kelvin;
307 223 : } else if (bcs->outdoorTemp > coolBalanceTemp) {
308 72 : Tin = coolSetpoint + Constant::Kelvin;
309 : } else {
310 151 : Real64 weight = (coolBalanceTemp - bcs->outdoorTemp) / (coolBalanceTemp - heatBalanceTemp);
311 151 : Tin = heatSetpoint * weight + coolSetpoint * (1.0 - weight) + Constant::Kelvin;
312 : }
313 424 : break;
314 : }
315 0 : default:
316 0 : Tin = 0.0;
317 0 : ShowSevereError(state,
318 0 : format("Illegal control type for Zone={}, Found value={}, in Schedule={}",
319 0 : state.dataHeatBal->Zone(zoneNum).Name,
320 : controlType,
321 0 : state.dataZoneCtrls->TempControlledZone(zoneControlNum).ControlTypeSchedName));
322 : }
323 484 : break;
324 : }
325 0 : case KIVAZONE_COMFORTCONTROL: {
326 :
327 0 : Tin = standardTemp + Constant::Kelvin;
328 0 : break;
329 : }
330 0 : case KIVAZONE_STAGEDCONTROL: {
331 :
332 0 : int heatSpSchId = state.dataZoneCtrls->StageControlledZone(zoneControlNum).HSBchedIndex;
333 0 : int coolSpSchId = state.dataZoneCtrls->StageControlledZone(zoneControlNum).CSBchedIndex;
334 0 : Real64 heatSetpoint = ScheduleManager::LookUpScheduleValue(state, heatSpSchId, hour, timestep);
335 0 : Real64 coolSetpoint = ScheduleManager::LookUpScheduleValue(state, coolSpSchId, hour, timestep);
336 0 : constexpr Real64 heatBalanceTemp = 10.0 + Constant::Kelvin; // (assumed)
337 0 : constexpr Real64 coolBalanceTemp = 15.0 + Constant::Kelvin; // (assumed)
338 0 : if (bcs->outdoorTemp < heatBalanceTemp) {
339 0 : Tin = heatSetpoint + Constant::Kelvin;
340 0 : } else if (bcs->outdoorTemp > coolBalanceTemp) {
341 0 : Tin = coolSetpoint + Constant::Kelvin;
342 : } else {
343 0 : Real64 weight = (coolBalanceTemp - bcs->outdoorTemp) / (coolBalanceTemp - heatBalanceTemp);
344 0 : Tin = heatSetpoint * weight + coolSetpoint * (1.0 - weight) + Constant::Kelvin;
345 : }
346 0 : break;
347 : }
348 0 : default: {
349 : // error?
350 0 : Tin = assumedFloatingTemp + Constant::Kelvin;
351 0 : break;
352 : }
353 : }
354 : }
355 804 : bcs->slabConvectiveTemp = bcs->wallConvectiveTemp = bcs->slabRadiantTemp = bcs->wallRadiantTemp = Tin;
356 :
357 804 : bcs->gradeForcedTerm = kmPtr->surfaceConvMap[floorSurface].f;
358 804 : bcs->gradeConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].out;
359 804 : bcs->slabConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].in;
360 :
361 804 : if (!wallSurfaces.empty()) {
362 120 : bcs->extWallForcedTerm = kmPtr->surfaceConvMap[wallSurfaces[0]].f;
363 120 : bcs->extWallConvectionAlgorithm = kmPtr->surfaceConvMap[wallSurfaces[0]].out;
364 120 : bcs->intWallConvectionAlgorithm = kmPtr->surfaceConvMap[wallSurfaces[0]].in;
365 : } else {
366 : // If no wall surfaces, assume that any exposed foundation wall in Kiva uses
367 : // same algorithm as exterior grade
368 684 : bcs->extWallForcedTerm = kmPtr->surfaceConvMap[floorSurface].f;
369 684 : bcs->extWallConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].out;
370 : // No interior walls
371 : }
372 804 : }
373 :
374 2016 : void KivaInstanceMap::setBoundaryConditions(EnergyPlusData &state)
375 : {
376 2016 : std::shared_ptr<Kiva::BoundaryConditions> bcs = instance.bcs;
377 :
378 2016 : bcs->outdoorTemp = state.dataEnvrn->OutDryBulbTemp + Constant::Kelvin;
379 2016 : bcs->localWindSpeed = DataEnvironment::WindSpeedAt(state, instance.ground->foundation.grade.roughness);
380 2016 : bcs->windDirection = state.dataEnvrn->WindDir * Constant::DegToRadians;
381 2016 : bcs->solarAzimuth = std::atan2(state.dataEnvrn->SOLCOS(1), state.dataEnvrn->SOLCOS(2));
382 2016 : bcs->solarAltitude = Constant::PiOvr2 - std::acos(state.dataEnvrn->SOLCOS(3));
383 2016 : bcs->directNormalFlux = state.dataEnvrn->BeamSolarRad;
384 2016 : bcs->diffuseHorizontalFlux = state.dataEnvrn->DifSolarRad;
385 2016 : bcs->skyEmissivity = pow4(state.dataEnvrn->SkyTempKelvin) / pow4(bcs->outdoorTemp);
386 :
387 2016 : bcs->slabAbsRadiation = state.dataHeatBalSurf->SurfOpaqQRadSWInAbs(floorSurface) + // solar
388 2016 : state.dataHeatBal->SurfQdotRadIntGainsInPerArea(floorSurface) + // internal gains
389 2016 : state.dataHeatBalSurf->SurfQdotRadHVACInPerArea(floorSurface); // HVAC
390 :
391 2016 : bcs->slabConvectiveTemp = state.dataHeatBal->SurfTempEffBulkAir(floorSurface) + Constant::Kelvin;
392 2016 : bcs->slabRadiantTemp = ThermalComfort::CalcSurfaceWeightedMRT(state, floorSurface, false) + Constant::Kelvin;
393 2016 : bcs->gradeForcedTerm = kmPtr->surfaceConvMap[floorSurface].f;
394 2016 : bcs->gradeConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].out;
395 2016 : bcs->slabConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].in;
396 :
397 : // Calculate area weighted average for walls
398 2016 : Real64 QAtotal = 0.0;
399 2016 : Real64 Atotal = 0.0;
400 2016 : Real64 TARadTotal = 0.0;
401 2016 : Real64 TAConvTotal = 0.0;
402 2736 : for (int wl : wallSurfaces) {
403 720 : Real64 Q = state.dataHeatBalSurf->SurfOpaqQRadSWInAbs(wl) + // solar
404 720 : state.dataHeatBal->SurfQdotRadIntGainsInPerArea(wl) + // internal gains
405 720 : state.dataHeatBalSurf->SurfQdotRadHVACInPerArea(wl); // HVAC
406 :
407 720 : Real64 const &A = state.dataSurface->Surface(wl).Area;
408 :
409 720 : Real64 Trad = ThermalComfort::CalcSurfaceWeightedMRT(state, wl, false);
410 720 : Real64 Tconv = state.dataHeatBal->SurfTempEffBulkAir(wl);
411 :
412 720 : QAtotal += Q * A;
413 720 : TARadTotal += Trad * A;
414 720 : TAConvTotal += Tconv * A;
415 720 : Atotal += A;
416 2016 : }
417 :
418 2016 : if (Atotal > 0.0) {
419 288 : bcs->wallAbsRadiation = QAtotal / Atotal;
420 288 : bcs->wallRadiantTemp = TARadTotal / Atotal + Constant::Kelvin;
421 288 : bcs->wallConvectiveTemp = TAConvTotal / Atotal + Constant::Kelvin;
422 288 : bcs->extWallForcedTerm = kmPtr->surfaceConvMap[wallSurfaces[0]].f;
423 288 : bcs->extWallConvectionAlgorithm = kmPtr->surfaceConvMap[wallSurfaces[0]].out;
424 288 : bcs->intWallConvectionAlgorithm = kmPtr->surfaceConvMap[wallSurfaces[0]].in;
425 : } else { // No wall surfaces
426 : // If no wall surfaces, assume that any exposed foundation wall in Kiva uses
427 : // same algorithm as exterior grade
428 1728 : bcs->extWallForcedTerm = kmPtr->surfaceConvMap[floorSurface].f;
429 1728 : bcs->extWallConvectionAlgorithm = kmPtr->surfaceConvMap[floorSurface].out;
430 : }
431 2016 : }
432 :
433 796 : KivaManager::Settings::Settings()
434 796 : : soilK(0.864), soilRho(1510), soilCp(1260), groundSolarAbs(0.9), groundThermalAbs(0.9), groundRoughness(0.9), farFieldWidth(40.0),
435 796 : deepGroundBoundary(AUTO), deepGroundDepth(40.0), autocalculateDeepGroundDepth(true), minCellDim(0.02), maxGrowthCoeff(1.5), timestepType(HOURLY)
436 : {
437 796 : }
438 :
439 6 : KivaManager::WallGroup::WallGroup(Real64 exposedPerimeter, std::vector<int> wallIDs) : exposedPerimeter(exposedPerimeter), wallIDs(wallIDs)
440 : {
441 6 : }
442 :
443 6 : KivaManager::WallGroup::WallGroup() : exposedPerimeter(0.0)
444 : {
445 6 : }
446 :
447 796 : KivaManager::KivaManager() : timestep(3600), defaultAdded(false), defaultIndex(0)
448 : {
449 796 : }
450 :
451 796 : KivaManager::~KivaManager()
452 : {
453 796 : }
454 :
455 9 : void KivaManager::readWeatherData(EnergyPlusData &state)
456 : {
457 : // Below from OpenEPlusWeatherFile
458 18 : auto kivaWeatherFile = state.files.inputWeatherFilePath.open(state, "KivaManager::readWeatherFile");
459 :
460 : // Read in Header Information
461 : static Array1D_string const Header(8,
462 : {"LOCATION",
463 : "DESIGN CONDITIONS",
464 : "TYPICAL/EXTREME PERIODS",
465 : "GROUND TEMPERATURES",
466 : "HOLIDAYS/DAYLIGHT SAVING",
467 : "COMMENTS 1",
468 : "COMMENTS 2",
469 9 : "DATA PERIODS"});
470 :
471 9 : int HdLine = 1; // Look for first Header
472 9 : bool StillLooking = true;
473 81 : while (StillLooking) {
474 72 : auto LineResult = kivaWeatherFile.readLine();
475 72 : if (LineResult.eof) {
476 0 : ShowFatalError(
477 : state,
478 0 : format("Kiva::ReadWeatherFile: Unexpected End-of-File on EPW Weather file, while reading header information, looking for header={}",
479 : Header(HdLine)));
480 : }
481 :
482 : // Use headers to know how to read data to memory (e.g., number of periods, number of intervals)
483 72 : int endcol = LineResult.data.size();
484 72 : if (endcol > 0) {
485 72 : if (int(LineResult.data[endcol - 1]) == DataSystemVariables::iUnicode_end) {
486 0 : ShowSevereError(state, "OpenWeatherFile: EPW Weather File appears to be a Unicode or binary file.");
487 0 : ShowContinueError(state, "...This file cannot be read by this program. Please save as PC or Unix file and try again");
488 0 : ShowFatalError(state, "Program terminates due to previous condition.");
489 : }
490 : }
491 72 : std::string::size_type Pos = FindNonSpace(LineResult.data);
492 72 : std::string::size_type const HdPos = index(LineResult.data, Header(HdLine));
493 72 : if (Pos != HdPos) continue;
494 72 : Pos = index(LineResult.data, ',');
495 :
496 : // Below borrowed from ProcessEPWHeader
497 :
498 72 : if ((Pos == std::string::npos) && (!has_prefixi(Header(HdLine), "COMMENTS"))) {
499 0 : ShowSevereError(state, "Invalid Header line in in.epw -- no commas");
500 0 : ShowContinueError(state, format("Line={}", LineResult.data));
501 0 : ShowFatalError(state, "Previous conditions cause termination.");
502 : }
503 72 : if (Pos != std::string::npos) LineResult.data.erase(0, Pos + 1);
504 :
505 72 : if (Util::makeUPPER(Header(HdLine)) == "DATA PERIODS") {
506 : bool IOStatus;
507 9 : uppercase(LineResult.data);
508 9 : int NumHdArgs = 2;
509 9 : int Count = 1;
510 63 : while (Count <= NumHdArgs) {
511 54 : strip(LineResult.data);
512 54 : Pos = index(LineResult.data, ',');
513 54 : if (Pos == std::string::npos) {
514 9 : if (len(LineResult.data) == 0) {
515 0 : while (Pos == std::string::npos) {
516 0 : LineResult.update(kivaWeatherFile.readLine());
517 0 : strip(LineResult.data);
518 0 : uppercase(LineResult.data);
519 0 : Pos = index(LineResult.data, ',');
520 : }
521 : } else {
522 9 : Pos = len(LineResult.data);
523 : }
524 : }
525 :
526 54 : switch (Count) {
527 9 : case 1:
528 9 : NumHdArgs += 4 * Util::ProcessNumber(LineResult.data.substr(0, Pos), IOStatus);
529 : // TODO: Error if more than one period? Less than full year?
530 9 : break;
531 9 : case 2:
532 9 : kivaWeather.intervalsPerHour = Util::ProcessNumber(LineResult.data.substr(0, Pos), IOStatus);
533 9 : break;
534 36 : default:
535 36 : break;
536 : }
537 54 : LineResult.data.erase(0, Pos + 1);
538 54 : ++Count;
539 : }
540 : }
541 :
542 72 : ++HdLine;
543 72 : if (HdLine == 9) StillLooking = false;
544 72 : }
545 :
546 9 : bool ErrorFound = false;
547 : int WYear;
548 : int WMonth;
549 : int WDay;
550 : int WHour;
551 : int WMinute;
552 : Real64 DryBulb;
553 : Real64 DewPoint;
554 : Real64 RelHum;
555 : Real64 AtmPress;
556 : Real64 ETHoriz;
557 : Real64 ETDirect;
558 : Real64 IRHoriz;
559 : Real64 GLBHoriz;
560 : Real64 DirectRad;
561 : Real64 DiffuseRad;
562 : Real64 GLBHorizIllum;
563 : Real64 DirectNrmIllum;
564 : Real64 DiffuseHorizIllum;
565 : Real64 ZenLum;
566 : Real64 WindDir;
567 : Real64 WindSpeed;
568 : Real64 TotalSkyCover;
569 : Real64 OpaqueSkyCover;
570 : Real64 Visibility;
571 : Real64 CeilHeight;
572 : Real64 PrecipWater;
573 : Real64 AerosolOptDepth;
574 : Real64 SnowDepth;
575 : Real64 DaysSinceLastSnow;
576 : Real64 Albedo;
577 : Real64 LiquidPrecip;
578 : int PresWeathObs;
579 9 : Array1D_int PresWeathConds(9);
580 :
581 9 : Real64 totalDB = 0.0;
582 9 : int count = 0;
583 :
584 : while (true) {
585 78849 : auto WeatherDataLine = kivaWeatherFile.readLine();
586 78849 : if (WeatherDataLine.eof) {
587 9 : break;
588 : }
589 78840 : Weather::InterpretWeatherDataLine(state,
590 : WeatherDataLine.data,
591 : ErrorFound,
592 : WYear,
593 : WMonth,
594 : WDay,
595 : WHour,
596 : WMinute,
597 : DryBulb,
598 : DewPoint,
599 : RelHum,
600 : AtmPress,
601 : ETHoriz,
602 : ETDirect,
603 : IRHoriz,
604 : GLBHoriz,
605 : DirectRad,
606 : DiffuseRad,
607 : GLBHorizIllum,
608 : DirectNrmIllum,
609 : DiffuseHorizIllum,
610 : ZenLum,
611 : WindDir,
612 : WindSpeed,
613 : TotalSkyCover,
614 : OpaqueSkyCover,
615 : Visibility,
616 : CeilHeight,
617 : PresWeathObs,
618 : PresWeathConds,
619 : PrecipWater,
620 : AerosolOptDepth,
621 : SnowDepth,
622 : DaysSinceLastSnow,
623 : Albedo,
624 : LiquidPrecip);
625 :
626 : // Checks for missing value
627 78840 : if (DryBulb >= 99.9) {
628 0 : DryBulb = state.dataWeather->wvarsMissing.OutDryBulbTemp;
629 : }
630 78840 : if (DewPoint >= 99.9) {
631 0 : DewPoint = state.dataWeather->wvarsMissing.OutDewPointTemp;
632 : }
633 78840 : if (WindSpeed >= 999.0) {
634 0 : WindSpeed = state.dataWeather->wvarsMissing.WindSpeed;
635 : }
636 78840 : if (OpaqueSkyCover >= 99.0) {
637 0 : OpaqueSkyCover = state.dataWeather->wvarsMissing.OpaqueSkyCover;
638 : }
639 :
640 78840 : kivaWeather.dryBulb.push_back(DryBulb);
641 78840 : kivaWeather.windSpeed.push_back(WindSpeed);
642 :
643 78840 : Real64 OSky = OpaqueSkyCover;
644 78840 : Real64 TDewK = min(DryBulb, DewPoint) + Constant::Kelvin;
645 78840 : Real64 ESky = (0.787 + 0.764 * std::log(TDewK / Constant::Kelvin)) * (1.0 + 0.0224 * OSky - 0.0035 * pow_2(OSky) + 0.00028 * pow_3(OSky));
646 :
647 78840 : kivaWeather.skyEmissivity.push_back(ESky);
648 :
649 78840 : ++count;
650 78840 : totalDB += DryBulb;
651 157689 : }
652 :
653 : // Annual averages
654 9 : kivaWeather.annualAverageDrybulbTemp = totalDB / count;
655 9 : }
656 :
657 9 : bool KivaManager::setupKivaInstances(EnergyPlusData &state)
658 : {
659 9 : std::pair<EnergyPlusData *, std::string> contextPair{&state, ""};
660 9 : Kiva::setMessageCallback(kivaErrorCallback, &contextPair);
661 9 : bool ErrorsFound = false;
662 :
663 9 : if (state.dataZoneCtrls->GetZoneAirStatsInputFlag) {
664 9 : ZoneTempPredictorCorrector::GetZoneAirSetPoints(state);
665 9 : state.dataZoneCtrls->GetZoneAirStatsInputFlag = false;
666 : }
667 :
668 9 : readWeatherData(state);
669 :
670 9 : auto &Surfaces = state.dataSurface->Surface;
671 9 : auto &Constructs = state.dataConstruction->Construct;
672 9 : auto &Materials = state.dataMaterial->Material;
673 :
674 9 : int inst = 0;
675 9 : int surfNum = 1;
676 :
677 499 : for (auto &surface : Surfaces) {
678 490 : if (surface.ExtBoundCond == DataSurfaces::KivaFoundation && surface.Class == DataSurfaces::SurfaceClass::Floor) {
679 :
680 : // Find other surfaces associated with the same floor
681 23 : std::vector<int> wallSurfaces;
682 :
683 127 : for (auto &wl : foundationInputs[surface.OSCPtr].surfaces) {
684 104 : if (Surfaces(wl).Zone == surface.Zone && wl != surfNum) {
685 15 : if (Surfaces(wl).Class != DataSurfaces::SurfaceClass::Wall) {
686 0 : if (Surfaces(wl).Class == DataSurfaces::SurfaceClass::Floor) {
687 0 : ErrorsFound = true;
688 0 : ShowSevereError(state,
689 0 : format("Foundation:Kiva=\"{}\", only one floor per Foundation:Kiva Object allowed.",
690 0 : foundationInputs[surface.OSCPtr].name));
691 : } else {
692 0 : ErrorsFound = true;
693 0 : ShowSevereError(state,
694 0 : format("Foundation:Kiva=\"{}\", only floor and wall surfaces are allowed to reference Foundation Outside "
695 : "Boundary Conditions.",
696 0 : foundationInputs[surface.OSCPtr].name));
697 0 : ShowContinueError(state, format("Surface=\"{}\", is not a floor or wall.", Surfaces(wl).Name));
698 : }
699 : } else {
700 15 : wallSurfaces.push_back(wl);
701 : }
702 : }
703 23 : }
704 :
705 : // Calculate total exposed perimeter attributes
706 23 : std::vector<bool> isExposedPerimeter;
707 :
708 23 : bool userSetExposedPerimeter = false;
709 23 : bool useDetailedExposedPerimeter = false;
710 23 : Real64 exposedFraction = 0.0;
711 :
712 23 : auto &expPerimMap = state.dataSurfaceGeometry->exposedFoundationPerimeter.surfaceMap;
713 23 : if (expPerimMap.count(surfNum) == 1) {
714 23 : userSetExposedPerimeter = true;
715 23 : useDetailedExposedPerimeter = expPerimMap[surfNum].useDetailedExposedPerimeter;
716 23 : if (useDetailedExposedPerimeter) {
717 95 : for (bool s : expPerimMap[surfNum].isExposedPerimeter) {
718 76 : isExposedPerimeter.push_back(s);
719 19 : }
720 : } else {
721 4 : exposedFraction = expPerimMap[surfNum].exposedFraction;
722 : }
723 : } else {
724 0 : ErrorsFound = true;
725 0 : ShowSevereError(state,
726 0 : format("Surface=\"{}\", references a Foundation Outside Boundary Condition but there is no corresponding "
727 : "SURFACEPROPERTY:EXPOSEDFOUNDATIONPERIMETER object defined.",
728 0 : Surfaces(surfNum).Name));
729 : }
730 :
731 23 : Kiva::Polygon floorPolygon;
732 115 : for (std::size_t i = 0; i < surface.Vertex.size(); ++i) {
733 92 : auto const &v = surface.Vertex[i];
734 92 : floorPolygon.outer().push_back(Kiva::Point(v.x, v.y));
735 92 : if (!userSetExposedPerimeter) {
736 0 : isExposedPerimeter.push_back(true);
737 : }
738 : }
739 :
740 23 : Real64 totalPerimeter = 0.0;
741 115 : for (std::size_t i = 0; i < surface.Vertex.size(); ++i) {
742 : std::size_t iNext;
743 92 : if (i == surface.Vertex.size() - 1) {
744 23 : iNext = 0;
745 : } else {
746 69 : iNext = i + 1;
747 : }
748 92 : auto const &v = surface.Vertex[i];
749 92 : auto const &vNext = surface.Vertex[iNext];
750 92 : totalPerimeter += distance(v, vNext);
751 : }
752 :
753 23 : if (useDetailedExposedPerimeter) {
754 19 : Real64 total2DPerimeter = 0.0;
755 19 : Real64 exposed2DPerimeter = 0.0;
756 95 : for (std::size_t i = 0; i < floorPolygon.outer().size(); ++i) {
757 : std::size_t iNext;
758 76 : if (i == floorPolygon.outer().size() - 1) {
759 19 : iNext = 0;
760 : } else {
761 57 : iNext = i + 1;
762 : }
763 76 : auto const &p = floorPolygon.outer()[i];
764 76 : auto const &pNext = floorPolygon.outer()[iNext];
765 76 : Real64 perim = Kiva::getDistance(p, pNext);
766 76 : total2DPerimeter += perim;
767 76 : if (isExposedPerimeter[i]) {
768 28 : exposed2DPerimeter += perim;
769 : } else {
770 48 : exposed2DPerimeter += 0.0;
771 : }
772 : }
773 19 : exposedFraction = std::min(exposed2DPerimeter / total2DPerimeter, 1.0);
774 : }
775 :
776 23 : Real64 totalExposedPerimeter = exposedFraction * totalPerimeter;
777 :
778 : // Remaining exposed perimeter will be alloted to each instance as appropriate
779 23 : Real64 remainingExposedPerimeter = totalExposedPerimeter;
780 :
781 : // Get combinations of wall constructions and wall heights -- each different
782 : // combination gets its own Kiva instance. Combination map points each set
783 : // of construction and wall height to the associated exposed perimeter and
784 : // list of wall surface numbers.
785 23 : std::map<std::pair<int, Real64>, WallGroup> combinationMap;
786 :
787 23 : if (!wallSurfaces.empty()) {
788 18 : for (int wl : wallSurfaces) {
789 :
790 15 : auto const &v = Surfaces(wl).Vertex;
791 15 : size_t numVs = v.size();
792 : // Enforce quadrilateralism
793 15 : if (numVs > 4) {
794 0 : ShowWarningError(state,
795 0 : format("Foundation:Kiva=\"{}\", wall surfaces with more than four vertices referencing",
796 0 : foundationInputs[surface.OSCPtr].name));
797 0 : ShowContinueError(
798 : state, "...Foundation Outside Boundary Conditions may not be interpreted correctly in the 2D finite difference model.");
799 0 : ShowContinueError(state, format("Surface=\"{}\", has {} vertices.", Surfaces(wl).Name, numVs));
800 0 : ShowContinueError(state,
801 : "Consider separating the wall into separate surfaces, each spanning from the floor slab to the top of "
802 : "the foundation wall.");
803 : }
804 :
805 : // get coplanar points with floor to determine perimeter
806 : std::vector<int> coplanarPoints = Vectors::PointsInPlane(
807 15 : Surfaces(surfNum).Vertex, Surfaces(surfNum).Sides, Surfaces(wl).Vertex, Surfaces(wl).Sides, ErrorsFound);
808 :
809 15 : Real64 perimeter = 0.0;
810 :
811 : // if there are two consecutive coplanar points, add the distance
812 : // between them to the overall perimeter for this wall
813 45 : for (std::size_t i = 0; i < coplanarPoints.size(); ++i) {
814 30 : int p(coplanarPoints[i]);
815 30 : int pC = p == (int)v.size() ? 1 : p + 1; // next consecutive point
816 30 : int p2 = i == coplanarPoints.size() - 1 ? coplanarPoints[0] : coplanarPoints[i + 1]; // next coplanar point
817 :
818 30 : if (p2 == pC) { // if next coplanar point is the next consecutive point
819 15 : perimeter += distance(v(p), v(p2));
820 : }
821 : }
822 :
823 15 : if (perimeter == 0.0) {
824 0 : ShowWarningError(state, format("Foundation:Kiva=\"{}\".", foundationInputs[surface.OSCPtr].name));
825 0 : ShowContinueError(state, format(" Wall Surface=\"{}\", does not have any vertices that are", Surfaces(wl).Name));
826 0 : ShowContinueError(state, format(" coplanar with the corresponding Floor Surface=\"{}\".", Surfaces(surfNum).Name));
827 0 : ShowContinueError(state,
828 : " Simulation will continue using the distance between the two lowest points in the wall for the "
829 : "interface distance.");
830 :
831 : // sort vertices by Z-value
832 0 : std::vector<int> zs;
833 0 : for (std::size_t i = 0; i < numVs; ++i) {
834 0 : zs.push_back(i);
835 : }
836 0 : sort(zs.begin(), zs.end(), [v](int a, int b) { return v[a].z < v[b].z; });
837 0 : perimeter = distance(v[zs[0]], v[zs[1]]);
838 0 : }
839 :
840 15 : Real64 surfHeight = Surfaces(wl).get_average_height(state);
841 : // round to avoid numerical precision differences
842 15 : surfHeight = std::round((surfHeight)*1000.0) / 1000.0;
843 :
844 15 : if (combinationMap.count({Surfaces(wl).Construction, surfHeight}) == 0) {
845 : // create new combination
846 6 : std::vector<int> walls = {wl};
847 6 : combinationMap[{Surfaces(wl).Construction, surfHeight}] = WallGroup(perimeter, walls);
848 6 : } else {
849 : // add to existing combination
850 9 : combinationMap[{Surfaces(wl).Construction, surfHeight}].exposedPerimeter += perimeter;
851 9 : combinationMap[{Surfaces(wl).Construction, surfHeight}].wallIDs.push_back(wl);
852 : }
853 18 : }
854 : }
855 :
856 : // setup map to point floor surface to all related kiva instances
857 23 : Kiva::Aggregator floorAggregator(Kiva::Surface::ST_SLAB_CORE);
858 :
859 : // Loop through combinations and assign instances until there is no remaining exposed pereimeter
860 23 : bool assignKivaInstances = true;
861 23 : auto comb = combinationMap.begin();
862 50 : while (assignKivaInstances) {
863 : int constructionNum;
864 : Real64 wallHeight;
865 : Real64 perimeter;
866 27 : std::vector<int> wallIDs;
867 27 : if (comb != combinationMap.end()) {
868 : // Loop through wall combinations first
869 6 : constructionNum = comb->first.first;
870 6 : wallHeight = comb->first.second;
871 6 : perimeter = comb->second.exposedPerimeter;
872 6 : wallIDs = comb->second.wallIDs;
873 : } else {
874 : // Assign the remaining exposed perimeter to a slab instance
875 21 : constructionNum = foundationInputs[surface.OSCPtr].wallConstructionIndex;
876 21 : wallHeight = 0.0;
877 21 : perimeter = remainingExposedPerimeter;
878 : }
879 :
880 : Real64 floorWeight;
881 :
882 27 : if (totalExposedPerimeter > 0.001) {
883 24 : floorWeight = perimeter / totalExposedPerimeter;
884 : } else {
885 3 : floorWeight = 1.0;
886 : }
887 :
888 : // Copy foundation input for this instance
889 27 : Kiva::Foundation fnd = foundationInputs[surface.OSCPtr].foundation;
890 :
891 : // Exposed Perimeter
892 27 : fnd.useDetailedExposedPerimeter = useDetailedExposedPerimeter;
893 27 : fnd.isExposedPerimeter = isExposedPerimeter;
894 27 : fnd.exposedFraction = exposedFraction;
895 :
896 27 : if (constructionNum > 0) {
897 6 : auto &c = Constructs(constructionNum);
898 :
899 : // Clear layers
900 6 : fnd.wall.layers.clear();
901 :
902 : // Push back construction's layers
903 12 : for (int layer = 1; layer <= c.TotLayers; layer++) {
904 6 : auto const *mat = Materials(c.LayerPoint(layer));
905 6 : if (mat->ROnly) {
906 0 : ErrorsFound = true;
907 0 : ShowSevereError(state, format("Construction=\"{}\", constructions referenced by surfaces with a", c.Name));
908 0 : ShowContinueError(state, "\"Foundation\" Outside Boundary Condition must use only regular material objects");
909 0 : ShowContinueError(state, format("Material=\"{}\", is not a regular material object", mat->Name));
910 0 : return ErrorsFound;
911 : }
912 :
913 6 : Kiva::Layer tempLayer;
914 :
915 6 : tempLayer.material = Kiva::Material(mat->Conductivity, mat->Density, mat->SpecHeat);
916 6 : tempLayer.thickness = mat->Thickness;
917 :
918 6 : fnd.wall.layers.push_back(tempLayer);
919 : }
920 6 : fnd.wall.interior.emissivity = Constructs(constructionNum).InsideAbsorpThermal;
921 6 : fnd.wall.interior.absorptivity = Constructs(constructionNum).InsideAbsorpSolar;
922 6 : fnd.wall.exterior.emissivity = Constructs(constructionNum).OutsideAbsorpThermal;
923 6 : fnd.wall.exterior.absorptivity = Constructs(constructionNum).OutsideAbsorpSolar;
924 : }
925 :
926 : // Set slab construction
927 67 : for (int i = 0; i < Constructs(surface.Construction).TotLayers; ++i) {
928 40 : auto const *mat = Materials(Constructs(surface.Construction).LayerPoint[i]);
929 40 : if (mat->ROnly) {
930 0 : ErrorsFound = true;
931 0 : ShowSevereError(
932 0 : state, format("Construction=\"{}\", constructions referenced by surfaces with a", Constructs(surface.Construction).Name));
933 0 : ShowContinueError(state, "\"Foundation\" Outside Boundary Condition must use only regular material objects");
934 0 : ShowContinueError(state, format("Material=\"{}\", is not a regular material object", mat->Name));
935 0 : return ErrorsFound;
936 : }
937 :
938 40 : Kiva::Layer tempLayer;
939 :
940 40 : tempLayer.material = Kiva::Material(mat->Conductivity, mat->Density, mat->SpecHeat);
941 40 : tempLayer.thickness = mat->Thickness;
942 :
943 40 : fnd.slab.layers.push_back(tempLayer);
944 : }
945 :
946 27 : fnd.slab.interior.emissivity = Constructs(surface.Construction).InsideAbsorpThermal;
947 27 : fnd.slab.interior.absorptivity = Constructs(surface.Construction).InsideAbsorpSolar;
948 :
949 27 : fnd.foundationDepth = wallHeight;
950 :
951 27 : fnd.hasPerimeterSurface = false;
952 27 : fnd.perimeterSurfaceWidth = 0.0;
953 :
954 : // Add blocks
955 27 : auto intHIns = foundationInputs[surface.OSCPtr].intHIns; // (AUTO_OK_OBJ) intend to make a copy?
956 27 : auto intVIns = foundationInputs[surface.OSCPtr].intVIns; // (AUTO_OK_OBJ) intend to make a copy?
957 27 : auto extHIns = foundationInputs[surface.OSCPtr].extHIns; // (AUTO_OK_OBJ) intend to make a copy?
958 27 : auto extVIns = foundationInputs[surface.OSCPtr].extVIns; // (AUTO_OK_OBJ) intend to make a copy?
959 27 : auto footing = foundationInputs[surface.OSCPtr].footing; // (AUTO_OK_OBJ) intend to make a copy?
960 :
961 27 : if (std::abs(intHIns.width) > 0.0) {
962 7 : intHIns.z += fnd.foundationDepth + fnd.slab.totalWidth();
963 7 : fnd.inputBlocks.push_back(intHIns);
964 : }
965 27 : if (std::abs(intVIns.width) > 0.0) {
966 0 : fnd.inputBlocks.push_back(intVIns);
967 : }
968 27 : if (std::abs(extHIns.width) > 0.0) {
969 0 : extHIns.z += fnd.wall.heightAboveGrade;
970 0 : extHIns.x = fnd.wall.totalWidth();
971 0 : fnd.inputBlocks.push_back(extHIns);
972 : }
973 27 : if (std::abs(extVIns.width) > 0.0) {
974 8 : extVIns.x = fnd.wall.totalWidth();
975 8 : fnd.inputBlocks.push_back(extVIns);
976 : }
977 27 : if (std::abs(footing.width) > 0.0) {
978 0 : footing.z = fnd.foundationDepth + fnd.slab.totalWidth() + fnd.wall.depthBelowSlab;
979 0 : footing.x = fnd.wall.totalWidth() / 2.0 - footing.width / 2.0;
980 0 : fnd.inputBlocks.push_back(footing);
981 : }
982 :
983 27 : Real64 initDeepGroundDepth = fnd.deepGroundDepth;
984 27 : fnd.deepGroundDepth = getDeepGroundDepth(fnd);
985 :
986 27 : if (fnd.deepGroundDepth > initDeepGroundDepth) {
987 0 : ShowWarningError(state,
988 0 : format("Foundation:Kiva=\"{}\", the autocalculated deep ground depth ({:.3T} m) is shallower than "
989 : "foundation construction elements ({:.3T} m)",
990 0 : foundationInputs[surface.OSCPtr].name,
991 : initDeepGroundDepth,
992 0 : fnd.deepGroundDepth - 1.0));
993 0 : ShowContinueError(state,
994 0 : format("The deep ground depth will be set one meter below the lowest element ({:.3T} m)", fnd.deepGroundDepth));
995 : }
996 :
997 : // polygon
998 :
999 27 : fnd.polygon = floorPolygon;
1000 :
1001 27 : std::pair<EnergyPlusData *, std::string> contexPair2{&state, format("Foundation:Kiva=\"{}\"", foundationInputs[surface.OSCPtr].name)};
1002 27 : Kiva::setMessageCallback(kivaErrorCallback, &contexPair2);
1003 :
1004 : // point surface to associated ground instance(s)
1005 54 : kivaInstances.emplace_back(state,
1006 : fnd,
1007 : surfNum,
1008 : wallIDs,
1009 27 : surface.Zone,
1010 27 : foundationInputs[surface.OSCPtr].assumedIndoorTemperature,
1011 : floorWeight,
1012 : constructionNum,
1013 27 : this);
1014 :
1015 : // Floors can point to any number of foundation surfaces
1016 27 : floorAggregator.add_instance(kivaInstances[inst].instance.ground.get(), floorWeight);
1017 :
1018 : // Walls can only have one associated ground instance
1019 42 : for (int wl : wallIDs) {
1020 15 : surfaceMap[wl] = Kiva::Aggregator(Kiva::Surface::ST_WALL_INT);
1021 15 : surfaceMap[wl].add_instance(kivaInstances[inst].instance.ground.get(), 1.0);
1022 27 : }
1023 :
1024 : // Increment instance counter
1025 27 : inst++;
1026 :
1027 : // Increment wall combinations iterator
1028 27 : if (comb != combinationMap.end()) {
1029 6 : comb++;
1030 : }
1031 :
1032 27 : remainingExposedPerimeter -= perimeter;
1033 :
1034 27 : if (remainingExposedPerimeter < 0.001) {
1035 23 : assignKivaInstances = false;
1036 23 : if (remainingExposedPerimeter < -0.1) {
1037 0 : ErrorsFound = true;
1038 0 : ShowSevereError(state, format("For Floor Surface=\"{}\", the Wall surfaces referencing", Surfaces(surfNum).Name));
1039 0 : ShowContinueError(state, format(" the same Foundation:Kiva=\"{}\" have", foundationInputs[Surfaces(surfNum).OSCPtr].name));
1040 0 : ShowContinueError(state, " a combined length greater than the exposed perimeter of the foundation.");
1041 0 : ShowContinueError(state, " Ensure that each Wall surface shares at least one edge with the corresponding");
1042 0 : ShowContinueError(state, " Floor surface.");
1043 : }
1044 : }
1045 27 : }
1046 :
1047 23 : surfaceMap[surfNum] = floorAggregator;
1048 23 : }
1049 :
1050 490 : surfNum++;
1051 9 : }
1052 :
1053 : // Loop through Foundation surfaces and make sure they are all assigned to an instance
1054 47 : for (int surfNum2 : state.dataSurface->AllHTKivaSurfaceList) {
1055 38 : if (surfaceMap[surfNum2].size() == 0) {
1056 0 : ErrorsFound = true;
1057 0 : ShowSevereError(state, format("Surface=\"{}\" has a 'Foundation' Outside Boundary Condition", Surfaces(surfNum).Name));
1058 0 : ShowContinueError(state, format(" referencing Foundation:Kiva=\"{}\".", foundationInputs[Surfaces(surfNum).OSCPtr].name));
1059 0 : if (Surfaces(surfNum2).Class == DataSurfaces::SurfaceClass::Wall) {
1060 0 : ShowContinueError(state, format(" You must also reference Foundation:Kiva=\"{}\"", foundationInputs[Surfaces(surfNum).OSCPtr].name));
1061 0 : ShowContinueError(state,
1062 0 : format(" in a floor surface within the same Zone=\"{}\".", state.dataHeatBal->Zone(Surfaces(surfNum).Zone).Name));
1063 0 : } else if (Surfaces(surfNum2).Class == DataSurfaces::SurfaceClass::Floor) {
1064 0 : ShowContinueError(state, " However, this floor was never assigned to a Kiva instance.");
1065 0 : ShowContinueError(state, " This should not occur for floor surfaces. Please report to EnergyPlus Development Team.");
1066 : } else {
1067 0 : ShowContinueError(state, " Only floor and wall surfaces are allowed to reference 'Foundation' Outside Boundary Conditions.");
1068 0 : ShowContinueError(state, format(" Surface=\"{}\", is not a floor or wall.", Surfaces(surfNum).Name));
1069 : }
1070 : }
1071 9 : }
1072 :
1073 9 : print(state.files.eio,
1074 : "{}",
1075 : "! <Kiva Foundation Name>, Horizontal Cells, Vertical Cells, Total Cells, Total Exposed "
1076 : "Perimeter, Perimeter Fraction, Wall Height, Wall Construction, Floor Surface, Wall "
1077 : "Surface(s)\n");
1078 :
1079 36 : for (auto &kv : kivaInstances) {
1080 27 : auto grnd = kv.instance.ground.get(); // (AUTO_OK_OBJ)
1081 :
1082 27 : std::string constructionName;
1083 27 : if (kv.constructionNum <= 0) {
1084 21 : constructionName = "<Default Footing Wall Construction>";
1085 : } else {
1086 6 : constructionName = state.dataConstruction->Construct(kv.constructionNum).Name;
1087 : }
1088 :
1089 27 : std::string wallSurfaceString;
1090 42 : for (int wl : kv.wallSurfaces) {
1091 15 : wallSurfaceString += "," + state.dataSurface->Surface(wl).Name;
1092 27 : }
1093 :
1094 : static constexpr std::string_view fmt = "{},{},{},{},{:.2R},{:.2R},{:.2R},{},{}{}\n";
1095 27 : print(state.files.eio,
1096 : fmt,
1097 27 : foundationInputs[state.dataSurface->Surface(kv.floorSurface).OSCPtr].name,
1098 27 : grnd->nX,
1099 27 : grnd->nZ,
1100 0 : grnd->nX * grnd->nZ,
1101 27 : grnd->foundation.netPerimeter,
1102 27 : kv.floorWeight,
1103 27 : grnd->foundation.foundationDepth,
1104 : constructionName,
1105 27 : state.dataSurface->Surface(kv.floorSurface).Name,
1106 : wallSurfaceString);
1107 36 : }
1108 :
1109 9 : return ErrorsFound;
1110 9 : }
1111 :
1112 27 : Real64 KivaManager::getDeepGroundDepth(Kiva::Foundation fnd)
1113 : {
1114 27 : Real64 totalDepthOfWallBelowGrade = fnd.wall.depthBelowSlab + (fnd.foundationDepth - fnd.wall.heightAboveGrade) + fnd.slab.totalWidth();
1115 27 : if (fnd.deepGroundDepth < totalDepthOfWallBelowGrade + 1.0) {
1116 0 : fnd.deepGroundDepth = totalDepthOfWallBelowGrade + 1.0;
1117 : }
1118 42 : for (auto &block : fnd.inputBlocks) {
1119 : // Change temporary zero depth indicators to default foundation depth
1120 15 : if (block.depth == 0.0) {
1121 0 : block.depth = fnd.foundationDepth;
1122 : }
1123 15 : if (settings.deepGroundBoundary == Settings::AUTO) {
1124 : // Ensure automatically set deep ground depth is at least 1 meter below lowest block
1125 15 : if (block.z + block.depth + 1.0 > fnd.deepGroundDepth) {
1126 0 : fnd.deepGroundDepth = block.z + block.depth + 1.0;
1127 : }
1128 : }
1129 27 : }
1130 27 : return fnd.deepGroundDepth;
1131 : }
1132 :
1133 59 : void KivaManager::initKivaInstances(EnergyPlusData &state)
1134 : {
1135 : // initialize temperatures at the beginning of run environment
1136 260 : for (auto &kv : kivaInstances) {
1137 : // Start with steady-state solution
1138 201 : kv.initGround(state, kivaWeather);
1139 59 : }
1140 59 : calcKivaSurfaceResults(state);
1141 59 : }
1142 :
1143 576 : void KivaManager::calcKivaInstances(EnergyPlusData &state)
1144 : {
1145 : // calculate heat transfer through ground
1146 2592 : for (auto &kv : kivaInstances) {
1147 2016 : kv.setBoundaryConditions(state);
1148 2016 : kv.instance.calculate(timestep);
1149 2016 : kv.instance.calculate_surface_averages();
1150 : #ifdef GROUND_PLOT
1151 : if (state.dataEnvrn->Month == 1 && state.dataEnvrn->DayOfMonth == 1 && state.dataGlobal->HourOfDay == 1 && state.dataGlobal->TimeStep == 1) {
1152 : kv.plotDomain(state);
1153 : }
1154 : #endif
1155 576 : }
1156 :
1157 576 : calcKivaSurfaceResults(state);
1158 576 : }
1159 :
1160 : #ifdef GROUND_PLOT
1161 : void KivaInstanceMap::plotDomain(EnergyPlusData &state)
1162 : {
1163 : gp.createFrame(*instance.ground, format("{}/{} {}:00", state.dataEnvrn->Month, state.dataEnvrn->DayOfMonth, state.dataGlobal->HourOfDay));
1164 :
1165 : instance.ground->writeCSV(format("{}/{}.csv", debugDir, plotNum));
1166 :
1167 : plotNum++;
1168 : }
1169 : #endif
1170 :
1171 635 : void KivaManager::calcKivaSurfaceResults(EnergyPlusData &state)
1172 : {
1173 3435 : for (int surfNum : state.dataSurface->AllHTKivaSurfaceList) {
1174 2800 : std::pair<EnergyPlusData *, std::string> contextPair{&state, format("Surface=\"{}\"", state.dataSurface->Surface(surfNum).Name)};
1175 2800 : Kiva::setMessageCallback(kivaErrorCallback, &contextPair);
1176 2800 : surfaceMap[surfNum].calc_weighted_results();
1177 2800 : state.dataHeatBalSurf->SurfHConvInt(surfNum) = state.dataSurfaceGeometry->kivaManager.surfaceMap[surfNum].results.hconv;
1178 3435 : }
1179 635 : Kiva::setMessageCallback(kivaErrorCallback, nullptr);
1180 635 : }
1181 :
1182 796 : void KivaManager::defineDefaultFoundation(EnergyPlusData &state)
1183 : {
1184 :
1185 796 : Kiva::Foundation defFnd;
1186 :
1187 : // From settings
1188 796 : defFnd.soil = Kiva::Material(settings.soilK, settings.soilRho, settings.soilCp);
1189 796 : defFnd.grade.absorptivity = settings.groundSolarAbs;
1190 796 : defFnd.grade.emissivity = settings.groundThermalAbs;
1191 796 : defFnd.grade.roughness = settings.groundRoughness;
1192 796 : defFnd.farFieldWidth = settings.farFieldWidth;
1193 :
1194 796 : Real64 waterTableDepth = 0.1022 * state.dataEnvrn->Elevation;
1195 :
1196 796 : if (settings.deepGroundBoundary == Settings::AUTO) {
1197 795 : if (waterTableDepth <= 40.) {
1198 701 : defFnd.deepGroundDepth = waterTableDepth;
1199 701 : defFnd.deepGroundBoundary = Kiva::Foundation::DGB_FIXED_TEMPERATURE;
1200 : } else {
1201 94 : defFnd.deepGroundDepth = 40.;
1202 94 : defFnd.deepGroundBoundary = Kiva::Foundation::DGB_ZERO_FLUX;
1203 : }
1204 795 : if (!settings.autocalculateDeepGroundDepth) {
1205 0 : if (defFnd.deepGroundDepth != settings.deepGroundDepth) {
1206 0 : ShowWarningError(state, "Foundation:Kiva:Settings, when Deep-Ground Boundary Condition is Autoselect,");
1207 0 : ShowContinueError(state, format("the user-specified Deep-Ground Depth ({:.1R} m)", settings.deepGroundDepth));
1208 0 : ShowContinueError(state, format("will be overridden with the Autoselected depth ({:.1R} m)", defFnd.deepGroundDepth));
1209 : }
1210 : }
1211 1 : } else if (settings.deepGroundBoundary == Settings::ZERO_FLUX) {
1212 1 : defFnd.deepGroundDepth = settings.deepGroundDepth;
1213 1 : defFnd.deepGroundBoundary = Kiva::Foundation::DGB_ZERO_FLUX;
1214 : } else { // if (settings.deepGroundBoundary == Settings::GROUNDWATER)
1215 0 : defFnd.deepGroundDepth = settings.deepGroundDepth;
1216 0 : defFnd.deepGroundBoundary = Kiva::Foundation::DGB_FIXED_TEMPERATURE;
1217 : }
1218 :
1219 796 : defFnd.wall.heightAboveGrade = 0.2; // m
1220 :
1221 796 : Kiva::Material concrete;
1222 796 : concrete.conductivity = 1.95; // W/m-K
1223 796 : concrete.density = 2400; // kg/m3
1224 796 : concrete.specificHeat = 900; // J/kg-K
1225 :
1226 796 : Kiva::Layer defaultFoundationWall;
1227 796 : defaultFoundationWall.thickness = 0.3; // m
1228 796 : defaultFoundationWall.material = concrete;
1229 :
1230 796 : defFnd.wall.layers.push_back(defaultFoundationWall);
1231 :
1232 796 : defFnd.wall.interior.emissivity = 0.9;
1233 796 : defFnd.wall.interior.absorptivity = 0.9;
1234 796 : defFnd.wall.exterior.emissivity = 0.9;
1235 796 : defFnd.wall.exterior.absorptivity = 0.9;
1236 :
1237 796 : defFnd.wall.depthBelowSlab = 0.0;
1238 :
1239 796 : defFnd.mesh.minCellDim = settings.minCellDim;
1240 796 : defFnd.mesh.maxNearGrowthCoeff = settings.maxGrowthCoeff;
1241 796 : defFnd.mesh.maxDepthGrowthCoeff = settings.maxGrowthCoeff;
1242 796 : defFnd.mesh.maxInteriorGrowthCoeff = settings.maxGrowthCoeff;
1243 796 : defFnd.mesh.maxExteriorGrowthCoeff = settings.maxGrowthCoeff;
1244 :
1245 796 : defaultFoundation.foundation = defFnd;
1246 796 : defaultFoundation.name = "<Default Foundation>";
1247 796 : defaultFoundation.assumedIndoorTemperature = -9999;
1248 796 : }
1249 :
1250 2 : void KivaManager::addDefaultFoundation()
1251 : {
1252 2 : foundationInputs.push_back(defaultFoundation);
1253 2 : defaultIndex = static_cast<int>(foundationInputs.size() - 1u);
1254 2 : defaultAdded = true;
1255 2 : }
1256 :
1257 30 : int KivaManager::findFoundation(std::string const &name)
1258 : {
1259 30 : int fndNum = 0;
1260 30 : for (auto const &fnd : foundationInputs) {
1261 : // Check if foundation exists
1262 30 : if (fnd.name == name) {
1263 30 : return fndNum;
1264 : }
1265 0 : fndNum++;
1266 60 : }
1267 0 : return (int)foundationInputs.size();
1268 : }
1269 :
1270 : } // namespace EnergyPlus::HeatBalanceKivaManager
|