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