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 : #include <algorithm>
50 : #include <cassert>
51 : #include <cmath>
52 : #include <limits>
53 : #include <tuple>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Construction.hh>
57 : #include <EnergyPlus/Data/EnergyPlusData.hh>
58 : #include <EnergyPlus/DataEnvironment.hh>
59 : #include <EnergyPlus/DataHeatBalSurface.hh>
60 : #include <EnergyPlus/DataHeatBalance.hh>
61 : #include <EnergyPlus/DataLoopNode.hh>
62 : #include <EnergyPlus/DataZoneEquipment.hh>
63 : #include <EnergyPlus/Psychrometrics.hh>
64 : #include <EnergyPlus/UtilityRoutines.hh>
65 : #include <EnergyPlus/WindowManager.hh>
66 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
67 :
68 : namespace EnergyPlus::DataSurfaces {
69 :
70 : // MODULE INFORMATION:
71 : // AUTHOR Linda Lawrie
72 : // DATE WRITTEN May 2000
73 : // MODIFIED July 2003, (CC) added a flag for reference air temperature
74 : // Dec 2006, DJS (PSU) added logical ecoroof variable
75 : // Dec 2008, TH added new properties to SurfaceWindowCalc for thermochromic windows
76 : // Jul 2011, M.J. Witte and C.O. Pedersen, add new fields to OSC for last T, max and min
77 :
78 : // Using/Aliasing
79 : using namespace DataVectorTypes;
80 : using namespace DataBSDFWindow;
81 : using namespace DataHeatBalance;
82 : using namespace DataZoneEquipment;
83 : using namespace DataLoopNode;
84 : using namespace Psychrometrics;
85 : using namespace DataEnvironment;
86 : using namespace Window;
87 :
88 : Array1D_string const cExtBoundCondition({-6, 0}, {"KivaFoundation", "FCGround", "OSCM", "OSC", "OSC", "Ground", "ExternalEnvironment"});
89 :
90 755 : Surface2D::Surface2D(ShapeCat const shapeCat, int const axis, Vertices const &v, Vector2D const &vl, Vector2D const &vu)
91 755 : : axis(axis), vertices(v), vl(vl), vu(vu)
92 : {
93 755 : size_type const n(vertices.size());
94 755 : assert(n >= 3);
95 :
96 : // Reverse vertices order if clockwise
97 : // If sorting by y for slab method can detect clockwise faster by just comparing edges at bottom or top-most vertex
98 755 : Real64 area(0.0); // Actually 2x the signed area
99 3852 : for (Vertices::size_type i = 0; i < n; ++i) {
100 3097 : Vector2D const &v(vertices[i]);
101 3097 : Vector2D const &w(vertices[(i + 1) % n]);
102 3097 : area += (v.x * w.y) - (w.x * v.y);
103 : }
104 755 : if (area < 0.0) {
105 404 : std::reverse(vertices.begin() + 1, vertices.end()); // Vertices in clockwise order: Reverse all but first
106 : }
107 :
108 : // Set up edge vectors for ray--surface intersection tests
109 755 : edges.reserve(n);
110 3852 : for (Vertices::size_type i = 0; i < n; ++i) {
111 3097 : edges.push_back(vertices[(i + 1) % n] - vertices[i]);
112 : }
113 755 : if (shapeCat == ShapeCat::Rectangular) { // Set side length squared for ray--surface intersection tests
114 663 : assert(n == 4u);
115 663 : s1 = edges[0].magnitude_squared();
116 663 : s3 = edges[3].magnitude_squared();
117 92 : } else if ((shapeCat == ShapeCat::Nonconvex) || (n >= nVerticesBig)) { // Set up slabs
118 6 : assert(n >= 4u);
119 6 : slabYs.reserve(n);
120 104 : for (size_type i = 0; i < n; ++i) {
121 98 : slabYs.push_back(vertices[i].y);
122 : }
123 6 : std::sort(slabYs.begin(), slabYs.end()); // Sort the vertex y coordinates
124 6 : auto const iClip(std::unique(slabYs.begin(), slabYs.end())); // Remove duplicate y-coordinate elements
125 6 : slabYs.erase(iClip, slabYs.end());
126 6 : slabYs.shrink_to_fit();
127 76 : for (size_type iSlab = 0, iSlab_end = slabYs.size() - 1; iSlab < iSlab_end; ++iSlab) { // Create slabs
128 70 : Real64 xl(std::numeric_limits<Real64>::max());
129 70 : Real64 xu(std::numeric_limits<Real64>::lowest());
130 70 : Real64 const yl(slabYs[iSlab]);
131 70 : Real64 const yu(slabYs[iSlab + 1]);
132 70 : slabs.push_back(Slab(yl, yu));
133 70 : Slab &slab(slabs.back());
134 : using CrossEdge = std::tuple<Real64, Real64, size_type>;
135 : using CrossEdges = std::vector<CrossEdge>;
136 70 : CrossEdges crossEdges;
137 1816 : for (size_type i = 0; i < n; ++i) { // Find edges crossing slab
138 1746 : Vector2D const &v(vertices[i]);
139 1746 : Vector2D const &w(vertices[(i + 1) % n]);
140 1746 : if (((v.y <= yl) && (yu <= w.y)) || // Crosses upward
141 1645 : ((yu <= v.y) && (w.y <= yl))) // Crosses downward
142 : {
143 202 : Edge const &e(edges[i]);
144 202 : assert(e.y != 0.0);
145 202 : Real64 const exy(e.x / e.y);
146 202 : Real64 const xb(v.x + (yl - v.y) * exy); // x_bot coordinate where edge intersects yl
147 202 : Real64 const xt(v.x + (yu - v.y) * exy); // x_top coordinate where edge intersects yu
148 202 : xl = std::min(xl, std::min(xb, xt));
149 202 : xu = std::max(xu, std::max(xb, xt));
150 202 : crossEdges.push_back(std::make_tuple(xb, xt, i));
151 : }
152 : }
153 70 : slab.xl = xl;
154 70 : slab.xu = xu;
155 70 : assert(crossEdges.size() >= 2u);
156 70 : std::sort(crossEdges.begin(),
157 140 : crossEdges.end(),
158 401 : [](CrossEdge const &e1, CrossEdge const &e2) -> bool // Lambda to sort by x_mid
159 : {
160 401 : return std::get<0>(e1) + std::get<1>(e1) <
161 401 : std::get<0>(e2) + std::get<1>(e2); // Sort edges by x_mid: x_bot or x_top could have repeats with shared vertex
162 : });
163 : #ifndef NDEBUG // Check x_bot and x_top are also sorted
164 70 : Real64 xb(std::get<0>(crossEdges[0]));
165 70 : Real64 xt(std::get<1>(crossEdges[0]));
166 70 : Real64 const tol(1.0e-9 * std::max(std::abs(xl), std::abs(xu))); // EnergyPlus vertex precision is not tight so tolerance isn't either
167 272 : for (auto const &edge : crossEdges) { // Detect non-simple polygon with crossing edges
168 202 : Real64 const xbe(std::get<0>(edge));
169 202 : Real64 const xte(std::get<1>(edge));
170 202 : assert(xb <= xbe + tol);
171 202 : assert(xt <= xte + tol);
172 202 : xb = xbe;
173 202 : xt = xte;
174 70 : }
175 : #endif
176 70 : assert((shapeCat == ShapeCat::Nonconvex) || (crossEdges.size() == 2u));
177 272 : for (auto const &edge : crossEdges) {
178 202 : size_type const iEdge(std::get<2>(edge));
179 202 : slab.edges.push_back(iEdge); // Add edge to slab
180 202 : Vector2D const &e(edges[iEdge]);
181 202 : assert(e.y != 0.0); // Constant y edge can't be a crossing edge
182 202 : slab.edgesXY.push_back(e.y != 0.0 ? e.x / e.y : 0.0); // Edge inverse slope
183 70 : }
184 70 : assert(slab.edges.size() % 2 == 0u);
185 70 : assert(slab.edges.size() == slab.edgesXY.size());
186 70 : }
187 6 : }
188 755 : }
189 :
190 : // Set Precomputed Parameters
191 758 : void SurfaceData::set_computed_geometry()
192 : {
193 758 : if (Vertex.size() >= 3) { // Skip no-vertex "surfaces"
194 755 : shapeCat = computed_shapeCat();
195 755 : plane = computed_plane();
196 755 : surface2d = computed_surface2d();
197 : }
198 758 : }
199 :
200 4660202 : Real64 SurfaceData::getInsideAirTemperature(EnergyPlusData &state, const int t_SurfNum) const
201 : {
202 : // SUBROUTINE INFORMATION:
203 : // AUTHOR Simon Vidanovic
204 : // DATE WRITTEN June 2016
205 : // MODIFIED na
206 : // RE-ENGINEERED na
207 :
208 : // PURPOSE OF THIS SUBROUTINE:
209 : // Routine calculates reference air temperature for given surface (refactoring from the code)
210 : //
211 : // NOTE: This routine has been copy/pasted in the past in several different modules with slight
212 : // modifications at some of those places. It is quite logical that reference air temperature
213 : // for the surface is calculated as public function of SurfaceData structure (class) and is
214 : // later called as needed. Note that SurfaceNum had to be passed to this routine because of
215 : // access to global array SurfTempEffBulkAir. I would propose refactoring where SurfTempEffBulkAir
216 : // is part of SurfaceData structure and instead of calling SurfTempEffBulkAir( SurfNum ) it should
217 : // be called Surface( SurfNum ).TempEffBulkAir (Simon Vidanovic)
218 :
219 4660202 : Real64 RefAirTemp = 0;
220 :
221 : // determine reference air temperature for this surface
222 4660202 : auto &thisSpaceHB = state.dataZoneTempPredictorCorrector->spaceHeatBalance(this->spaceNum);
223 4660202 : switch (state.dataSurface->SurfTAirRef(t_SurfNum)) {
224 4 : case RefAirTemp::ZoneMeanAirTemp: {
225 4 : RefAirTemp = thisSpaceHB.MAT;
226 4 : } break;
227 4 : case RefAirTemp::AdjacentAirTemp: {
228 4 : RefAirTemp = state.dataHeatBal->SurfTempEffBulkAir(t_SurfNum);
229 4 : } break;
230 9 : case RefAirTemp::ZoneSupplyAirTemp: {
231 : // determine ZoneEquipConfigNum for this zone
232 : // ControlledZoneAirFlag = .FALSE.
233 : // ZoneEquipConfigNum = ZoneNum;
234 : // check whether this zone is a controlled zone or not
235 9 : if (!state.dataHeatBal->Zone(Zone).IsControlled) {
236 0 : ShowFatalError(state,
237 0 : format("Zones must be controlled for Ceiling-Diffuser Convection model. No system serves zone {}",
238 0 : state.dataHeatBal->Zone(Zone).Name));
239 : // return;
240 : }
241 : // determine supply air conditions
242 9 : Real64 SumSysMCp = 0;
243 9 : Real64 SumSysMCpT = 0;
244 9 : auto const &inletNodes = (state.dataHeatBal->doSpaceHeatBalance) ? state.dataZoneEquip->spaceEquipConfig(this->spaceNum).InletNode
245 9 : : state.dataZoneEquip->ZoneEquipConfig(Zone).InletNode;
246 27 : for (int nodeNum : inletNodes) {
247 18 : auto const &inNode = state.dataLoopNodes->Node(nodeNum);
248 18 : Real64 CpAir = PsyCpAirFnW(thisSpaceHB.airHumRat);
249 18 : SumSysMCp += inNode.MassFlowRate * CpAir;
250 18 : SumSysMCpT += inNode.MassFlowRate * CpAir * inNode.Temp;
251 : }
252 : // a weighted average of the inlet temperatures.
253 9 : if (SumSysMCp > 0.0) {
254 : // a weighted average of the inlet temperatures.
255 6 : RefAirTemp = SumSysMCpT / SumSysMCp;
256 : } else {
257 3 : RefAirTemp = thisSpaceHB.MAT;
258 : }
259 9 : } break;
260 4660185 : default: {
261 : // currently set to mean air temp but should add error warning here
262 4660185 : RefAirTemp = thisSpaceHB.MAT;
263 4660185 : } break;
264 : }
265 :
266 4660202 : return RefAirTemp;
267 : }
268 :
269 0 : Real64 SurfaceData::getOutsideAirTemperature(EnergyPlusData &state, const int t_SurfNum) const
270 : {
271 : // SUBROUTINE INFORMATION:
272 : // AUTHOR Simon Vidanovic
273 : // DATE WRITTEN June 2016
274 : // MODIFIED na
275 : // RE-ENGINEERED na
276 :
277 : // PURPOSE OF THIS SUBROUTINE:
278 : // Routine calculates outside air temperature for given surface.
279 : // Routine will return inside air temperature if it is interior surface. (refactoring from the code)
280 : //
281 : // NOTE: This routine has been copy/pasted in the past in several different modules with slight
282 : // modifications at some of those places. Exterior/interior surface air temperature is tied to surface.
283 0 : Real64 temperature = 0;
284 :
285 0 : if (ExtBoundCond > 0) // Interzone window
286 : {
287 0 : temperature = getInsideAirTemperature(state, t_SurfNum);
288 : } else {
289 0 : if (ExtWind) {
290 : // Window is exposed to wind (and possibly rain)
291 0 : if (state.dataEnvrn->IsRain) {
292 : // Raining: since wind exposed, outside window surface gets wet
293 0 : temperature = state.dataSurface->SurfOutWetBulbTemp(t_SurfNum);
294 : } else {
295 : // Dry
296 0 : temperature = state.dataSurface->SurfOutDryBulbTemp(t_SurfNum);
297 : }
298 : } else {
299 : // Window not exposed to wind
300 0 : temperature = state.dataSurface->SurfOutDryBulbTemp(t_SurfNum);
301 : }
302 : }
303 :
304 0 : return temperature;
305 : }
306 :
307 0 : Real64 SurfaceData::getOutsideIR(EnergyPlusData &state, const int t_SurfNum) const
308 : {
309 : // SUBROUTINE INFORMATION:
310 : // AUTHOR Simon Vidanovic
311 : // DATE WRITTEN July 2016
312 : // MODIFIED na
313 : // RE-ENGINEERED na
314 :
315 : // PURPOSE OF THIS SUBROUTINE:
316 : // Calculates outside infrared radiation
317 0 : Real64 value = 0;
318 0 : if (ExtBoundCond > 0) {
319 0 : value = state.dataSurface->SurfWinIRfromParentZone(ExtBoundCond) + state.dataHeatBalSurf->SurfQdotRadHVACInPerArea(ExtBoundCond);
320 : } else {
321 0 : Real64 tout = getOutsideAirTemperature(state, t_SurfNum) + Constant::Kelvin;
322 0 : value = Constant::StefanBoltzmann * pow_4(tout);
323 0 : value =
324 0 : ViewFactorSkyIR * (state.dataSurface->SurfAirSkyRadSplit(t_SurfNum) * Constant::StefanBoltzmann * pow_4(state.dataEnvrn->SkyTempKelvin) +
325 0 : (1.0 - state.dataSurface->SurfAirSkyRadSplit(t_SurfNum)) * value) +
326 0 : ViewFactorGroundIR * value;
327 : }
328 0 : return value;
329 : }
330 :
331 0 : Real64 SurfaceData::getSWIncident(EnergyPlusData &state, const int t_SurfNum)
332 : {
333 : // SUBROUTINE INFORMATION:
334 : // AUTHOR Simon Vidanovic
335 : // DATE WRITTEN July 2016
336 : // MODIFIED na
337 : // RE-ENGINEERED na
338 :
339 : // PURPOSE OF THIS SUBROUTINE:
340 : // Return total short wave incident to the surface
341 :
342 0 : return state.dataHeatBal->SurfQRadSWOutIncident(t_SurfNum) +
343 0 : state.dataHeatBal->EnclSolQSWRad(state.dataSurface->Surface(t_SurfNum).SolarEnclIndex);
344 : }
345 :
346 0 : int SurfaceData::getTotLayers(EnergyPlusData &state) const
347 : {
348 : // SUBROUTINE INFORMATION:
349 : // AUTHOR Simon Vidanovic
350 : // DATE WRITTEN August 2016
351 : // MODIFIED na
352 : // RE-ENGINEERED na
353 :
354 : // PURPOSE OF THIS SUBROUTINE:
355 : // Returns total number of layer for current surface
356 :
357 0 : auto &construction(state.dataConstruction->Construct(Construction));
358 0 : return construction.TotLayers;
359 : }
360 :
361 : // Computed Shape Category
362 755 : ShapeCat SurfaceData::computed_shapeCat() const
363 : {
364 755 : if (Shape == SurfaceShape::Triangle) {
365 5 : return ShapeCat::Triangular;
366 750 : } else if (Shape == SurfaceShape::TriangularWindow) {
367 0 : return ShapeCat::Triangular;
368 750 : } else if (Shape == SurfaceShape::TriangularDoor) {
369 0 : return ShapeCat::Triangular;
370 750 : } else if (Shape == SurfaceShape::Rectangle) {
371 619 : return ShapeCat::Rectangular;
372 131 : } else if (Shape == SurfaceShape::RectangularDoorWindow) {
373 44 : return ShapeCat::Rectangular;
374 87 : } else if (Shape == SurfaceShape::RectangularOverhang) {
375 0 : return ShapeCat::Rectangular;
376 87 : } else if (Shape == SurfaceShape::RectangularLeftFin) {
377 0 : return ShapeCat::Rectangular;
378 87 : } else if (Shape == SurfaceShape::RectangularRightFin) {
379 0 : return ShapeCat::Rectangular;
380 87 : } else if (IsConvex) {
381 83 : return ShapeCat::Convex;
382 : } else {
383 4 : return ShapeCat::Nonconvex;
384 : }
385 : }
386 :
387 : // Computed Plane
388 1510 : SurfaceData::Plane SurfaceData::computed_plane() const
389 : {
390 1510 : Vertices::size_type const n(Vertex.size());
391 1510 : assert(n >= 3);
392 1510 : Vector center(0.0); // Center (vertex average) point (not mass centroid)
393 1510 : Real64 a(0.0), b(0.0), c(0.0), d(0.0); // Plane coefficients
394 7704 : for (Vertices::size_type i = 0; i < n; ++i) { // Newell's method for robustness (not speed)
395 6194 : Vector const &v(Vertex[i]);
396 6194 : Vector const &w(Vertex[(i + 1) % n]);
397 6194 : a += (v.y - w.y) * (v.z + w.z);
398 6194 : b += (v.z - w.z) * (v.x + w.x);
399 6194 : c += (v.x - w.x) * (v.y + w.y);
400 6194 : center += v;
401 : }
402 1510 : d = -(dot(center, Vector(a, b, c)) / n); // center/n is the center point
403 1510 : return Plane(a, b, c, d); // a*x + b*y + c*z + d = 0
404 1510 : }
405 :
406 : // Computed axis-projected 2D surface
407 755 : Surface2D SurfaceData::computed_surface2d() const
408 : {
409 : // Project along axis of min surface range for 2D intersection use
410 755 : Vertices::size_type const n(Vertex.size());
411 755 : assert(n >= 3);
412 755 : assert(plane == computed_plane()); // Set plane first
413 : using Vertex2D = ObjexxFCL::Vector2<Real64>;
414 : using Vertices2D = ObjexxFCL::Array1D<Vertex2D>;
415 :
416 : // Select axis to project along
417 755 : Real64 const a(std::abs(plane.x)); // Plane normal x coordinate magnitude
418 755 : Real64 const b(std::abs(plane.y)); // Plane normal y coordinate magnitude
419 755 : Real64 const c(std::abs(plane.z)); // Plane normal z coordinate magnitude
420 755 : int const axis(a >= std::max(b, c) ? 0 : (b >= std::max(a, c) ? 1 : 2)); // Project along plane's normal's largest magnitude coordinate
421 :
422 : // Set up 2D surface
423 755 : Vertices2D v2d(n);
424 755 : Vector const &v0(Vertex[0]);
425 755 : if (axis == 0) { // Use y,z for 2D surface
426 197 : Real64 yl(v0.y), yu(v0.y); // y coordinate ranges
427 197 : Real64 zl(v0.z), zu(v0.z); // z coordinate ranges
428 982 : for (Vertices::size_type i = 0; i < n; ++i) {
429 785 : Vector const &v(Vertex[i]);
430 785 : v2d[i] = Vertex2D(v.y, v.z);
431 785 : yl = std::min(yl, v.y);
432 785 : yu = std::max(yu, v.y);
433 785 : zl = std::min(zl, v.z);
434 785 : zu = std::max(zu, v.z);
435 : }
436 197 : return Surface2D(shapeCat, axis, v2d, Vertex2D(yl, zl), Vertex2D(yu, zu));
437 558 : } else if (axis == 1) { // Use x,z for 2D surface
438 284 : Real64 xl(v0.x), xu(v0.x); // x coordinate ranges
439 284 : Real64 zl(v0.z), zu(v0.z); // z coordinate ranges
440 1419 : for (Vertices::size_type i = 0; i < n; ++i) {
441 1135 : Vector const &v(Vertex[i]);
442 1135 : v2d[i] = Vertex2D(v.x, v.z);
443 1135 : xl = std::min(xl, v.x);
444 1135 : xu = std::max(xu, v.x);
445 1135 : zl = std::min(zl, v.z);
446 1135 : zu = std::max(zu, v.z);
447 : }
448 284 : return Surface2D(shapeCat, axis, v2d, Vertex2D(xl, zl), Vertex2D(xu, zu));
449 : } else { // Use x,y for 2D surface
450 274 : Real64 xl(v0.x), xu(v0.x); // x coordinate ranges
451 274 : Real64 yl(v0.y), yu(v0.y); // y coordinate ranges
452 1451 : for (Vertices::size_type i = 0; i < n; ++i) {
453 1177 : Vector const &v(Vertex[i]);
454 1177 : v2d[i] = Vertex2D(v.x, v.y);
455 1177 : xl = std::min(xl, v.x);
456 1177 : xu = std::max(xu, v.x);
457 1177 : yl = std::min(yl, v.y);
458 1177 : yu = std::max(yu, v.y);
459 : }
460 274 : return Surface2D(shapeCat, axis, v2d, Vertex2D(xl, yl), Vertex2D(xu, yu));
461 : }
462 755 : }
463 :
464 9 : Real64 SurfaceData::get_average_height(EnergyPlusData &state) const
465 : {
466 9 : if (std::abs(SinTilt) < Constant::SmallDistance) {
467 1 : return 0.0;
468 : }
469 : using Vertex2D = ObjexxFCL::Vector2<Real64>;
470 : using Vertices2D = ObjexxFCL::Array1D<Vertex2D>;
471 8 : Vertices::size_type const n(Vertex.size());
472 8 : assert(n >= 3);
473 :
474 8 : Vertices2D v2d(n);
475 :
476 : // project onto 2D vertical plane
477 8 : Real64 xRef = Vertex[0].x;
478 8 : Real64 yRef = Vertex[0].y;
479 8 : Real64 const &saz(SinAzim);
480 8 : Real64 const &caz(CosAzim);
481 42 : for (Vertices::size_type i = 0; i < n; ++i) {
482 34 : Vector const &v(Vertex[i]);
483 34 : v2d[i] = Vertex2D(-(v.x - xRef) * caz + (v.y - yRef) * saz, v.z);
484 : }
485 :
486 : // piecewise linear integration
487 :
488 : // Get total width of polygon
489 8 : Real64 minX(v2d[0].x), maxX(v2d[0].x);
490 42 : for (Vertices::size_type i = 0; i < n; ++i) {
491 34 : Vertex2D const &v(v2d[i]);
492 34 : minX = std::min(minX, v.x);
493 34 : maxX = std::max(maxX, v.x);
494 : }
495 8 : Real64 totalWidth = maxX - minX;
496 :
497 8 : if (totalWidth == 0.0) {
498 : // This should never happen, but if it does, print a somewhat meaningful fatal error
499 : // (instead of allowing a divide by zero).
500 0 : ShowFatalError(state, format("Calculated projected surface width is zero for surface=\"{}\"", Name));
501 : }
502 :
503 8 : Real64 averageHeight = 0.0;
504 42 : for (Vertices::size_type i = 0; i < n; ++i) {
505 34 : Vertex2D const &v(v2d[i]);
506 :
507 : Vertex2D *v2;
508 34 : if (i == n - 1) {
509 8 : v2 = &v2d[0];
510 : } else {
511 26 : v2 = &v2d[i + 1];
512 : }
513 34 : averageHeight += 0.5 * (v.y + v2->y) * (v2->x - v.x) / totalWidth;
514 : }
515 8 : return std::abs(averageHeight) / SinTilt;
516 8 : }
517 :
518 12 : void SurfaceData::make_hash_key(EnergyPlusData &state, const int SurfNum)
519 : {
520 12 : auto &s_surf = state.dataSurface;
521 :
522 12 : calcHashKey = SurfaceCalcHashKey();
523 12 : calcHashKey.Construction = Construction;
524 12 : calcHashKey.Azimuth = round(Azimuth * 10.0) / 10.0;
525 12 : calcHashKey.Tilt = round(Tilt * 10.0) / 10.0;
526 12 : calcHashKey.Height = round(Height * 10.0) / 10.0;
527 12 : calcHashKey.Zone = Zone;
528 12 : calcHashKey.EnclIndex = SolarEnclIndex;
529 12 : calcHashKey.TAirRef = s_surf->SurfTAirRef(SurfNum);
530 :
531 12 : int extBoundCond = s_surf->Surface(SurfNum).ExtBoundCond;
532 12 : if (extBoundCond > 0) {
533 0 : calcHashKey.ExtZone = s_surf->Surface(extBoundCond).Zone;
534 0 : calcHashKey.ExtEnclIndex = s_surf->Surface(extBoundCond).SolarEnclIndex;
535 0 : calcHashKey.ExtCond = 1;
536 : } else {
537 12 : calcHashKey.ExtZone = 0;
538 12 : calcHashKey.ExtEnclIndex = 0;
539 12 : calcHashKey.ExtCond = extBoundCond;
540 : }
541 :
542 12 : calcHashKey.ExtSolar = ExtSolar;
543 12 : calcHashKey.ExtWind = ExtWind;
544 12 : calcHashKey.ViewFactorGround = round(ViewFactorGround * 10.0) / 10.0;
545 12 : calcHashKey.ViewFactorSky = round(ViewFactorSky * 10.0) / 10.0;
546 :
547 12 : calcHashKey.HeatTransferAlgorithm = HeatTransferAlgorithm;
548 12 : calcHashKey.intConvModel = s_surf->surfIntConv(SurfNum).model;
549 12 : calcHashKey.extConvModel = s_surf->surfExtConv(SurfNum).model;
550 12 : calcHashKey.intConvUserModelNum = s_surf->surfIntConv(SurfNum).userModelNum;
551 12 : calcHashKey.extConvUserModelNum = s_surf->surfExtConv(SurfNum).userModelNum;
552 12 : calcHashKey.OSCPtr = OSCPtr;
553 12 : calcHashKey.OSCMPtr = OSCMPtr;
554 :
555 12 : calcHashKey.FrameDivider = FrameDivider;
556 12 : calcHashKey.SurfWinStormWinConstr = s_surf->SurfWinStormWinConstr(SurfNum);
557 :
558 12 : calcHashKey.MaterialMovInsulExt = s_surf->extMovInsuls(SurfNum).matNum;
559 12 : calcHashKey.MaterialMovInsulInt = s_surf->intMovInsuls(SurfNum).matNum;
560 12 : calcHashKey.movInsulExtSchedNum = (s_surf->extMovInsuls(SurfNum).sched == nullptr) ? -1 : s_surf->extMovInsuls(SurfNum).sched->Num;
561 12 : calcHashKey.movInsulIntSchedNum = (s_surf->intMovInsuls(SurfNum).sched == nullptr) ? -1 : s_surf->intMovInsuls(SurfNum).sched->Num;
562 :
563 12 : calcHashKey.externalShadingSchedNum =
564 12 : (s_surf->Surface(SurfNum).surfExternalShadingSched != nullptr) ? s_surf->Surface(SurfNum).surfExternalShadingSched->Num : -1;
565 12 : calcHashKey.SurroundingSurfacesNum = s_surf->Surface(SurfNum).SurfSurroundingSurfacesNum;
566 12 : calcHashKey.LinkedOutAirNode = s_surf->Surface(SurfNum).SurfLinkedOutAirNode;
567 12 : calcHashKey.outsideHeatSourceTermSchedNum = (outsideHeatSourceTermSched != nullptr) ? outsideHeatSourceTermSched->Num : -1;
568 12 : calcHashKey.insideHeatSourceTermSchedNum = (insideHeatSourceTermSched != nullptr) ? insideHeatSourceTermSched->Num : -1;
569 12 : calcHashKey.ViewFactorSrdSurfs = s_surf->Surface(SurfNum).ViewFactorSrdSurfs;
570 12 : }
571 :
572 12 : void SurfaceData::set_representative_surface(EnergyPlusData &state, const int SurfNum)
573 : {
574 : // Make hash key for this surface (used to determine uniqueness)
575 12 : state.dataSurface->Surface(SurfNum).make_hash_key(state, SurfNum);
576 : // Insert surface key into map. If key already exists, it will not be added.
577 : // Assign the representative surface number based on the first instance of the identical key
578 12 : state.dataSurface->Surface(SurfNum).RepresentativeCalcSurfNum =
579 24 : state.dataSurface->RepresentativeSurfaceMap.insert({state.dataSurface->Surface(SurfNum).calcHashKey, SurfNum}).first->second;
580 :
581 12 : state.dataSurface->Surface(state.dataSurface->Surface(SurfNum).RepresentativeCalcSurfNum).ConstituentSurfaceNums.push_back(SurfNum);
582 12 : }
583 :
584 : // Functions
585 :
586 249958 : void SetSurfaceOutBulbTempAt(EnergyPlusData &state)
587 : {
588 249958 : if (state.dataEnvrn->SiteTempGradient == 0.0) {
589 0 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
590 0 : state.dataSurface->SurfOutDryBulbTemp(SurfNum) = state.dataEnvrn->OutDryBulbTemp;
591 0 : state.dataSurface->SurfOutWetBulbTemp(SurfNum) = state.dataEnvrn->OutWetBulbTemp;
592 : }
593 : } else {
594 249958 : Real64 const BaseDryTemp(state.dataEnvrn->OutDryBulbTemp + state.dataEnvrn->WeatherFileTempModCoeff);
595 249958 : Real64 const BaseWetTemp(state.dataEnvrn->OutWetBulbTemp + state.dataEnvrn->WeatherFileTempModCoeff);
596 2353945 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
597 : // Base temperatures at Z = 0 (C)
598 2103987 : Real64 const Z(state.dataSurface->Surface(SurfNum).Centroid.z); // Centroid value
599 2103987 : if (Z <= 0.0) {
600 366782 : state.dataSurface->SurfOutDryBulbTemp(SurfNum) = BaseDryTemp;
601 366782 : state.dataSurface->SurfOutWetBulbTemp(SurfNum) = BaseWetTemp;
602 : } else {
603 1737205 : Real64 GradientDividend = state.dataEnvrn->SiteTempGradient * DataEnvironment::EarthRadius * Z;
604 1737205 : Real64 GradientDivisor = DataEnvironment::EarthRadius + Z;
605 1737205 : state.dataSurface->SurfOutDryBulbTemp(SurfNum) = BaseDryTemp - GradientDividend / GradientDivisor;
606 1737205 : state.dataSurface->SurfOutWetBulbTemp(SurfNum) = BaseWetTemp - GradientDividend / GradientDivisor;
607 : }
608 : }
609 : }
610 249958 : }
611 :
612 249956 : void CheckSurfaceOutBulbTempAt(EnergyPlusData &state)
613 : {
614 : // Using/Aliasing
615 : using DataEnvironment::SetOutBulbTempAt_error;
616 :
617 249956 : Real64 minBulb = 0.0;
618 2353940 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
619 2103984 : minBulb = min(minBulb, state.dataSurface->SurfOutDryBulbTemp(SurfNum), state.dataSurface->SurfOutWetBulbTemp(SurfNum));
620 2103984 : if (minBulb < -100.0) {
621 0 : SetOutBulbTempAt_error(state, "Surface", state.dataSurface->Surface(SurfNum).Centroid.z, state.dataSurface->Surface(SurfNum).Name);
622 : }
623 : }
624 249956 : }
625 :
626 249956 : void SetSurfaceWindSpeedAt(EnergyPlusData &state)
627 : {
628 249956 : Real64 const fac(state.dataEnvrn->WindSpeed * state.dataEnvrn->WeatherFileWindModCoeff *
629 249956 : std::pow(state.dataEnvrn->SiteWindBLHeight, -state.dataEnvrn->SiteWindExp));
630 249956 : if (state.dataEnvrn->SiteWindExp == 0.0) {
631 0 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
632 0 : state.dataSurface->SurfOutWindSpeed(SurfNum) = state.dataEnvrn->WindSpeed;
633 : }
634 : } else {
635 :
636 2353940 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
637 2103984 : if (!state.dataSurface->Surface(SurfNum).ExtWind) {
638 764659 : continue;
639 : }
640 1339325 : Real64 const Z(state.dataSurface->Surface(SurfNum).Centroid.z); // Centroid value
641 1339325 : if (Z <= 0.0) {
642 52141 : state.dataSurface->SurfOutWindSpeed(SurfNum) = 0.0;
643 : } else {
644 : // [Met] - at meterological Station, Height of measurement is usually 10m above ground
645 : // LocalWindSpeed = Windspeed [Met] * (Wind Boundary LayerThickness [Met]/Height [Met])**Wind Exponent[Met] &
646 : // * (Height above ground / Site Wind Boundary Layer Thickness) ** Site Wind Exponent
647 1287184 : state.dataSurface->SurfOutWindSpeed(SurfNum) = fac * std::pow(Z, state.dataEnvrn->SiteWindExp);
648 : }
649 : }
650 : }
651 249956 : }
652 :
653 249956 : void SetSurfaceWindDirAt(EnergyPlusData &state)
654 : {
655 2353940 : for (int SurfNum = 1; SurfNum <= state.dataSurface->TotSurfaces; SurfNum++) {
656 2103984 : state.dataSurface->SurfOutWindDir(SurfNum) = state.dataEnvrn->WindDir;
657 : }
658 249956 : }
659 :
660 96 : std::string cSurfaceClass(SurfaceClass const ClassNo)
661 : {
662 :
663 : // FUNCTION INFORMATION:
664 : // AUTHOR Linda Lawrie
665 : // DATE WRITTEN May 2006
666 : // MODIFIED na
667 : // RE-ENGINEERED na
668 :
669 : // PURPOSE OF THIS FUNCTION:
670 : // This function returns a string based on class number.
671 :
672 : // Return value
673 96 : std::string ClassName;
674 :
675 96 : switch (ClassNo) {
676 79 : case SurfaceClass::Wall: {
677 79 : ClassName = "Wall";
678 79 : } break;
679 7 : case SurfaceClass::Floor: {
680 7 : ClassName = "Floor";
681 7 : } break;
682 7 : case SurfaceClass::Roof: {
683 7 : ClassName = "Roof";
684 7 : } break;
685 3 : case SurfaceClass::Window: {
686 3 : ClassName = "Window";
687 3 : } break;
688 0 : case SurfaceClass::GlassDoor: {
689 0 : ClassName = "Glass Door";
690 0 : } break;
691 0 : case SurfaceClass::Door: {
692 0 : ClassName = "Door";
693 0 : } break;
694 0 : case SurfaceClass::TDD_Dome: {
695 0 : ClassName = "TubularDaylightDome";
696 0 : } break;
697 0 : case SurfaceClass::TDD_Diffuser: {
698 0 : ClassName = "TubularDaylightDiffuser";
699 0 : } break;
700 0 : case SurfaceClass::IntMass: {
701 0 : ClassName = "Internal Mass";
702 0 : } break;
703 0 : case SurfaceClass::Shading: {
704 0 : ClassName = "Shading";
705 0 : } break;
706 0 : case SurfaceClass::Detached_B: {
707 0 : ClassName = "Detached Shading:Building";
708 0 : } break;
709 0 : case SurfaceClass::Detached_F: {
710 0 : ClassName = "Detached Shading:Fixed";
711 0 : } break;
712 0 : default: {
713 0 : ClassName = "Invalid/Unknown";
714 0 : } break;
715 : }
716 :
717 96 : return ClassName;
718 0 : }
719 0 : Real64 AbsFrontSide(EnergyPlusData &state, int SurfNum)
720 : {
721 : Real64 AbsorptanceFromExteriorFrontSide =
722 0 : (state.dataSurface->SurfWinExtBeamAbsByShade(SurfNum) + state.dataSurface->SurfWinExtDiffAbsByShade(SurfNum)) *
723 0 : state.dataSurface->SurfWinShadeAbsFacFace1(SurfNum);
724 : Real64 AbsorptanceFromInteriorFrontSide =
725 0 : (state.dataSurface->SurfWinIntBeamAbsByShade(SurfNum) + state.dataSurface->SurfWinIntSWAbsByShade(SurfNum)) *
726 0 : state.dataSurface->SurfWinShadeAbsFacFace2(SurfNum);
727 0 : return AbsorptanceFromExteriorFrontSide + AbsorptanceFromInteriorFrontSide;
728 : }
729 :
730 0 : Real64 AbsBackSide(EnergyPlusData &state, int SurfNum)
731 : {
732 : Real64 AbsorptanceFromInteriorBackSide =
733 0 : (state.dataSurface->SurfWinIntBeamAbsByShade(SurfNum) + state.dataSurface->SurfWinIntSWAbsByShade(SurfNum)) *
734 0 : state.dataSurface->SurfWinShadeAbsFacFace1(SurfNum);
735 : Real64 AbsorptanceFromExteriorBackSide =
736 0 : (state.dataSurface->SurfWinExtBeamAbsByShade(SurfNum) + state.dataSurface->SurfWinExtDiffAbsByShade(SurfNum)) *
737 0 : state.dataSurface->SurfWinShadeAbsFacFace2(SurfNum);
738 0 : return AbsorptanceFromExteriorBackSide + AbsorptanceFromInteriorBackSide;
739 : }
740 :
741 116 : void GetVariableAbsorptanceSurfaceList(EnergyPlusData &state)
742 : {
743 116 : if (!state.dataMaterial->AnyVariableAbsorptance) {
744 116 : return;
745 : }
746 0 : for (int surfNum : state.dataSurface->AllHTSurfaceList) {
747 0 : auto const &thisSurface = state.dataSurface->Surface(surfNum);
748 0 : auto const &thisConstruct = state.dataConstruction->Construct(thisSurface.Construction);
749 0 : if (thisConstruct.TotLayers == 0) {
750 0 : continue;
751 : }
752 0 : if (thisConstruct.LayerPoint(1) == 0) {
753 0 : continue; // error finding material number
754 : }
755 0 : auto const *mat = state.dataMaterial->materials(thisConstruct.LayerPoint(1));
756 0 : if (mat->group != Material::Group::Regular) {
757 0 : continue;
758 : }
759 :
760 0 : if (mat->absorpVarCtrlSignal != Material::VariableAbsCtrlSignal::Invalid) {
761 : // check for dynamic coating defined on interior surface
762 0 : if (thisSurface.ExtBoundCond != ExternalEnvironment) {
763 0 : ShowWarningError(state,
764 0 : format("MaterialProperty:VariableAbsorptance defined on an interior surface, {}. This VariableAbsorptance property "
765 : "will be ignored here",
766 0 : thisSurface.Name));
767 : } else {
768 0 : state.dataSurface->AllVaryAbsOpaqSurfaceList.push_back(surfNum);
769 : }
770 : }
771 0 : }
772 : // check for dynamic coating defined on the non-outside layer of a construction
773 0 : for (int ConstrNum = 1; ConstrNum <= state.dataHeatBal->TotConstructs; ++ConstrNum) {
774 0 : auto const &thisConstruct = state.dataConstruction->Construct(ConstrNum);
775 0 : for (int Layer = 2; Layer <= thisConstruct.TotLayers; ++Layer) {
776 0 : auto const *mat = state.dataMaterial->materials(thisConstruct.LayerPoint(Layer));
777 0 : if (mat->group != Material::Group::Regular) {
778 0 : continue;
779 : }
780 0 : if (mat->absorpVarCtrlSignal != Material::VariableAbsCtrlSignal::Invalid) {
781 0 : ShowWarningError(state,
782 0 : format("MaterialProperty:VariableAbsorptance defined on a inside-layer materials, {}. This VariableAbsorptance "
783 : "property will be ignored here",
784 0 : mat->Name));
785 : }
786 : }
787 : }
788 : } // GetVariableAbsorptanceSurfaceList()
789 :
790 0 : Compass4 AzimuthToCompass4(Real64 azimuth)
791 : {
792 0 : assert(azimuth >= 0.0 && azimuth < 360.0);
793 0 : for (int c4 = 0; c4 < static_cast<int>(Compass4::Num); ++c4) {
794 0 : Real64 lo = Compass4AzimuthLo[c4];
795 0 : Real64 hi = Compass4AzimuthHi[c4];
796 0 : if (lo > hi) {
797 0 : if (azimuth >= lo || azimuth < hi) {
798 0 : return static_cast<Compass4>(c4);
799 : }
800 : } else {
801 0 : if (azimuth >= lo && azimuth < hi) {
802 0 : return static_cast<Compass4>(c4);
803 : }
804 : }
805 : }
806 0 : assert(false);
807 : return Compass4::Invalid;
808 : }
809 :
810 806 : Compass8 AzimuthToCompass8(Real64 azimuth)
811 : {
812 806 : assert(azimuth >= 0.0 && azimuth < 360.0);
813 3390 : for (int c8 = 0; c8 < static_cast<int>(Compass8::Num); ++c8) {
814 3390 : Real64 lo = Compass8AzimuthLo[c8];
815 3390 : Real64 hi = Compass8AzimuthHi[c8];
816 3390 : if (lo > hi) {
817 806 : if (azimuth >= lo || azimuth < hi) {
818 158 : return static_cast<Compass8>(c8);
819 : }
820 : } else {
821 2584 : if (azimuth >= lo && azimuth < hi) {
822 648 : return static_cast<Compass8>(c8);
823 : }
824 : }
825 : }
826 0 : assert(false);
827 : return Compass8::Invalid;
828 : }
829 :
830 : } // namespace EnergyPlus::DataSurfaces
|