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