File size: 11,272 Bytes
845939b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
"""
Drapery module for HVAC Load Calculator.
This module provides classes and functions for handling drapery properties
and calculating their effects on window heat transfer.

Based on ASHRAE principles for drapery thermal characteristics.
"""

from typing import Dict, Any, Optional, Tuple
from enum import Enum


class DraperyOpenness(Enum):
    """Enum for drapery openness classification."""
    OPEN = "Open (>25%)"
    SEMI_OPEN = "Semi-open (7-25%)"
    CLOSED = "Closed (0-7%)"


class DraperyColor(Enum):
    """Enum for drapery color/reflectance classification."""
    DARK = "Dark (0-25%)"
    MEDIUM = "Medium (25-50%)"
    LIGHT = "Light (>50%)"


class Drapery:
    """Class for drapery properties and calculations."""
    
    def __init__(self, 
                 openness: float = 0.05, 
                 reflectance: float = 0.5, 
                 transmittance: float = 0.3, 
                 fullness: float = 1.0,
                 enabled: bool = True):
        """
        Initialize drapery object.
        
        Args:
            openness: Openness factor (0-1), fraction of fabric area that is open
            reflectance: Reflectance factor (0-1), fraction of incident radiation reflected
            transmittance: Transmittance factor (0-1), fraction of incident radiation transmitted
            fullness: Fullness factor (0-2), ratio of fabric width to covered width
            enabled: Whether the drapery is enabled/present
        """
        self.openness = max(0.0, min(1.0, openness))
        self.reflectance = max(0.0, min(1.0, reflectance))
        self.transmittance = max(0.0, min(1.0, transmittance))
        self.fullness = max(0.0, min(2.0, fullness))
        self.enabled = enabled
        
        # Calculate derived properties
        self.absorptance = 1.0 - self.reflectance - self.transmittance
        
        # Classify drapery based on openness and reflectance
        self.openness_class = self._classify_openness(self.openness)
        self.color_class = self._classify_color(self.reflectance)
    
    @staticmethod
    def _classify_openness(openness: float) -> DraperyOpenness:
        """Classify drapery based on openness factor."""
        if openness > 0.25:
            return DraperyOpenness.OPEN
        elif openness > 0.07:
            return DraperyOpenness.SEMI_OPEN
        else:
            return DraperyOpenness.CLOSED
    
    @staticmethod
    def _classify_color(reflectance: float) -> DraperyColor:
        """Classify drapery based on reflectance factor."""
        if reflectance > 0.5:
            return DraperyColor.LIGHT
        elif reflectance > 0.25:
            return DraperyColor.MEDIUM
        else:
            return DraperyColor.DARK
    
    def get_classification(self) -> str:
        """Get drapery classification string."""
        openness_map = {
            DraperyOpenness.OPEN: "I",
            DraperyOpenness.SEMI_OPEN: "II",
            DraperyOpenness.CLOSED: "III"
        }
        
        color_map = {
            DraperyColor.DARK: "D",
            DraperyColor.MEDIUM: "M",
            DraperyColor.LIGHT: "L"
        }
        
        return f"{openness_map[self.openness_class]}{color_map[self.color_class]}"
    
    def calculate_iac(self, glazing_shgc: float = 0.87) -> float:
        """
        Calculate Interior Attenuation Coefficient (IAC) for the drapery.
        
        The IAC represents the fraction of heat flow that enters the room
        after being modified by the drapery.
        
        Args:
            glazing_shgc: Solar Heat Gain Coefficient of the glazing (default: 0.87 for clear glass)
            
        Returns:
            IAC value (0-1)
        """
        if not self.enabled:
            return 1.0  # No attenuation if drapery is not enabled
        
        # Calculate base IAC for flat drapery (no fullness)
        # This is based on the principles from the Keyes Universal Chart
        # and ASHRAE's IAC calculation methods
        
        # Calculate yarn reflectance (based on openness and reflectance)
        if self.openness < 0.0001:  # Prevent division by zero
            yarn_reflectance = self.reflectance
        else:
            yarn_reflectance = self.reflectance / (1.0 - self.openness)
            yarn_reflectance = min(1.0, yarn_reflectance)  # Cap at 1.0
        
        # Base IAC calculation using fabric properties
        # This is a simplified version of the ASHWAT model calculations
        base_iac = 1.0 - (1.0 - self.openness) * (1.0 - self.transmittance / (1.0 - self.openness)) * yarn_reflectance
        
        # Adjust for fullness
        # Fullness creates multiple reflections between adjacent fabric surfaces
        if self.fullness <= 0.0:
            fullness_factor = 1.0
        else:
            # Fullness effect increases with higher fullness values
            # More fullness means more fabric area and more reflections
            fullness_factor = 1.0 - 0.15 * (self.fullness / 2.0)
            
        # Apply fullness adjustment
        adjusted_iac = base_iac * fullness_factor
        
        # Ensure IAC is within valid range
        adjusted_iac = max(0.1, min(1.0, adjusted_iac))
        
        return adjusted_iac
    
    def calculate_shading_coefficient(self, glazing_shgc: float = 0.87) -> float:
        """
        Calculate shading coefficient for the drapery.
        
        The shading coefficient is the ratio of solar heat gain through
        the window with the drapery to that of standard clear glass.
        
        Args:
            glazing_shgc: Solar Heat Gain Coefficient of the glazing (default: 0.87 for clear glass)
            
        Returns:
            Shading coefficient (0-1)
        """
        # Calculate IAC
        iac = self.calculate_iac(glazing_shgc)
        
        # Calculate shading coefficient
        # SC = IAC * SHGC / 0.87
        shading_coefficient = iac * glazing_shgc / 0.87
        
        return shading_coefficient
    
    def calculate_u_value_adjustment(self, base_u_value: float) -> float:
        """
        Calculate U-value adjustment for the drapery.
        
        The drapery adds thermal resistance to the window assembly,
        reducing the overall U-value.
        
        Args:
            base_u_value: Base U-value of the window without drapery (W/m²K)
            
        Returns:
            Adjusted U-value (W/m²K)
        """
        if not self.enabled:
            return base_u_value  # No adjustment if drapery is not enabled
        
        # Calculate additional thermal resistance based on drapery properties
        # This is a simplified approach based on ASHRAE principles
        
        # Base resistance from drapery
        # More closed fabrics provide more resistance
        base_resistance = 0.05  # m²K/W, typical for medium-weight drapery
        
        # Adjust for openness (more closed = more resistance)
        openness_factor = 1.0 - self.openness
        
        # Adjust for fullness (more fullness = more resistance due to air gaps)
        fullness_factor = 1.0 + 0.25 * self.fullness
        
        # Calculate total additional resistance
        additional_resistance = base_resistance * openness_factor * fullness_factor
        
        # Convert base U-value to resistance
        base_resistance = 1.0 / base_u_value
        
        # Add drapery resistance
        total_resistance = base_resistance + additional_resistance
        
        # Convert back to U-value
        adjusted_u_value = 1.0 / total_resistance
        
        return adjusted_u_value
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert drapery object to dictionary."""
        return {
            "openness": self.openness,
            "reflectance": self.reflectance,
            "transmittance": self.transmittance,
            "fullness": self.fullness,
            "enabled": self.enabled,
            "absorptance": self.absorptance,
            "openness_class": self.openness_class.value,
            "color_class": self.color_class.value,
            "classification": self.get_classification()
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Drapery':
        """Create drapery object from dictionary."""
        return cls(
            openness=data.get("openness", 0.05),
            reflectance=data.get("reflectance", 0.5),
            transmittance=data.get("transmittance", 0.3),
            fullness=data.get("fullness", 1.0),
            enabled=data.get("enabled", True)
        )
    
    @classmethod
    def from_classification(cls, classification: str, fullness: float = 1.0) -> 'Drapery':
        """
        Create drapery object from ASHRAE classification.
        
        Args:
            classification: ASHRAE classification (ID, IM, IL, IID, IIM, IIL, IIID, IIIM, IIIL)
            fullness: Fullness factor (0-2)
            
        Returns:
            Drapery object
        """
        # Parse classification
        if len(classification) < 2:
            raise ValueError(f"Invalid classification: {classification}")
        
        # Handle single-character openness class (I) vs two-character (II, III)
        if classification.startswith("II") or classification.startswith("III"):
            if classification.startswith("III"):
                openness_class = "III"
                color_class = classification[3] if len(classification) > 3 else ""
            else:  # II
                openness_class = "II"
                color_class = classification[2] if len(classification) > 2 else ""
        else:  # I
            openness_class = "I"
            color_class = classification[1] if len(classification) > 1 else ""
        
        # Set default values
        openness = 0.05
        reflectance = 0.5
        transmittance = 0.3
        
        # Set openness based on class
        if openness_class == "I":
            openness = 0.3  # Open (>25%)
        elif openness_class == "II":
            openness = 0.15  # Semi-open (7-25%)
        elif openness_class == "III":
            openness = 0.03  # Closed (0-7%)
        
        # Set reflectance and transmittance based on color class
        if color_class == "D":
            reflectance = 0.2  # Dark (0-25%)
            transmittance = 0.05
        elif color_class == "M":
            reflectance = 0.4  # Medium (25-50%)
            transmittance = 0.15
        elif color_class == "L":
            reflectance = 0.7  # Light (>50%)
            transmittance = 0.2
        
        return cls(
            openness=openness,
            reflectance=reflectance,
            transmittance=transmittance,
            fullness=fullness
        )


# Predefined drapery types based on ASHRAE classifications
PREDEFINED_DRAPERIES = {
    "ID": Drapery.from_classification("ID"),
    "IM": Drapery.from_classification("IM"),
    "IL": Drapery.from_classification("IL"),
    "IID": Drapery.from_classification("IID"),
    "IIM": Drapery.from_classification("IIM"),
    "IIL": Drapery.from_classification("IIL"),
    "IIID": Drapery.from_classification("IIID"),
    "IIIM": Drapery.from_classification("IIIM"),
    "IIIL": Drapery.from_classification("IIIL")
}