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