Mapping Comfort: An Analysis Method for Understanding Diversity in the Thermal Environment

Mapping Comfort: An Analysis Method for
Understanding Diversity in the Thermal
Environment
by
Amanda Laurel Webb
B.A., Yale University (2006)
Submitted to the Department of Architecture
in partial fulfillment of the requirements for the degree of
Master of Science in Architecture Studies
at the
MASSACHUSETTS INSTITUTE OF TECHNOLOGY
June 2012
c Massachusetts Institute of Technology 2012. All rights reserved.
Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Department of Architecture
May 24, 2012
Certified by . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
John Fernandez, MArch
Associate Professor of Architecture and Building Technology and
Engineering Systems
Thesis Supervisor
Accepted by . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Takehiko Nagakura
Associate Professor of Design and Computation, Chair of the
Department Committee on Graduate Students
2
Thesis Readers
John Fernandez
Associate Professor of Architecture and Building Technology and Engineering Systems
Christoph Reinhart
Associate Professor of Architecture and Building Technology
Evelyn Wang
Associate Professor of Mechanical Engineering
3
4
Mapping Comfort: An Analysis Method for Understanding
Diversity in the Thermal Environment
by
Amanda Laurel Webb
Submitted to the Department of Architecture
on May 24, 2012, in partial fulfillment of the
requirements for the degree of
Master of Science in Architecture Studies
Abstract
Our thermal experience is never neutral. Whether standing near a cold window in the
winter, or in the shade on a sunny day, we constantly experience a rich set of thermal
stimuli. Yet, many of the tools used in professional practice to analyze and design
thermal environments in buildings do not account for the richness of our thermal
experience. This disconnect between our analysis tools and our experience results in
buildings that use more energy than they should, and that leave occupants dissatisfied
with their thermal environment.
This thesis seeks to bridge the gap between our thermal experience and our building thermal analysis tools. A unique methodology has been developed that produces
mapping of thermal comfort parameters in all three spatial dimensions, as well as over
time. Both heat balance and adaptive comfort indices have been incorporated into
the methodology. An accompanying software program, called cMap, has been developed to illustrate the ways that this methodology can be used with existing energy
analysis software and to demonstrate how it can fit into existing analysis workflows
in professional practice.
Thesis Supervisor: John Fernandez, MArch
Title: Associate Professor of Architecture and Building Technology and Engineering
Systems
5
6
Acknowledgments
I would like to express my sincere gratitude to my thesis readers, Professor John Fernandez,
Professor Christoph Reinhart, and Professor Evelyn Wang. They have each provided me
with continued guidance and encouragement, both for this thesis work and for my overall
graduate career.
In addition to my readers, I would like to thank Kevin Settlemyre, who has acted as
both a mentor and a friend. He has generously lent his knowledge of the building simulation
field to my project, and provided me with excellent suggestions during the development of
this thesis.
I would also like to thank MITs Department of Architecture, especially the Building
Technology Program, for providing me with invaluable financial and administrative support.
My close friends and family have had immense faith in me and my intellectual project
during the past two years. To Vishal, thank you for your ever-present sense of humor. To
Clare, thank you for your love and patience.
The inspiration for this thesis came largely from my time in professional practice. Many
thanks to my former colleagues at Atelier Ten and to our clients, who always brought new
and interesting challenges across my desk.
Finally, I would like to thank the MIT Womens Ice Hockey Club and our supporters.
Your camaraderie has meant the world to me these past two years. Go Tech!
7
8
Contents
Nomenclature
15
1 Introduction
17
2 Background
25
2.1
Thermal Comfort Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
2.2
Existing Thermal Comfort Analysis Methods and Software Tools . . . . . .
31
2.3
Thermal Comfort Analysis in Practice . . . . . . . . . . . . . . . . . . . . .
37
3 Methodology
41
3.1
Mean Radiant Temperature and Comfort . . . . . . . . . . . . . . . . . . .
41
3.2
Angle Factor Calculation
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
3.3
MRT Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4 Software
45
4.1
cMap Workflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
4.2
cMap Outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
4.3
cMap Capabilities and Limitations . . . . . . . . . . . . . . . . . . . . . . .
48
5 Conclusions
61
5.1
Thesis Achievements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.2
Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
5.3
Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
Bibliography
67
A cMap Source Code
71
9
10
List of Figures
1-1 Bayt al-Suhaymi, Cairo . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
1-2 Bayt al-Sinnari, Cairo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
1-3 Cairo from the Citadel, 1860 . . . . . . . . . . . . . . . . . . . . . . . . . .
21
1-4 Analysis feedback cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2-1 Comfort concept hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
2-2 Fanger comfort model vs. adaptive comfort model . . . . . . . . . . . . . .
30
2-3 Control volume vs. discretized analysis methods . . . . . . . . . . . . . . .
32
2-4 ASHRAE Thermal Comfort Tool . . . . . . . . . . . . . . . . . . . . . . . .
34
2-5 UC Berkeley Advanced Human Thermal Comfort Model . . . . . . . . . . .
35
2-6 CFD results from San Francisco Federal Building . . . . . . . . . . . . . . .
37
2-7 ASHRAE comfort zone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
3-1 Angle factors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4-1 cMap interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50
4-2 cMap workflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
4-3 cMap scatterplot, winter example . . . . . . . . . . . . . . . . . . . . . . . .
52
4-4 cMap contour heatmap X-Y slice, winter example . . . . . . . . . . . . . . .
53
4-5 cMap contour heatmap Y-Z slice, winter example . . . . . . . . . . . . . . .
54
4-6 cMap contour heatmap X-Z slice, winter example . . . . . . . . . . . . . . .
55
4-7 cMap scatterplot, summer example range . . . . . . . . . . . . . . . . . . .
56
4-8 cMap scatterplot, summer example peak . . . . . . . . . . . . . . . . . . . .
57
4-9 cMap contour heatmap X-Y slice, summer example . . . . . . . . . . . . . .
58
4-10 cMap contour heatmap Y-Z slice, summer example . . . . . . . . . . . . . .
59
4-11 cMap contour heatmap X-Z slice, summer example . . . . . . . . . . . . . .
60
11
12
List of Tables
2.1
ASHRAE 7-point scale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
2.2
Existing thermal comfort software tools . . . . . . . . . . . . . . . . . . . .
33
3.1
Control volume variables vs. discretized variables . . . . . . . . . . . . . . .
44
4.1
cMap and existing thermal comfort software tools . . . . . . . . . . . . . . .
47
13
14
Nomenclature
fcl
clothing area surface factor
FP −n angle factor between person P and surface n, ◦ C
hc
convective heat transfer coefficient, W/m2 · K
Icl
clothing insulation, m2 · K/W
M
metabolic rate, W/m2
pa
water vapor partial pressure, Pa
T−n
average outdoor temperature n days befpre the day in question, ◦ C
ta
air dry bulb temperature, ◦ C
tcl
clothing surface temperature, ◦ C
Tmrt
mean radiant temperature, ◦ C
Tm
mean monthly outdoor temperature, ◦ C
Tn
temperature of surface n, ◦ C
Trm3
3-day running mean outdoor temperature, ◦ C
Trm7
7-day running mean outdoor temperature, ◦ C
tr
mean radiant temperature, ◦ C
var
relative air velocity, m/s
W
effective mechanical power, W/m2
15
16
Chapter 1
Introduction
This thesis begins with the argument that Bayt al-Suhaymi, shown in Figure 1-1, is one of
the most compelling buildings ever built. And that it is compelling primarily because it
embodies the concept of thermal diversity. The building in Figure 1-1 is merely a proxy;
we can extrapolate this argument to state that buildings that thermally diverse buildings
are extremely compelling.
What is thermal diversity? Thermal diversity is simply terminology to package the
intuitive truth that our thermal experience is not neutral. As Lisa Heschong writes in her
short but seminal book Thermal Delight in Architecture:
Thermal information is never neutral; it always reflects what is directly happening to the body. This is because the thermal nerve endings are heat flow
sensors, not temperature sensors. They cant tell directly what the temperature of something is; rather, they monitor how quickly our bodies are losing or
gaining heat. (24).
The thermal environment, to us, is a world of opposites; our bodies are constantly
evaluating whether the objects around us - the coffee cup we are holding, the window we
are seated next to, the surrounding air - are hotter or colder than we are.
What does this intuitive truth about our thermal experience mean for buildings? In the
first place, it means that there are a variety of elements in a building that control the rate
of heat gain and loss from our bodies. Such elements include the surfaces that make up a
building, the air inside of a building, and the objects within a building. In contrast, the
17
prevailing way of designing buildings for the past half-century or more has focused only on
the volume of air in a building, rather than utilizing all of the components mentioned above.
Typical space conditioning systems duct hot or cold air into a space to meet a particular
setpoint temperature; the volume of air is assumed to be the same temperature throughout
(the well-mixed assumption) and is intended to create a thermally neutral sensation the
occupant is neither hot nor cold, is neither gaining nor losing heat. Buildings that embody
thermal diversity acknowledge and exploit the fact that our thermal experience is diverse.
Rather than just supplying hot or cold air, thermally diverse buildings also use surfaces and
other objects within a building to help create a sensation of thermal comfort.
Figure 1-1: Bayt al-Suhaymi, in Cairo, provides an excellent example of a
building that embodies thermal diversity. Photo by Hans Munk Hansen, from
http://www.davidmus.dk/assets/972/Bayt-al-Suhaymi-Cairo-Egypten.jpg
Secondly, the goal in a thermally diverse building is not to create a perfectly uniform
volume of air, nor to create a neutral sensation for building occupants. Instead, comfort is
typically created by using contrast, providing some air movement on a hot day, for example,
18
or using a large mass wall to dampen peak temperatures. These buildings seek to ’take the
edge off’, facilitating heat loss or heat gain from the body just enough to provide relief.
Despite the prevailing concept of comfort as a neutral sensation, there is clear evidence
that people do not necessarily want to feel neutral. Humphreys and Hancock (27) have
shown through field studies that a persons desired thermal sensation is something other
than neutral most of the time. Similarly, the adaptive comfort model (discussed in the
Background section below), does not equate comfort with neutrality, but posits a comfort
temperature correlated to the outdoor temperature.
If a thermally diverse building uses a variety of strategies to create a non-uniform
thermal environment, how does the building shown in Figure 1-1 do this? A section through
Bayt al-Suhaymi was not available, but a section through a similar building, Bayt al-Sinnari,
is shown in Figure 1-2 below.
The section reveals several key strategies that the building uses to create a thermal
environment that is both diverse and comfortable. First, thick, massive walls have a high
capacity to store heat, helping to reduce peak surface and air temperatures. Second, the
building reduces solar heat gain through small window openings, shaded by a dense wooden
mashrabiyya screen. Third, the building is organized around a courtyard that provides selfshading, allowing cool night air to sink down into the courtyard and remain there until later
in the day. Fourth, a windscoop or malqaf reaches up above surrounding buildings to direct
airflow down into the building. Fifth, a fountain provides localized evaporative cooling
in occupied spaces. None of these strategies attempts to create a uniformly conditioned
volume of air. For more on the environmental strategies used in Mamluk and Ottoman era
townhouses in Egypt, see Fathy (16) and Webb (49).
This thesis was originally conceived as a comprehensive study of the thermal environments in vernacular buildings. I originally became interested in thermal diversity through
my undergraduate thesis, which focused qualitatively on the thermal environment in Bayt
al-Suhaymi, and on the historical evolution and urban impacts of windscoops in Cairo, as
shown in Figure 1-3 below. In the present work, I wanted to gain a quantitative understanding of the thermal environments created in buildings like Bayt al-Suhaymi. As architects,
we often look to vernacular buildings for examples of how passive design can achieve thermal comfort. But there is little quantitative evidence illustrating the thermal conditions
in these buildings and comparing to them to our current comfort standards. If we look to
19
Figure 1-2: Section of Bayt al-Sinnari, a similar building in Cairo, illustrating the
features of the building that embody thermal diversity. Diagram by the author.
Section of Bayt al-Sinnari from Maury et. al. Planche LXXVII (31)
20
Figure 1-3: Photograph of Cairo from atop the Citadel, 1860. Photo by Frith (20).
A close inspection shows the abundance of windscoops across the roofs of the city
21
these buildings as precedent, we should know whether the conditions they create accord
with our current comfort expectations.
I very quickly became sidetracked by the methods that allow us to quantify thermal
diversity, and how those methods are used in professional practice. It turns out that analyzing thermally diverse spaces can be a complex process, and many of the analysis tools
used in professional practice have limited capacity to perform such analysis. As a result,
this thesis work is aimed at developing a methodology for analyzing thermal diversity that
is viable for use in professional practice.
Figure 1-4: Diagram illustrating the key role that analysis tools play in the design
process. What our analysis tools lack, our buildings will also lack. Diagram by the
author.
Developing a methodology for use in professional practice is important because of the
argument that began this thesis that thermally diverse buildings are more compelling.
Compelling buildings simultaneously provide for us, educate us and endear themselves to
us. They are the kind of buildings in which we recognize a core set of values, and that
we want to preserve for future generations. In short, they are the kind of buildings that I
22
believe we should be building. Analysis methodology is critical to this process, as shown in
Figure 1-4 below. Thermal analysis is an essential part of the building design process; if our
design tools do not have the capability to analyze thermally diverse spaces, we simply wont
build them. An important part of this thesis work is integrating the capability to analyze
thermally diverse spaces into professional workflows.
Building thermally diverse spaces could significantly impact our built environment in
two ways. First, thermally diverse spaces often use less energy. Rather than conditioning an
entire volume of air, diverse spaces typically provide heating and cooling locally. There are
clear energy impacts associated with uniformly conditioning a volume of air. Our concept
of comfort including our standards, our design goals, and our analysis methods all need
to be revised to reflect this. A 2008 issue of Building Research Information dedicated to
the topic of comfort in a low carbon society put it thus:
””The systems of knowledge, and of design and construction that spawned comfort science and air-conditioned buildings, required cheap energy, a planetary
atmosphere that could be disregarded, an ascendant engineering elite, technological regulation, powerful corporations, and cooperative governments. Those
times are going, if not already gone. (44).
Second, thermally diverse spaces carry cultural significance that should not be lost.
Consider the affection that we feel for sitting next to a roaring fire on a cold winter night,
or enjoying the shade of a picnic pavilion on a hot summer day. Not only do these spaces
conjure certain emotions, they also have the potential to create a distinctive urban form.
Consider the unique skyline created by the forest of malqafs atop the roofs of Cairo, shown
in Figure 1-3. As Heschong writes:
””The thermal environment also has the potential for such sensuality, cultural
roles, and symbolism that need not, indeed should not, be designed out of
existence in the name of a thermally neutral world.”” (24).
23
24
Chapter 2
Background
Over the past century, the issue of how and when we feel thermally comfortable has been
researched, debated and incorporated into our building standards. This section provides
context for my work by briefly answering the following questions:
• How do we, as architects and engineers, conceptualize comfort?
• How can we analyze thermal comfort? What methods and tools are available?
• How do we analyze thermal comfort in practice?
A short discussion on thermal comfort theory, existing thermal comfort analysis tools,
and the use of these tools in professional practice follows.
2.1
Thermal Comfort Theory
The study of human thermal comfort is inherently multidisciplinary. Understanding human
comfort requires an exploration of both physical and psychosocial factors. These factors
include human physiology and the way that it interacts with the built environment, the
physics of the built environment, and cultural and behavioral thermal preferences.
We can quantify human thermal sensation at several scales, which, taken together form
our current concept of human thermal comfort. At the scale of a single human body, we
can evaluate the rate of heat transfer to and from the body. The rate of heat transfer
is influenced by the characteristics of the surrounding environment, which can be broken
25
down into variables like the room air temperature, or relative humidity. These variables
can be combined into a single, more convenient comfort index (operative temperature, for
instance, is a combination of room air temperature and mean radiant temperature.) Our
comfort standards then set acceptable ranges for these indices, establishing the bounds for
what is comfortable and what is not.
Figure 2-1: Diagram illustrating the relationship between comfort standards, comfort
indices, and heat transfer processes. Taken together, these form our hierarchical
concept of thermal comfort. Diagram by the author.
Precisely where these comfort boundaries lie and which comfort index should be used
has been the topic of a longstanding debate that still continues to evolve.
Many of the earliest thermal comfort indices were geared towards understanding the
effects of extreme thermal conditions (especially extreme heat) on the human body. The
early heating, ventilation and air conditioning (HVAC) industry evolved primarily in response to manufacturing needs, and early comfort studies were typically concerned with
26
setting safety limits for workers exposed to the often severe thermal conditions in factories.
The dry bulb temperature and humidity were the main environmental variables explored
in these studies. For more on the development of comfort indices and standards in the first
half of the 20th century, see Fanger (15) and Cooper (11).
Fanger’s pioneering work in the late 1960s and early 1970s introduced a more thorough
comfort index, called Predicted Mean Vote, or PMV. Fanger first derived a comfort equation
based on a static heat balance for the human body. This equation accounted for six variables
that Fanger asserted affected thermal comfort: dry bulb temperature, relative humidity,
mean radiant temperature, airspeed, clothing level and activity level. Fanger then developed
the PMV index by combining his comfort equation with experimental data. He seated his
subjects in a climate chamber, where he changed each of the six comfort variables and
recorded peoples votes on the seven point psycho-physical scale developed by the American
Society of Heating, Refrigeration and Air Conditioning Engineers (ASHRAE).
Table 2.1: ASHRAE 7-point psycho-physical scale
cold
cool
-3
-2
slightly
cool
-1
neutral
0
slightly
warm
1
warm
hot
2
3
The resulting PMV index ranges in value from -3 to +3, and is calculated from the
following equation (15) (3):
27
P M V = [0.303 · (−0.036 · M ) + 0.028]·
{z
}
|
thermal sensation coefficient
{(M − W )
| {z }
internal heat production
−3.05 ∗ 10−3 · [5733 − 6.99 · (M − W ) − pa ]
{z
}
|
heat loss through skin
−0.42 · [(M − W ) − 58.15]
|
{z
}
heat loss by sweating
−1.7 · 10−5 · M · (5867 − pa )
|
{z
}
(2.1)
latent respiration heat loss
−0.0014 · M · (34 − ta )
{z
}
|
dry respiration heat loss
−3.96 · 10−8 · fcl · [(tcl + 273)4 ) + (tr + 273)4 )]
|
{z
}
heat loss by radiation
+fcl · hc · (tcl − ta )}
|
{z
}
term description here
The variables tcl , hc and fcl are determined from the following equations; tcl and hc are
found by iteration.
tcl = {35.7 − 0.028 · (M − W ) − tcl · 3.96 · 10−8 · fcl ·
(2.2)
[(tcl + 273)4 ) + (tr + 273)4 )] + fcl · hc · (tcl − ta )}


2.38 · tcl − ta 0.25 for 2.38 · tcl − ta 0.25 > 12.1 · √var
hc =

12.1 · √var for 2.38 · tcl − ta 0.25 < 12.1 · √var
fcl =


1.00 + 1.290 · Icl for Icl ≤ 0.078
(2.3)
(2.4)

1.05 + 0.645 · Icl for Icl > 0.078
It is important to note that the PMV is a mean, intended to represent an average person
28
in a space. Therefore, a PMV of -0.3 means that an average person would feel somewhere
between neutral and slightly cool under the specified conditions. Since not all people are
alike, Fanger also developed the Predicted Percentage of Dissatisifed, or PPD index for
provide a clearer measure of discomfort. The PPD index is related to the PMV index as
follows:
P P D = 100 − 95 · exp(−0.03353 · P M V 4 − 0.2179 · P M V 2 )
(2.5)
The PPD index suggests that there will always be some number of dissatisfied individuals. At a PMV of zero, i.e., when the average individual in the space is neutral (representing
perfectly comfortable), 5% of individuals in the space will still be dissatisfied with the thermal environment.
The current comfort standard in the United States, ASHRAE Standard 55: Thermal
Environmental Conditions for Human Occupancy, sets limits of PPD < 10% and -0.5 <
PMV < +0.5 for acceptable thermal environments. (2)
In the 1990s, the adaptive comfort model was developed in response to Fanger’s work. In
contrast to Fanger’s highly controlled climate chamber tests, de Dear and Brager surveyed
occupants in actual buildings about their comfort preferences and measured the environmental conditions at the time of survey. They then developed the adaptive comfort model
based on a linear regression of their results. According to their model:
Tcomf
= 17.8◦ C + 0.31 × Tm
(2.6)
where Tm is the monthly average of the daily average outdoor dry bulb temperatures.
The bounds for 80 percent acceptability and 90 percent acceptability are at +/− 2.5 ◦ C and
+/− 3.5 ◦ C, respectively. deDear and Brager’s work was conducted as part of ASHRAE
RP-884 and the full scope of their work can be found in deDear (14). Additional summaries
of the adaptive model can be found in Brager (8), Humphreys (28), Nicol (35), and de Dear
(12)(13).
Whereas Fanger’s PMV index uses six variables to predict comfort, the adaptive model
29
suggests that comfort is correlated with only two variables
the operative temperature
and the mean outdoor temperature. In addition to these three measurable variables, the
adaptive standard presupposes that there are psychological, cultural, and personal factors
that contribute to an individual’s perception of thermal comfort.
Figure 2-2: Conceptual illustration of the difference between the Fanger comfort
model (at left below) and the adaptive comfort model (at right below). Fanger’s
model is based on a heat balance method; the adaptive comfort model assumes a set
of psychosocial factors underlie comfort preferences. Diagram by the author.
While ASHRAE Standard 55 includes guidance on both the Fanger model and the
adaptive model, the standard states that the adaptive model may only be used in spaces
that have operable windows and that do not utilize a mechanical cooling system.
In the past decade, variations to the adaptive comfort model have emerged in different
countries. These models differ in two main ways from the original adaptive model that
has been incorporated into ASHRAE Standard 55. First, they use different statistical
sample sets. Whereas the original adaptive model used a global database of buildings,
the adaptive model that has been incorporated into European standard EN 15251 used
a databased on European buildings only. Second, the models use different mean outdoor
temperatures. Whereas the original adaptive model uses the monthly average of the daily
average outdoor dry bulb temperatures, the models incorporated into EN 15251 and Dutch
standard NPR-CR-1752 uses an exponentially weighted running mean of previous daily
outdoor temperatures.
30
The adaptive model that has been incorporated into the European Standard EN-15251
states that:
Tcomf = 18.8◦ C + 0.33 × Trm7
(2.7)
where Trm7 is calcuated as:
Trm7 = T−1 + 0.8T−2 + 0.6T−3 + 0.5T−4 + 0.4T−5 + 0.3T−6 + 0.2T−7 /3.8
(2.8)
The adaptive model that has been incorporated into the Dutch Standard NPR-CR-7251
states that:
Tcomf
= 17.8◦ C + 0.31 × Trm3
(2.9)
where Trm3 is calcuated as:
Trm3 = T0 + 0.8T−2 + 0.4T−3 + 0.2T−4 /2.4
(2.10)
Details on the European and Dutch variations to the original adaptive comfort model
are provided in Borgeson (6), McCartney (32), Nicol (36), and van der Linden (47).
2.2
Existing Thermal Comfort Analysis Methods
and Software Tools
There are a number of existing software programs that provide explicit support for evaluating thermal comfort in a space. These tools vary widely in their scope, capabilities and
31
limitations. They can be usefully categorized based on the following:
• Analysis method. Does the tool use control volume analysis or discretized analysis?
• Scale of focus. Does the tool provide analysis of a human body, or of a point in space?
• Spatial output. Does the tool provide spatial mapping of comfort analysis results
over an entire space, or does it only provide the comfort conditions at a single point?
• Temporal output. Does the tool provide comfort analysis results over a range of time,
or does it only provide the comfort conditions at a single point in time?
Perhaps the most important distinction between these tools is whether or not they use
control volume or discretized analysis methods. Control volume analysis draws a boundary
around a volume and solves for the inputs and the outputs. In contrast, discretized methods
create a grid of points within the object of interest, and solve for the values at each grid
point. Because these two methods set up the analysis problem in very different ways, each
method has a very different set of possible outputs.
Figure 2-3: Illustration depicting the conceptual differences between a control volume
analysis approach (at left below) and discretized analysis methods (at right below).
Diagram by the author.
Table 2.2 summarizes these primary characteristics for several existing comfort analysis
tools.
32
Table 2.2: Characteristics of existing thermal comfort software tools
Tool Name
EnergyPlus
ASHRAE Comfort Tool
UC Berkeley AHTCM
Arup ROOM
Method
control colume
control volume
discretized
discretized
Scale
room
space
body
room
Spatial
point
point
space
space
Temporal
range
point
point
point
EnergyPlus (of Energy), is a whole building energy analysis program used by architects,
engineers, and researchers to model building energy and water use at each hour of a typical
year. This software is maintained by the U.S. Department of Energy and is free for download. The software provides thermal comfort outputs as part of its People object. The user
can specify the mean radiant temperature calculation in three different ways, depending on
how much information the user is willing to provide: Zone Averaged, Surface Weighted, and
Angle Factor. If the user chooses the Angle Factor method, the user must calculate and
input the angle factors EnergyPlus does not perform this calculation. EnergyPlus can provide analysis output based on several different comfort models, including both the Fanger
model and the ASHRAE Standard 55 adaptive model. More information on thermal comfort analysis using EnergyPlus can be found in the programs Input-Output Reference Guide
and in the program’s Engineering Reference, available as part of the program download or
in the documents section of the program website.
ASHRAE developed its own Thermal Comfort Tool software in the mid 1990s, as part
of research project in RP-781 (18) (19). This tool is designed to help HVAC engineers
determine whether their design is in compliance with ASHRAE Standard 55. The tool is
available for purchase from ASHRAE. In contrast to EnergyPlus, the ASHRAE Thermal
Comfort Tool does not provide information about building energy use or thermal conditions;
the user must supply the thermal conditions of interest and the tool calculates whether or
not PMV or adaptive comfort criteria are met. The tool is only able to provide information
about comfort conditions at one set of criteria, that is, at a single point in space and at
a single point in time. Version 2.0 of the ASHRAE Thermal Comfort Tool has recently
been released. This updated version includes a more detailed mean radiant temperature
calculator than the previous version, however, the user still must supply the angle factors
to the software. (ASHRAE)
33
Figure 2-4: Screenshot of the ASHRAE Thermal Comfort Tool user interface, version
1.0
34
UC Berkeley’s Advanced Human Thermal Comfort Model (AHTCM) is a detailed model
of the human body and its interactions with the surrounding thermal environment. The
model can predict comfort and thermal perception for the human body as a whole, as well
as for specific body parts. Like the ASHRAE Thermal Comfort Tool, the AHTCM cannot
predict building energy use, and only provides results based on a specific point in time.
In contrast to the ASHRAE Thermal Comfort Tool, the AHTCM provides a rendering of
a person in a space that includes spatial mapping of temperatures and thermal comfort
parameters. The AHTCM is maintained by the Center for the Built Environment at UC
Berkeley, and is not available for public use or purchase. See Huizenga (25). A number
of similar computational thermal models of the human body have been discussed by Yang
(51), van Treek (48), and Rees (39).
Figure 2-5: Screenshot of the UC Berkeley Advanced Human Thermal Comfort Model
user interface. Image from Huizenga (26)
The software ROOM is proprietary tool that has been developed by the engineering
firm Arup over the past 30 years. ROOM provides all-in-one energy analysis, radiation
and shading analysis, and thermal comfort analysis. The thermal comfort module produces
2-D spatial mapping of thermal comfort conditions at a set vertical distance from the floor.
35
ROOM can produce these results for an average day for each month of the year, but it is
not able to produce results for every hour of the year. For information on ROOM, please
see White (50). The author also received a demonstration of the ROOM software from
Jauni Novak, a Graduate Mechanical Engineer in the Arup Los Angeles office (personal
communication, April 11, 2012).
While the focus of this thesis in on indoor thermal comfort analysis, it is worth mentioning the maturing body of literature and range of thermal comfort analysis tools for outdoor
thermal comfort analysis. Both the RayMan (30) and ENVI-MET (Bruse) programs apply
similar comfort analysis methods to outdoor thermal comfort problems.
While it is an analysis method and not a software tool, computational fluid dynamics
(CFD) is increasingly being used to provide detailed analysis of the thermal environment
in buildings. CFD utilizes discretized analysis methods to solve the Navier-Stokes equations for fluid flow. As a result, CFD can map temperature and velocity fields throughout
a given air volume, as shown in Figure 2-6 below. A variety of studies in recent years
have utilized CFD methods to provide spatial mapping of thermal comfort conditions in a
space, and these are discussed in more detail in the following section of this paper. While
CFD provides information about air temperature and velocity fields, it does not determine
other comfort variables, e.g., mean radiant temperature, clothing level, therefore does not
explicitly provide information about thermal comfort conditions. While the use of CFD
is increasing in professional practice, it is a time-intensive and expertise-intensive process,
and is currently much less common than whole building energy modeling. For more on the
use of CFD for building analysis applications, see Srebric (45)
In addition to the analysis tools and methods discussed above, two research projects
serve as a useful predecent for this thesis. Herkel et. al. (42) developed an interactive
tool for the visualization of thermal comfort conditions in a space. This tool produces 2-D
slices of comfort conditions within a perspective view of a space. Gan (21) (22) developed
a methodology for the full evaluation of thermal comfort in a space, and produced a series
of thermal comfort spatial maps using this methodology.
36
Figure 2-6: CFD results from the design phase analysis of the San Francisco Federal
Building depicting the air velocity field. Image from Haves (23)
2.3
Thermal Comfort Analysis in Practice
Despite the variety of thermal comfort analysis tools and methods discussed above, there
are a number of major barriers to their use in professional practice:
Too time-intensive Tools might be too time-intensive because their computational methods take a long time, e.g., CFD, because the inputs to the tool are non-trival to
determine, e.g., angle factors in EnergyPlus, or because they are an entirely separate
tool and do not fit within a company’s existing analysis workflow. In professional
practice, time is extremely valuable and analysis that cannot be performed quickly,
or sold to a client as important will simply not happen.
Too experience-intensive Tools or methods that require advanced inputs or user knowledge, e.g, CFD, require expert users to ensure quality results. Design firms may have
more difficulty finding prospective employees with such training, and may not want
to bear the cost of training existing employees in these skills.
Unavailable Only one of the indoor thermal comfort tools listed above is publicly available
37
at no cost EnergyPlus. All of the other tools listed are not publicly available, or are
available for a fee.
As a result, these tools are not commonly used in professional practice. Often, comfort is simply approximated by evaluating zone air dry bulb temperature (or operative
temperature, if available) and relative humidity using a whole building energy model, and
comparing the calculated values to the boundaries of the comfort zone shown in Figure
5.2.1.1 of ASHRAE Standard 55 (2), and reproduced below.
Figure 2-7: Diagram plotting the humidity ratio (y-axis), the operative temperature (x-axis) and delineating the ASHRAE Standard 55 Comfort Zone. Image from
ASHRAE Standard 55 (2)
The low occurrence of thermal comfort analysis in practice suggests the need for a
cohesive comfort analysis process. Ideally this process would happen at multiple stages
during the building design process, and could focus on several different scales
building, a single window, a particular part of the human body
38
the whole
depending on the project
needs.
While there are no existing professional resources, e.g, guides or standards, suggesting
such a process, several research studies outline a comfort analysis process as a consequence
of their work.
Negrao (34) evaluated thermal comfort for a four-zone sample building in Brazil. He
first used a nodal network to evaluate thermal conditions for each zone as a whole for every
hour of the year, and then coupled the nodal network results with CFD analysis. The CFD
analysis was emplyed in one of the zones for two points in time a heating design condition
and a cooling design condition. A 7-node model of a human shape was used to evaluate
thermal comfort. Spatial mapping of PMV values were produced at one height in the x-y
direction.
van Treeck (48) performed an initial simulation at the coarse level, running a whole
building energy simulation for the whole year to identify periods where comfort temperatures
are not satisfied in a building zone as a whole. The results from the critical periods of
potential discomfort are then imported into the ”virtual climate chamber” for local analysis
using a computational thermal manikin.
Published analysis from the design phases for the San Francisco Federal Building presents
perhaps the best example of a comfort analysis process used in practice. Several portions
of the building would not have mechanical cooling systems and the team needed to demonstrate that the natural ventilation scheme would produce comfortable indoor conditions.
The analysis process first used EnergyPlus to evaluate zonal conditions using a nodal network model. CFD was then used to provide a more detailed analysis, and to help refine
opening sizes. For a summary of this analysis work, see Haves (23). For a detailed case
study on the design process, see Meguro (33)
What is common to all of these examples is the need for analysis at a zonal (or ”coarse”)
level first, to understand the global comfort conditions for the building. This type of analysis
can tell us, over the course of a year, what the thermal conditions are for each zone as a
whole. These example all also have a second analysis at a finer level. This may be finer
analysis using a human body model, or using a spatially resolved model of a room. While
the coarse analysis tells us when a space might be uncomfortable, the finer analysis tells us
more precisely where and why.
39
40
Chapter 3
Methodology
This thesis seeks to remedy some of the issues with existing thermal comfort analysis tools
and to remove the barriers to the use of thermal comfort analysis in professional practice.
The goal of this thesis is to develop an analysis methodology that is able to:
• Map thermal comfort parameters over space
• Plot thermal comfort metrics over time, both at a specific hour of the year, and
averaged over a specified period
• Fit easily into existing energy analysis workflows in professional practice, i.e., is a
computationally lightweight method
3.1
Mean Radiant Temperature and Comfort
All of the comfort indices discussed in section 2.1 have two variables in common: dry bulb
temperature and mean radiant temperature, which accounts for radiative exchange between
a person and the surroundings. Using one of these two variables, then, as a basis for this
methodology has the added benefit of being able to apply it to assess comfort for a range
of different comfort standards.
A key objective of this methodology is to be able to map comfort parameters over space.
Mapping the dry bulb temperature field in a space is a relatively complex process, generally
requiring CFD analysis. Mean radiant temperature, on the other hand, is dependent on
41
location by its very definition. While it is not a trivial process to plot mean radiant temperature as a function of space, it is less computationally intensive than CFD. Therefore,
mean radiant temperature has been selected as the basis for this methodology.
It is worth noting the general importance of mean radiant temperature in the literature.
Fanger (15) devoted a significant portion of his work to the calculation of mean radiant
temperature. Powitz (38) suggests that radiant temperature asymmetries are a chief cause
of comfort complaints in actual buildings.
Mean radiant temperature is calculated using the following equation:
Tmrt = T1 FP −1 + T2 FP −2 + ..... + Tn FP −n
(3.1)
These angle factors are highly dependent on the location of a person in a space. Since
these angle factors must sum to unity, they are effectively how much of a each surface a
body ”sees”. If a body is standing closer to a surface or if the surface is large, the body will
”see” more of that surface than other surfaces in the space. Vice versa for surfaces that are
smaller or further away. This concept is illustrated in the figure below.
Figure 3-1: Diagram illustrating angle factors and their dependence on location. In
both images below, surface 1 is the left shaded surface and surface 2 is the right
shaded surface. Diagram by the author.
42
3.2
Angle Factor Calculation
While the calculation of mean radiant temperature is relatively straightforward, calculating
the angle factor component is much more complex. The angle factor is a function of the
surface area and posture of the human body, and the location of a human body within a
space. A full derivation of the angle factor is given in Fanger (15). Using the results from a
set of experiments, Fanger produced a set of nomograms to allow for the quick calculation
of angle factor.
Unfortunately, nomograms cannot be used for computational methods, such as the one
proposed here. Cannistraro (10) developed an algorithm for the determination of angle
factors based on curve-fitting Fanger’s nomograms. This algorithm has been used in subsequent research (5) and has been used for the calculation of angle factors in this work.
3.3
MRT Mapping
The proposed methodology can be described in 4 steps:
Step 1 Discretize the space with a 3-D set of gridpoints
Step 2 Calculate the view factors at each gridpoint
Step 3 From the view factors, calculate the mean radiant temperature at each gridpoint
Step 4 Combine the control volume values for the other comfort parameters with the mean
radiant temperature at each point to calculate the comfort indices at each point.
It is important to clarify what is being calculated as a function of space. While mean
radiant temperature is being plotted as a function of space, all of the other comfort parameters are being pulled from the control volume method. But, because mean radiant
temperature is being plotted as a function of space, the thermal comfort indices can be
plotted as a function of space. See Table 3-1 below for a summary. This is a kind of hybrid control volume/discretized method, where some of the values are provided via control
volume analysis and mean radiant temperature and comfort indices are discretized.
While this method doesnt plot full temperature and velocity fields, like CFD does,
this method is faster and much less computationally intensive, and provides a first order
approximation of how comfort changes over the space.
43
Table 3.1: Listing of which analysis results are determined using control volume
methods and which are discretized
Discretized
mean radiant temperature
all comfort indices
Control Volume
dry bulb temperature
relative humidity
airpeed
clothing level
activity level
It should be noted that the use of mean radiant temperature plotting to produce spatial
comfort plotting in three dimensions is a logical extension of previous thermal comfort work.
Cannistraro suggested that one of the greatest potential results of their algorithms was the
ability to be able to determine mean radiant temperature at every point in a room and plot
’iso-comfort’ lines (10). Fanger himself produced thermal comfort plots as a part of the full
assessment of the thermal environment. See Figure 28 in Fanger (15).
44
Chapter 4
Software
A software program called Thermal Comfort Spatial Map (cMap) has been developed to
demonstrate one possible way to implement the methodology discussed in the previous
section. In addition, the cMap outputs are designed to suggest a clear thermal comfort
analysis workflow to be used in professional practice.
Please note that all of the figures discussed in this section are located at the end of the
chapter.
The cMap software includes a backend script that performs the thermal comfort calculations, and a GUI that produces a series of plots. The GUI is interactive, allowing the
user to quickly scroll through the results for different periods of the year. A screenshot of
the cMap interface is shown in Figure 4-1 below, highlighting the input, navigation, and
output areas.
The software has been written in Python. Numpy and Scipy were used to perform most
of the comfort calculations. Matplotlib was used to produce the plots. wxPython was used
to create the GUI. The full source code for the software is included in Appendix A.
4.1
cMap Workflow
cMap has been built to work with the EnergyPlus whole building energy simulation software. cMap does not create or run the EnergyPlus model this model must exist already,
presumably having been built previously as part of a projects energy modeling needs. Running cMap involves the following three basic steps from the user:
45
Step 1 The user inputs the location of the EnergyPlus .idf (Input Data File) and .csv
(Comma Separated Value results file) file into cMap. cMap parses these files for
location on the room geometry and thermal conditions, e.g., surface temperatures,
dry bulb temperature, etc. cMap then creates a 3-D set of gridpoints within the space
and calculates mean radiant temperature based on the methodology described in the
previous section.
Step 2 The user selects the desired thermal comfort metric and timeframe. cMap is capable
of producing the results for the following comfort indices and standards:
• Operative Temperature
• Mean Radiant Temperature
• Predicted Mean Vote
• Predicted Percentage Dissatisfied
• ASHRAE 55 Adaptive Comfort
• EN 15251 Adaptive Comfort
• NPR-CR 7251 Adaptive Comfort
The user can request any of these results at a single hour of the year, or averaged
over a particular time period.
The comfort indices are calculated based on the equations listed in section 2.1.
Step 3 Based on the users selected metric and timeframe, cMap creates two types of plots.
First, a scatterplot is created that plots the room averaged comfort value, for each
of the hours of the year. These values are plotted against the acceptable boundaries
for the selected metric. Second, a spatial contour heatmap is created that plots these
comfort values within the space. cMap produces 2-D slices through the space, and
the user can select 2-D slices in any direction (X-Y, X-Z, X-Y) and at any location
in the space. These plots can be exported from the program as .png files, which can
then be used in a report or presentation.
The steps above are all relatively quick. The software has been designed in anticipation
of the user iterating Steps 2 and 3 many times to understand how the comfort conditions
change over space and time throughout the year.
46
A diagram of the cMap software process is shown in Figure 4-2 below.
Various output examples for a summer and winter analysis case are shown in Figures
4-3 through 4-12. These cases are based on a single zone model with a north-facing window
and ASHRE 90.1 code minimum envelope properties. The analysis was run using a Boston,
MA climate file.
In order to perform the comfort calculations, cMap uses a hybrid control volume and
discretized method. The mean radiant temperature is calculated using a discretized method;
cMap creates a set of gridpoints within the space and calculates the view factors and mean
radiant temperature at each of those points. All of the other variables required to perform
the comfort calculations dry bulb temperature, airspeed are pulled from the EnergyPlus
.csv results file. All of the other variables, therefore, are calculated using the control volume
method.
It is critical to note the importance of mean radiant temperature here. This hybrid
method can provide spatial mapping for all of the different comfort variables only because
all of them are a function of mean radiant temperature. Since we can map mean radiant
temperature as function of space, we can therefore also map PMV, PPD, and the Adaptive
models as a function of space.
The capabilities of the cMap software are summarized in comparison to other thermal
comfort analysis tools in Table 4-1 below.
Table 4.1: cMap characteristics in comparison to existing thermal comfort software
tools
Tool Name
EnergyPlus
ASHRAE Comfort Tool
UC Berkeley AHTCM
Arup ROOM
cMap
4.2
Method
control colume
control volume
discretized
discretized
discretized
Scale
room
space
body
room
room
Spatial
point
point
space
space
space
Temporal
range
point
point
point
range
cMap Outputs
The cMap software and outputs have been intentionally designed to suggest a larger thermal
comfort analysis workflow that answers the question How comfortable is this space? The
47
software has been designed according to the visual information seeking mantra: Overview
first, then zoom and filter; details on demand. (41).
Overview first While spatial mapping is important, in order to determine how comfortable a space is, a user would first want to see the average comfort value for the space
plotted over the entire period of interest. This period may be all of the occupied
hours of the year, or a peak condition. This helps give the user an idea of when the
space, on average, meets the comfort criteria, and when it does not. cMap provides a
scatterplot of the room averaged comfort condition for all of the user-specified hours
of interest.
Zoom and filter Spatial mapping is particularly useful once the user has an idea of when,
on average, the space meets or exceeds the acceptable comfort bounds. A second tab
on the interface produces a contour heatmap plot that maps comfort variables in all
three dimensions in the space.
Details on demand Users may want to know the values for all of the environmental
variables at the time of interest. A section of the navigation bar displays all of these
relevant parameters indoor and outdoor dry bulb temperature, relative humidity,
airspeed, clothing level and activity level at the selected time period of interest. The
user can choose to display or hide this menu, depending on the desired level of detail.
The design of the cMap software has also attempted to best practices for data visualization wherever possible. (46)(52)(40)(7).
4.3
cMap Capabilities and Limitations
cMap makes many improvements on existing thermal comfort analysis tools, including the
following:
• cMap provides both spatial and temporal mapping of comfort parameters in all three
dimensions and at every hour of the year.
• cMap calculates angle factors, and automatically pulls surface temperatures and environmental conditions from EnergyPlus, rather than requiring the user to supply
these inputs.
48
• cMap analysis can be done very quickly, making it more viable for use in professional
practice.
However, cMap still has several limitations in its current form:
• cMap can only handle a single zone EnergyPlus model.
• cMap cannot account for direct solar radiation in some parts of the space
• cMap cannot handle complex surfaces
• The user must ask for the correct set of output variables in the EnergyPlus model
• cMap can only view one set of results at a time; the GUI doesnt have comparison
capabilities.
• cMap cannot predict air temperature and velocity fields in a space
• cMap cannot predict comfort for different parts of the human body
It is the authors intent that several of the limitations above will be resolved in future
versions of the software.
It is important to keep in mind what cMap is not. It is not a replacement for CFD
analysis. If a design problem requires the precise prediction of air temperature and velocity
fields in a space, a CFD package should be used. It is not a comfort model of the human
body. If a design problem requires the prediction of comfort for different parts of the human
body, UC Berkeleys Advanced Human Thermal Comfort Model or similar software should
be used.
49
Figure 4-1: Screenshot of cMap interface identifying the locations for the required
inputs, navigation controls, and analysis outputs
50
Figure 4-2: Diagram illustrating the analysis workflow within cMap.
Diagram4.jpg
51
Figure 4-3: Screenshot of cMap showing scatterplot output. Example shown is the
operative temperature for a single point in time in January.
52
Figure 4-4: Screenshot of cMap showing the contour heatmap output. Example shown
is the operative temperature for a single point in time in January, slice in the X-Y
direction
53
Figure 4-5: Screenshot of cMap showing the contour heatmap output. Example shown
is the operative temperature for a single point in time in January, slice in the Y-Z
direction
54
Figure 4-6: Screenshot of cMap showing the contour heatmap output. Example shown
is the operative temperature for a single point in time in January, slice in the X-Z
direction
55
Figure 4-7: Screenshot of cMap showing scatterplot output. Example shown is the
ASHRAE 55 adaptive comfort model, for a range of time during the summer. Data
points for the month of June are highlighted in red.
56
Figure 4-8: Screenshot of cMap showing scatterplot output. Example shown is the
ASHRAE 55 adaptive comfort model, for a single peak point in time during the
summer.
57
Figure 4-9: Screenshot of cMap showing the contour heatmap output. Example shown
is the operative temperature for a single peak point in time in June, slice in the X-Y
direction
58
Figure 4-10: Screenshot of cMap showing the contour heatmap output. Example
shown is the operative temperature for a single peak point in time in June, slice in
the Y-Z direction
59
Figure 4-11: Screenshot of cMap showing the contour heatmap output. Example
shown is the operative temperature for a single peak point in time in June, slice in
the X-Z direction
60
Chapter 5
Conclusions
5.1
Thesis Achievements
This overarching goal of this thesis is to bridge the gap between our thermal experience and
our building analysis tools. This work provides three primary contributions to the field of
thermal comfort research and for the analysis of thermal comfort in practice:
Methodology A unique methodology has been developed that produces mapping of thermal comfort parameters in all three spatial dimensions, as well as over time. Both
the Fanger and the adaptive comfort models have been fully incorporated into the
methodology.
Software An accompanying software program, called cMap, has been developed to illustrate the ways that this methodology can be used with existing energy analysis
software and to demonstrate how it can fit into existing analysis workflows in professional practice. The software is also intended to provide educational benefits, by
quantifying and visualizing the thermal environment using a range of thermal comfort
metrics.
Dialogue This work is intended to contribute to the larger discussion about thermal comfort and the built environment. As discussed in the background section, the dialogue
surrounding the definition of thermal comfort has evolved greatly over the past century. This work is intended to support discussions about the benefits of a diverse
thermal environment in our buildings.
61
5.2
Discussion
This thesis began with the proposition that thermally diverse buildings are compelling.
This introductory discussion defined thermal diversity and identified the eventual fate of
a compelling building it becomes beloved. What the initial discussion misses is a clear
argument for why thermal diversity makes a building compelling. I want to briefly address
that here.
The initial discussion highlighted the role of our thermal receptors as heat flow sensors.
These receptors are located in our skin and cover the entire surface area of our bodies. We
are constantly awash in a sea of thermal sensations the thermal environment is intimately
connected with our physical being. As discussed in the section on mean radiant temperature,
our thermal sensations are function of location. Not only are we awash in a sea of thermal
sensations, differentiation in those sensations informs our spatial sense. In sum, the thermal
environment plays a critical role in how we locate ourselves in space.
Second, the thermal environment plays a critical role in how we locate ourselves in
time. In fact, the thermal environment, to great extent, defines the very notion of time
itself. Consider day and night, defined by the presence or absence of the sun - the ultimate
source of heat for our planet. Similarly the passing of the seasons, cycles of growth and
death are defined by heatflow from the sun. This is also true for buildings. Our experience
of time in the built environment is largely defined by these thermal cycles. We know, for
example, that it is daytime when window surfaces are hotter and night when they are cooler.
The role of the thermal environment in our perceptions of space and time cannot be
underestimated. It goes beyond the issues of symbolism and sensuality that Heschong
identifies; the thermal environment is an essential part of our very existence.
Ultimately, this speaks to my larger intellectual project to change the way we, both as
individuals and as a society, relate to the natural environment. The thermal environment
has the power to make us feel connected to the natural world and can lead to a positive
shift in our environmental attitudes. As humans, we spend most of our time in buildings.
A connection to the natural world must happen in our buildings and can happen through
the use of thermal diversity.
How can a software tool do this? As illustrated in Figure 1-4, analysis tools play a
critical role in the design and construction process. Their ability to quantify performance
62
determines what can and cannot be built. If we want thermal diversity in our built environment, then our analysis tools absolutely need to be able to quantify it.
But the benefits of software tools do not lie solely in the outcome. Software also has
the ability to influence the user throughout the analysis process. The building simulation
guide from the Chartered Institute of Building Services Engineers (CIBSE), the equivalent
of ASHRAE in the United Kingdom, puts it thus:
Consequently, modelling can not only predict the end result, it can also identify the physical processes that have led to that result. By understanding the
reasons rather than just the answers, the designer can carry this knowledge forward to the next project. Modelling also speeds the process of learning, which
previously could come only from anecdotal feedback from completed projects.
(1).
Software tools can actually influence and improve the way that concepts are understood and communicated. Given the persistent hold that the Fanger model and thermal
neutrality and the well-mixed assumption have on our professional standards and analysis
methods, the educational effects of software tools are especially important. In order to build
a more thermally diverse environment, architects and engineers need to understand what
that means, both in general and for their designs. Software tools can play a very important
role in that learning process.
The hope is that the cMap methodology will be used as part of the suite of comfort
analysis tools that have been identified in this thesis. Project teams need to understand as
much as possible about the capabilities and limitations of each available comfort analysis
tool in order to select the best tool for to meet the project needs. The appropriate tool will
likely depend on the phase of design, the space type being analyzed, and the simulation
experience level of the design team. cMap could be used in any situation where an EnergyPlus model has been created (through native EnergyPlus or through a GUI program like
DesignBuilder or COMFEN). Ideally it would be used as early as possible in the design
process, and would be a particularly helpful aid in glazing and faade design decisions. It
could also add additional perspective to the value engineering process. Consider the case of
triple pane glazing, which often saves energy but can also be very expensive. If the design
team could also quantify the thermal comfort benefits, such an item may have a better
63
chance of remaining in the project.
5.3
Future Work
This work emerged, in part, out of the author’s experience in professional practice. A
number of validation studies and additions to both the methodology and the graphical user
interface would help further this work as a viable tool for use in professional practice.
In the short term, a number of features could be implemented into the software:
Direct solar radiation capabilities The current mean radiant temperature calculations
do not account for the impact of direct solar radiation. A modified mean radiant
temperature calculation that includes direct solar radiation should be implemented
into the code.
Complex faade capabilities The current mean radiant temperature calculations are not
capable of accounting for the effects of complex surfaces, e.g., multiple windows, small
windows. The current calculations assume one temperature per surface, for each of
the six surfaces in a zone. A mean radiant temperature calculation method accounting
for multiple temperatures per surface should be implemented into the code.
Multi-zone model capabilities The current code can only handle single-zone EnergyPlus models. Since most models in professional practice are multi-zone models, the
.idf and .csv file parsing code should be changed to handle multi-zone models.
Simple PMV calculation The PMV and PPD calculations for the current code are extremely slow, as a result of the iterative process required to determine tcl. The current
method is to use a bisection search to find tcl. While there are many possible solutions, one potential solution may be to use a simplified PMV calculation. A number
of authors have derived a non-iterative comfort equation, including Sherman (43) and
Federspiel (17). The viability of these methods for speeding up the PMV calculation
should be investigated.
EnergyPlus output viewing While cMap currently has the capability to display ambient
and indoor environmental parameters, e.g., dry bulb temperature, airspeed, the tool
could benefit from providing the user with a more robust way to view the EnergyPlus
64
.csv output. When faced with a unexpected plot results, the user should have a quick
way to look at the EnergyPlus output for troubleshooting purposes.
Exceedance timeseries heatmap While the scatterplot output is helpful, a heatmap
plots indicating the hours in exceedance of comfort conditions would help provide
the user with an even broader overview of the comfort conditions in the space. The
months of the year would be plotted on the x-axis and the hours of the day would
be plotted on the y-axis. The temporal maps shown in Mardaljevic (29) provide an
example of this type of plot.
In the authors opinion, all of the above features could be implemented with relatively
little difficulty.
In the longer term, a set of more involved studies would help provide additional evidence
for the value of this approach, and expand the analysis:
Comfort tools survey A survey of architects and engineers in professional practice could
help provide a very clear picture of current comfort analysis practices. Much of the
information on current comfort analysis practice comes from the authors experience.
Because there are no clear guidelines on the analysis of comfort in practice, a survey
could provide an initial step towards developing this type of guidance.
Comparison with CFD cMap does not purport to provide results similar to CFD. However, both tools can be used to quantify comfort. Given this, it would be useful to
have a detailed picture of the tradeoffs capabilities, cost, time, expertise - between
the two tools. This type of information could help practicing architects and engineers
make decisions about which tool to use and when.
Integration with body model cMap does not purport to provide information about
comfort levels for different parts of the body. The most complete comfort tool would
ideally provide comfort conditions across an entire space and across a human body. It
may be possible to integrate the cMap methodology with the UC Berkeley Advanced
Human Thermal Comfort model or similar body-based tool.
Better airflow considerations The output from EnergyPlus assumes that the air temperature and velocity is the same throughout a space. Since cMap is interested in
65
spatial mapping of comfort parameters, it may be possible to use heuristic methods
or similar work to provide more resolved information about airflow in a space. Something as simple as assuming a stratification profile would be an improvement over the
current well-mixed assumption.
66
Bibliography
[1] (1998). Applications Manual AM11: Building Energy and Environmental Modeling.
Chartered Institution of Building Services Engineers (CIBSE).
[2] (2004). Standard 55-2004: Thermal Environmental Conditions for Human Occupancy. The American Society of Heating, Refrigeration and Air-Conditioning Engineers
(ASHRAE).
[3] (2005). International Standard ISO 7730: Ergonomic of the thermal environment Analytical determination and interpretation of thermal comfort using calculation of the
PMV and PPD indices and local thermal comfort criteria.
[ASHRAE] ASHRAE.
Thermal comfort tool.
publications/bookstore/thermal-comfort-tool.
http://www.ashrae.org/resources–
[5] Bessoudo, M., Tzempelikos, A., Athienitis, A., and Zmeureanu, R. (2010). Indoor
thermal environmental conditions near glazed facades with shading devices - part i: Experiments and building thermal model. Building and Environment, 45:2506–2516.
[6] Borgeson, S. and Brager, G. (2011). Comfort standards and variations in exceedance
for mixed-mode buildings. Building Research and Information, 32(2):118–133.
[7] Borland, D. and Taylor, R. (2007). Rainbow color map (still) considered harmful. GG
A Visualization Viewpoints.
[8] Brager, G. and de Dear, R. (1998). Thermal adaptation in the built environment: A
literature review. Energy and Buildings, 27(1):83–96.
[Bruse] Bruse, M. Envi-met. http://www.envi-met.com/.
[10] Cannistraro, G., Franzitta, G., and Giaconia, C. (1992). Algorithms for the calculation of the view factors between human body and rectangular surfaces in parallelepiped
environments. Energy and Buildings, 19:51–60.
[11] Cooper, G. (1998). Air-conditioning America: Engineers and the controlled environment, 1900-1960. The Johns Hopkins University Press.
[12] de Dear, R. and Brager, G. (2001). The adaptive model of thermal comfort and energy
conservation in the built environment. International Journal of Biometeorology, 45:100–
108.
[13] de Dear, R. and Brager, G. (2002). Thermal comfort in naturally ventilated buildings:
Revisions to ashrae standard 55. Energy and Buildings, 34:549–561.
67
[14] de Dear, R., Brager, G., and Cooper, D. (1997). Ashrae rp-884: Developing an adaptive model of thermal comfort and preference. Technical report, The American Society
of Heating, Refrigeration and Air-Conditioning Engineers, Inc., and Environmental Analytics.
[15] Fanger, P. (1970). Thermal comfort: Analysis and application in environmental engineering. McGraw-Hill Book Company.
[16] Fathy, H. (1986). Natural Energy and Vernacular Architecture: Principles and Examples with Reference to Hot Arid Climates. The University of Chicago Press.
[17] Federspiel, C. C. (1992). User-adapatable and minimum-power thermal comfort control.
PhD thesis, Massachusetts Institute of Technology.
[18] Fountain, M. and Huizenga, C. (1995). Ashrae rp-781: A thermal sensation model for
use by the engineering profession. Technical report, The American Society of Heating,
Refrigeration and Air-Conditioning Engineers, Inc., and Environmental Analytics.
[19] Fountain, M. and Huizenga, C. (1997). A thermal sensation prediction software tool
for use by the profession. In ASHRAE Transactions, volume 103, pages 130–136. The
American Society of Heating, Refrigeration and Air-Conditioning Engineers.
[20] Frith, F. (1860). Egypt, Sinai and Jerusalem: a series of twenty photographic views.
William Mackenzie, London.
[21] Gan, G. (1994). Numerical method for a full assessment of indoor thermal comfort.
Indoor Air, 4:154–168.
[22] Gan, G. (2001). Analysis of mean radiant temperature and thermal comfort. Building
Services Engineering Research and Technology, 22(2):95–101.
[23] Haves, P., da Graca, G., and Linden, P. (2003). Use of simulation in the design of a large
naturally ventilated commercial office building. Proceedings of Building Simulation 2003
- 8th International IBPSA Conference, Eindhoven, Netherlands. International Building
Performance Simulation Association.
[24] Heschong, L. (1979). Thermal Delight in Architecture. MIT Press.
[25] Huizenga, C., Hui, Z., and Arens, E. (2001). A model of human physiology and comfort
for assessing complex thermal environments. Building and Environment, 36:691–699.
[26] Huizenga, C., Zhang, H., Mattelaer, P., Yu, T., Arens, E., and Lyons, P. (2006). Window performance for human thermal comfort: Report to the national fenestration rating
council. Technical report, Center for the Built Environment - University of California,
Berkeley.
[27] Humphreys, M. and Hancock, M. (2007). Do people like to feel ’neutral’ ? exploring
the variation of the desired thermal sensation on the ashrae scale. Energy and Buildings,
39:867–874.
[28] Humphreys, M. and Nicol, F. (2002). The validity of iso-pmv for predicting comfort
votes in every-day thermal environments. Energy and Buildings, 34(6):667–684.
68
[29] Mardaljevic, J. (2004). Spatio-temporal dynamics of solar shading for a parametrically
defined roof system. Energy and Buildings, 36:815–823.
[30] Matzarakis, A., Rutz, F., and Mayer, H. (2010). Modeling radiation fluxes in simple and complex environments: basics of the rayman model. International Journal of
Biometeorology, 54:131–139.
[31] Maury, B., Raymond, A., Revault, J., and Zakariya, M. (1983). Palais et Maisons
du Caire: Epoque Ottomane. Editions du Centre National de la Recherche Scientifique,
Paris.
[32] McCartney, K. and Nicol, F. (2002). Developing an adaptive control algorithm for
europe. Energy and Buildings, 34(6):623–635.
[33] Meguro, W. (2005). Beyond blue and red arrows: Optimizing natural ventilation in
large buildings. Master’s thesis, Massachusetts Institute of Technology.
[34] Negrao, C., Carvalho, C., and Melo, C. (1999). Numerical analysis of human thermal
comfort inside occupied spaces. Proceedings of Building Simulation 1999 - 6th International IBPSA Conference, Kyoto, Japan. International Building Performance Simulation
Association.
[35] Nicol, F. and Humphreys, M. (2002). Adaptive thermal comfort and sustainable thermal standards for buildins. Energy and Buildings, 34(6):563–572.
[36] Nicol, F. and Humphreys, M. (2010). Derivation of the adaptive equations for thermal
comfort in free-running buildings in european standard en15251. Building and Environment, 45(1):11–17.
[of Energy] of Energy,
U. D.
Energyplus
http://apps1.eere.energy.gov/buildings/energyplus/.
energy
simulation
software.
[38] Powitz, R. and Balsamo, J. (1999). Measuring thermal comfort. Journal of Environmental Health, 62(5):37–38.
[39] Rees, S., Lomas, K., and Fiala, D. (2008). Predicting local thermal discomfort adjacent
to glazing. In ASHRAE Transactions, volume 114, page 1. The American Society of
Heating, Refrigeration and Air-Conditioning Engineers.
[40] Rogowitz, B. and Treinish, L. (1995). How not to lie with visualization. IBM Watson.
[41] Schneiderman, B. (1996). The eyes have it: a task by data type taxonomy for information visualizations. Proceedings of Visual Languages ’96.
[42] Sebastian Herkel, Frank Schoffel, J. D. (1999). Interactive three-dimensional visualisation of thermal comfort. Proceedings of Building Simulation 1999 - 6th International
IBPSA Conference, Kyoto, Japan. International Building Performance Simulation Association.
[43] Sherman, M. (1985). A simplified model of thermal comfort. Energy and Buildings,
8(37-50).
69
[44] Shove, E., Chappells, H., Lutzenhiser, L., and Hackett, B. (2008). Comfort in a lower
carbon society. Building Research and Information, 36(4):307–311.
[45] Srebric, J. (2011). Building Performance Simulation for Design and Optimization,
chapter Chapter 6: Ventilation performance prediction. Routledge.
[46] Tufte, E. (1997). Visual and Statistical Thinking: Displays of Evidence for Making
Decisions. Graphics Press.
[47] van der Linden, A., Boerstra, A., Raue, A., Kurvers, S., and de Dear, R. (2006).
Adaptive temperature limits: A new guideline in the netherlands - a new approach for
the assessment of building performance with respect to thermal indoor climate. Energy
and Buildings, 38:8–17.
[48] van Treeck, C., Frisch, J., Egger, M., and Rank, E. (2009). Model-adaptive analysis
of indoor thermal comfort. Proceedings of Building Simulation 2009 - 11th International
IBPSA Conference, Glasgow, Scotland. International Building Performance Simulation
Association.
[49] Webb, A. L. (2009). How to win poetic praise and influence architects. Magazine on
Urbanism (MONU), (11).
[50] White, A. and Holmes, M. (2009). Advanced simulation applications using room. Proceedings of Building Simulation 2009 - 11th International IBPSA Conference, Glasgow,
Scotland. International Building Performance Simulation Association.
[51] Yang, T., Cropper, P., Cook, M., Yousaf, R., and Fiala, D. (2007). A new simulation
system to predict human-environment thermal interactions in naturally ventilated buildings. Proceedings of Building Simulation 2007 - 10th International IBPSA Conference,
Beijing, China. International Building Performance Simulation Association.
[52] Zeileis, A., Hornik, K., and Murrell, P. (2009). Escaping rgbland: Selecting colors for
statistical graphics. Computational Statistics and Data Analysis.
70
Appendix A
cMap Source Code
The full source code for cMap is reproduced below. The program is written in Python. The
program relies on Numpy and Scipy for performing the primary calculations. The program
uses Matplotlib to create the plots. The program uses wxPython to create the graphical
user interface.
1
2
# !/usr/bin/env python
3
# Filename: cMap.py
4
5
""" Script creates 3-D set of grid points from an EnergyPlus .idf file
6
Script uses EnergyPlus .csv file to calculate comfort outputs at each point
7
Script outputs results as .png image file
8
Script includes interface for controlling raw inputs & visualizing output
9
"""
10
11
# BEGIN INTERFACE -----------------------------------------------------
12
# Mapping Script Imports ----------------------------------------------
13
from numpy import *
14
import csv
15
import re
16
import math
17
from matplotlib import pyplot, mpl, cm
71
18
from matplotlib.colors import *
19
from scipy.optimize import brentq
20
from matplotlib.lines import Line2D
21
22
# Interface Imports ---------------------------------------------------
23
import sys
24
import os
25
import wx
26
import wx.lib.agw.aui as aui
27
import wx.combo
28
import wx.lib.buttons as buttons
29
import wx.lib.agw.floatspin as FS
30
import wx.lib.scrolledpanel as scrolled
31
from mpl_toolkits.mplot3d import axes3d
32
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
33
import wx.lib.agw.foldpanelbar as fpb
34
from matplotlib.figure import Figure
35
from matplotlib.backends.backend_wxagg import \
36
37
FigureCanvasWxAgg as FigureCanvas
import matplotlib.font_manager as fm
38
39
# Interface Components ------------------------------------------------
40
class MyFrame(wx.Frame):
41
42
43
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title, size=(1100, 760))
44
45
# AUI Panes, Panels, Menu -------------------------------------
46
self._mgr = aui.AuiManager(self)
47
48
self.panel1 = wx.Panel(self, -1)
49
self.panel2 = wx.Panel(self, -1)
72
50
self.panel2.SetBackgroundColour("White")
51
self.panel3 = wx.Panel(self, -1)
52
self.panel3.SetBackgroundColour("White")
53
self.panel4 = wx.Panel(self, -1)
54
self.panel4.SetBackgroundColour("White")
55
56
self.CreateStatusBar()
57
58
# add the panes to the manager
59
self._mgr.AddPane(self.panel1, aui.AuiPaneInfo().
60
Caption("Input Parameters").CaptionVisible(False).Left().
61
Layer(2).Floatable(True).Dockable(True).
62
MinimizeButton(True).MaximizeButton(True).CloseButton(True).
63
BestSize(wx.Size(300, 760)))
64
self._mgr.AddPane(self.panel2, aui.AuiPaneInfo().Name("panel2").
65
Caption("Heatmap").Center().Floatable(True).Dockable(True).
66
MinimizeButton(True).MaximizeButton(True).CloseButton(True).
67
BestSize(wx.Size(400, 360)))
68
self._mgr.AddPane(self.panel3, aui.AuiPaneInfo().Name("panel3").
69
Caption("Scatter Plot").Center().Floatable(True).
70
Dockable(True).MinimizeButton(True).MaximizeButton(True).
71
CloseButton(True).BestSize(wx.Size(400, 360)),
72
target=self._mgr.GetPane("panel2"))
73
self._mgr.AddPane(self.panel4, aui.AuiPaneInfo().Name("panel4").
74
Caption("Slice Key").Left().Floatable(True).Dockable(True).
75
MinimizeButton(True).MaximizeButton(True).CloseButton(True).
76
BestSize(wx.Size(200, 200)))
77
78
# Float panel4
79
self._mgr.GetPane("panel4").Float().FloatingPosition((940,480)). \
80
FloatingSize((200,150))
81
73
82
# Tell the manager to commit all the changes just made
83
self._mgr.Update()
84
self.Bind(wx.EVT_CLOSE, self.OnClose)
85
86
# Default number of gridpoints --------------------------------
87
self.gpt = 10
88
89
# Sizers & Sizer Items ----------------------------------------
90
# set-up all sizers on panel1
91
idfSizer
= wx.BoxSizer(wx.VERTICAL)
92
csvSizer
= wx.BoxSizer(wx.VERTICAL)
93
calcTextSizer1
94
calcTextSizer
95
dateSizer1
= wx.GridBagSizer(hgap=6, vgap=0)
96
dateSizer2
= wx.GridBagSizer(hgap=6, vgap=0)
97
dateSizer3
= wx.GridBagSizer(hgap=6, vgap=0)
= wx.GridBagSizer(hgap=6, vgap=0)
= wx.GridSizer(rows=1, cols=3, hgap=40, vgap=0)
98
99
comfSizer
= wx.GridBagSizer(hgap=6, vgap=0)
100
pointTextSizer
= wx.GridSizer(rows=1, cols=2, hgap=110, vgap=0)
101
cutSizer
102
colorTextSizer
103
colorSizer
= wx.GridBagSizer(hgap=7, vgap=0)
104
pointSizer
= wx.GridBagSizer(hgap=0, vgap=0)
105
scaleTextSizer
106
scaleSizer
= wx.GridBagSizer(hgap=5, vgap=0)
107
printSizer
= wx.BoxSizer(wx.VERTICAL)
= wx.GridBagSizer(hgap=10, vgap=0)
= wx.GridSizer(rows=1, cols=1)
= wx.GridSizer(rows=1, cols=3, hgap=25, vgap=0)
108
109
# all font & caption bar styles -------------------------------
110
titleFont = wx.Font(13, wx.SWISS, wx.NORMAL, wx.NORMAL)
111
subFont = wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
112
subFontSM = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
113
subBoldFont = wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
74
114
dateFont = wx.Font(7, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
115
scrollingFont = wx.Font(9, wx.DEFAULT, wx.ITALIC, wx.NORMAL)
116
117
cs = fpb.CaptionBarStyle()
118
cs.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V)
119
color1 = wx.Colour(220, 220, 220)
120
color2 = wx.Colour(195, 195, 195)
121
cs.SetFirstColour(color1)
122
cs.SetSecondColour(color2)
123
cs.SetCaptionFont(titleFont)
124
125
# Sizer Items - controls and static texts ---------------------
126
# create fold panel instance on main panel
127
self.fold_panel = fpb.FoldPanelBar(self.panel1, wx.ID_ANY,
128
style=fpb.FPB_VERTICAL)
129
130
# Fold panel bar - file selector -------------------------------
131
sectFile = self.fold_panel.AddFoldPanel("File Selection",
132
collapsed=False, cbstyle=cs)
133
subpanelFile = wx.Panel(sectFile, -1)
134
sizerFile = wx.BoxSizer(wx.VERTICAL)
135
136
# texts
137
idfTxt = wx.StaticText(subpanelFile, -1, "EnergyPlus .idf file",
138
style=wx.ALIGN_LEFT)
139
idfTxt.SetFont(subFont)
140
csvTxt = wx.StaticText(subpanelFile, -1, "EnergyPlus .csv file",
141
142
style=wx.ALIGN_LEFT)
csvTxt.SetFont(subFont)
143
144
# controls
145
self.idfSel = FileSelectorComboIDF(subpanelFile, size=(280, -1))
75
146
idfSizer.Add(self.idfSel, 0, wx.LEFT, border=5)
147
self.csvSel = FileSelectorComboCSV(subpanelFile, size=(280, -1))
148
csvSizer.Add(self.csvSel, 0, wx.LEFT, border=5)
149
150
# load button
151
self.load = wx.Button(subpanelFile, -1, ’Load Files’,
152
153
size=(110, -1))
self.load.Bind(wx.EVT_BUTTON, self.loadFiles)
154
155
# put controls on folding sizer
156
sizerFile.Add(idfTxt, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 7)
157
sizerFile.Add(idfSizer, 0, wx.TOP|wx.BOTTOM, 2)
158
sizerFile.AddSpacer(5)
159
sizerFile.Add(csvTxt, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 7)
160
sizerFile.Add(csvSizer, 0, wx.TOP|wx.BOTTOM, 2)
161
sizerFile.Add(self.load, 0, wx.TOP|wx.BOTTOM|wx.ALIGN_RIGHT, 2)
162
163
sizerFile.AddSpacer(15)
164
165
# set folding sizer on folding panel
166
subpanelFile.SetSizer(sizerFile)
167
subpanelFile.Fit()
168
169
# add folding panel to foldpanelbar
170
self.fold_panel.AddFoldPanelWindow(sectFile, subpanelFile,
171
172
fpb.FPB_ALIGN_LEFT)
self.fold_panel.AddFoldPanelSeparator(sectFile)
173
174
# Fold panel bar - metric selector ----------------------------
175
sectMetric = self.fold_panel.AddFoldPanel("Metric & Timeframe",
176
177
collapsed=False, cbstyle=cs)
subpanelMetric = wx.Panel(sectMetric, -1)
76
178
sizerMetric = wx.BoxSizer(wx.VERTICAL)
179
180
# texts
181
calcTxt = wx.StaticText(subpanelMetric, -1, "Comfort metric",
182
style=wx.ALIGN_LEFT)
183
calcTxt.SetFont(subFont)
184
timeframeTxt = wx.StaticText(subpanelMetric, -1,
185
"Calculation timeframe", style=wx.ALIGN_LEFT)
186
timeframeTxt.SetFont(subFont)
187
simpMonthTxt = wx.StaticText(subpanelMetric, -1, "Month",
188
style=wx.CENTER)
189
simpMonthTxt.SetFont(dateFont)
190
simpDayTxt = wx.StaticText(subpanelMetric, -1, "Day",
191
style=wx.ALIGN_CENTER)
192
simpDayTxt.SetFont(dateFont)
193
simpHourTxt = wx.StaticText(subpanelMetric, -1, "Hour",
194
style=wx.ALIGN_CENTER)
195
simpHourTxt.SetFont(dateFont)
196
stTxt = wx.StaticText(subpanelMetric, -1, "Start date/time",
197
style=wx.ALIGN_LEFT)
198
stTxt.SetFont(subFont)
199
self.enTxt = wx.StaticText(subpanelMetric, -1, "End date/time",
200
style=wx.ALIGN_LEFT)
201
self.enTxt.SetFont(subFont)
202
calcTextSizer1.Add(stTxt,pos=(0,0))
203
204
# add text to text gridsizers
205
calcTextSizer.Add(simpMonthTxt, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0)
206
calcTextSizer.Add(simpDayTxt, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 4)
207
calcTextSizer.Add(simpHourTxt, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0)
208
209
# controls
77
210
# calc type selector
211
self.calcDrop = wx.Choice(parent=subpanelMetric, size=(280, -1))
212
self.calcList = [
213
’Mean Radiant Temperature
[C]’,
214
’Operative Temperature
215
’Predicted Mean Vote
216
’Percentage People Dissatisfied
217
’Adaptive Model - ASHRAE Standard 55
218
’Adaptive Model - EN Standard 15251
219
’Adaptive Model - NPR-CR 1752 Type Beta
220
]
[C]’,
[+/-]’,
[%]’,
[C]’,
[C]’,
[C]’,
221
self.calcDrop.AppendItems(strings=self.calcList)
222
self.calcDrop.Select(n=0)
223
224
self.timeframeDrop = wx.Choice(parent=subpanelMetric, size=(280, -1))
225
self.timeframeList = [
226
’Time Range
[#] ’,
227
’Point in Time
228
]
[#] ’,
229
self.timeframeDrop.AppendItems(strings=self.timeframeList)
230
self.timeframeDrop.Select(n=0)
231
self.timeframeDrop.Bind(wx.EVT_CHOICE, self.DisableAnnual)
232
233
# run button
234
self.run = wx.Button(subpanelMetric, -1, ’Run cMap’, size=(80, -1))
235
self.run.Bind(wx.EVT_BUTTON, self.runCmap)
236
dateSizer2.Add(self.run, pos=(0,3),flag=wx.TOP|wx.LEFT|wx.ALIGN_RIGHT,
237
border=5)
238
239
# calc start date/time spins
240
self.StartMonthSpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
241
self.StartMonthSpin.SetRange(1,12)
78
242
self.StartMonthSpin.SetValue(1)
243
self.StartMonthSpin.Bind(wx.EVT_SPIN, self.runUpdate)
244
self.StartMonthSpin.Bind(wx.EVT_TEXT, self.runUpdate)
245
dateSizer1.Add(self.StartMonthSpin, pos=(1,0))
246
247
self.StartDaySpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
248
self.StartDaySpin.SetRange(1,31)
249
self.StartDaySpin.SetValue(1)
250
self.StartDaySpin.Bind(wx.EVT_SPIN, self.runUpdate)
251
self.StartDaySpin.Bind(wx.EVT_TEXT, self.runUpdate)
252
dateSizer1.Add(self.StartDaySpin, pos=(1,1))
253
254
self.StartHourSpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
255
self.StartHourSpin.SetRange(1,24)
256
self.StartHourSpin.SetValue(1)
257
self.StartHourSpin.Bind(wx.EVT_SPIN, self.runUpdate)
258
self.StartHourSpin.Bind(wx.EVT_TEXT, self.runUpdate)
259
dateSizer1.Add(self.StartHourSpin, pos=(1,2))
260
261
# calc end date/time spins
262
self.EndMonthSpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
263
self.EndMonthSpin.SetRange(1,12)
264
self.EndMonthSpin.SetValue(12)
265
dateSizer2.Add(self.EndMonthSpin, pos=(0,0))
266
267
self.EndDaySpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
268
self.EndDaySpin.SetRange(1,31)
269
self.EndDaySpin.SetValue(31)
270
dateSizer2.Add(self.EndDaySpin, pos=(0,1))
271
272
self.EndHourSpin = wx.SpinCtrl(subpanelMetric, -1, size=(60, -1))
273
self.EndHourSpin.SetRange(1,24)
79
274
self.EndHourSpin.SetValue(24)
275
dateSizer2.Add(self.EndHourSpin, pos=(0,2))
276
277
dateSizer1.Add(simpMonthTxt, pos=(0,0), flag=wx.LEFT, border=8)
278
dateSizer1.Add(simpDayTxt, pos=(0,1), flag=wx.LEFT, border=12)
279
dateSizer1.Add(simpHourTxt, pos=(0,2), flag=wx.LEFT, border=12)
280
281
# put controls on folding sizer
282
sizerMetric.Add(calcTxt, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 8)
283
sizerMetric.AddSpacer(2)
284
sizerMetric.Add(self.calcDrop, 0, wx.LEFT, border=5)
285
sizerMetric.AddSpacer(10)
286
sizerMetric.Add(timeframeTxt, 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 8)
287
sizerMetric.AddSpacer(2)
288
sizerMetric.Add(self.timeframeDrop, 0, wx.LEFT, border=5)
289
sizerMetric.AddSpacer(10)
290
sizerMetric.Add(dateSizer1, 0, wx.LEFT, 5)
291
sizerMetric.Add(calcTextSizer1, flag=wx.LEFT, border=8)
292
sizerMetric.AddSpacer(5)
293
sizerMetric.Add(dateSizer2, 0, wx.LEFT, 5)
294
sizerMetric.Add(self.enTxt, flag=wx.LEFT, border=8)
295
sizerMetric.AddSpacer(15)
296
297
# set folding sizer on folding panel
298
subpanelMetric.SetSizer(sizerMetric)
299
subpanelMetric.Fit()
300
301
# add folding panel to foldpanelbar
302
self.fold_panel.AddFoldPanelWindow(sectMetric, subpanelMetric,
303
304
fpb.FPB_ALIGN_LEFT)
self.fold_panel.AddFoldPanelSeparator(sectMetric)
305
80
306
# Fold panel bar - thermal comfort parameters -----------------
307
sectComfParams = self.fold_panel.AddFoldPanel
308
\
("Thermal Comfort Parameters", collapsed=False, cbstyle=cs)
309
subpanelComfParams = wx.Panel(sectComfParams, -1)
310
sizerComfParams = wx.BoxSizer(wx.VERTICAL)
311
312
# texts & controls
313
outdoorTxt = wx.StaticText(subpanelComfParams, -1, "OUTDOOR",
314
style=wx.ALIGN_RIGHT)
315
outdoorTxt.SetFont(subFont)
316
comfSizer.Add(outdoorTxt, pos=(0,0),
317
flag=wx.RIGHT|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
318
319
320
indoorText = wx.StaticText(subpanelComfParams, -1, "INDOOR",
style=wx.ALIGN_RIGHT)
321
indoorText.SetFont(subFont)
322
comfSizer.Add(indoorText, pos=(0,3),
323
flag=wx.RIGHT|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
324
325
326
peopleText = wx.StaticText(subpanelComfParams, -1, "PEOPLE",
style=wx.ALIGN_RIGHT)
327
peopleText.SetFont(subFont)
328
comfSizer.Add(peopleText, pos=(0,6),
329
flag=wx.RIGHT|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
330
331
# display items for outdoor dry bulb
332
oDBTxt = wx.StaticText(subpanelComfParams, -1, "dry bulb:",
333
style=wx.ALIGN_RIGHT)
334
oDBTxt.SetFont(subFont)
335
comfSizer.Add(oDBTxt, pos=(1,0),
336
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=1)
337
81
338
339
self.oDBSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
340
self.oDBSpin.SetFont(subBoldFont)
341
comfSizer.Add(self.oDBSpin, pos=(1,1),
342
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
343
344
345
oDBUnit = wx.StaticText(subpanelComfParams, -1, "C",
style=wx.ALIGN_RIGHT)
346
oDBUnit.SetFont(subFontSM)
347
comfSizer.Add(oDBUnit, pos=(1,2),
348
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
349
350
# display items for outdoor relative humidity
351
oRHTxt = wx.StaticText(subpanelComfParams, -1, "humidity:",
352
style=wx.ALIGN_RIGHT)
353
oRHTxt.SetFont(subFont)
354
comfSizer.Add(oRHTxt, pos=(2,0),
355
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
356
357
358
self.oRHSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
359
self.oRHSpin.SetFont(subBoldFont)
360
comfSizer.Add(self.oRHSpin, pos=(2,1),
361
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
362
363
364
oRHUnit = wx.StaticText(subpanelComfParams, -1, "%",
style=wx.ALIGN_RIGHT)
365
oRHUnit.SetFont(subFontSM)
366
comfSizer.Add(oRHUnit, pos=(2,2),
367
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
368
369
# display items for outdoor airspeed
82
370
371
oAirVeloTxt = wx.StaticText(subpanelComfParams, -1, "airspeed",
style=wx.ALIGN_RIGHT)
372
oAirVeloTxt.SetFont(subFont)
373
comfSizer.Add(oAirVeloTxt, pos=(3,0),
374
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
375
376
377
self.oAirVSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
378
self.oAirVSpin.SetFont(subBoldFont)
379
comfSizer.Add(self.oAirVSpin, pos=(3,1),
380
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
381
382
383
oAirVUnit = wx.StaticText(subpanelComfParams, -1, "m/s",
style=wx.ALIGN_RIGHT)
384
oAirVUnit.SetFont(subFontSM)
385
comfSizer.Add(oAirVUnit, pos=(3,2),
386
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=1)
387
388
# display items for indoor dry bulb
389
zDBTxt = wx.StaticText(subpanelComfParams, -1, "dry bulb:",
390
style=wx.ALIGN_RIGHT)
391
zDBTxt.SetFont(subFont)
392
comfSizer.Add(zDBTxt, pos=(1,3),
393
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
394
395
396
self.zDBSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
397
self.zDBSpin.SetFont(subBoldFont)
398
comfSizer.Add(self.zDBSpin, pos=(1,4),
399
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
400
401
zDBUnit = wx.StaticText(subpanelComfParams, -1, "C",
83
",
402
style=wx.ALIGN_RIGHT)
403
zDBUnit.SetFont(subFontSM)
404
comfSizer.Add(zDBUnit, pos=(1,5),
405
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
406
407
# display items for indoor relative humidity
408
zRHTxt = wx.StaticText(subpanelComfParams, -1, "humidity:",
409
style=wx.ALIGN_RIGHT)
410
zRHTxt.SetFont(subFont)
411
comfSizer.Add(zRHTxt, pos=(2,3),
412
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
413
414
415
self.zRHSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
416
self.zRHSpin.SetFont(subBoldFont)
417
comfSizer.Add(self.zRHSpin, pos=(2,4),
418
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
419
420
421
zRHUnit = wx.StaticText(subpanelComfParams, -1, "%",
style=wx.ALIGN_RIGHT)
422
zRHUnit.SetFont(subFontSM)
423
comfSizer.Add(zRHUnit, pos=(2,5),
424
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
425
426
# display items for indoor airspeed
427
zAirVTxt = wx.StaticText(subpanelComfParams, -1, "airspeed:",
428
style=wx.ALIGN_RIGHT)
429
zAirVTxt.SetFont(subFont)
430
comfSizer.Add(zAirVTxt, pos=(3,3),
431
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
432
433
self.zAirVSchSpin = wx.StaticText(subpanelComfParams, -1, "-
84
",
434
style=wx.ALIGN_LEFT)
435
self.zAirVSchSpin.SetFont(subBoldFont)
436
comfSizer.Add(self.zAirVSchSpin, pos=(3,4),
437
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
438
439
440
zAirVUnit = wx.StaticText(subpanelComfParams, -1, "m/s",
style=wx.ALIGN_RIGHT)
441
zAirVUnit.SetFont(subFontSM)
442
comfSizer.Add(zAirVUnit, pos=(3,5),
443
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
444
445
# display items for clothing level
446
zCloSchTxt = wx.StaticText(subpanelComfParams, -1, "clothing:",
447
style=wx.ALIGN_RIGHT)
448
zCloSchTxt.SetFont(subFont)
449
comfSizer.Add(zCloSchTxt, pos=(1,6),
450
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=1)
451
452
453
self.zCloSchSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
454
self.zCloSchSpin.SetFont(subBoldFont)
455
comfSizer.Add(self.zCloSchSpin, pos=(1,7),
456
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
457
458
459
CloUnit = wx.StaticText(subpanelComfParams, -1, "clo",
style=wx.ALIGN_RIGHT)
460
CloUnit.SetFont(subFontSM)
461
comfSizer.Add(CloUnit, pos=(1,8),
462
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
463
464
# display items for activity level
465
zActSchTxt = wx.StaticText(subpanelComfParams, -1, "activity:",
85
466
style=wx.ALIGN_RIGHT)
467
zActSchTxt.SetFont(subFont)
468
comfSizer.Add(zActSchTxt, pos=(2,6),
469
flag=wx.TOP|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,border=3)
470
471
472
self.zActSchSpin = wx.StaticText(subpanelComfParams, -1, "style=wx.ALIGN_LEFT)
473
self.zActSchSpin.SetFont(subBoldFont)
474
comfSizer.Add(self.zActSchSpin, pos=(2,7),
475
",
flag=wx.TOP|wx.ALIGN_CENTER_VERTICAL,border=3)
476
477
478
MetUnit = wx.StaticText(subpanelComfParams, -1, "met",
style=wx.ALIGN_RIGHT)
479
MetUnit.SetFont(subFontSM)
480
comfSizer.Add(MetUnit, pos=(2,8),
481
flag=wx.TOP|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL,border=3)
482
483
# put controls on folding sizer
484
sizerComfParams.AddSpacer(5)
485
sizerComfParams.Add(comfSizer, 0,
486
487
wx.LEFT| wx.ALIGN_CENTER_VERTICAL, 5)
sizerComfParams.AddSpacer(15)
488
489
# set folding sizer on folding panel
490
subpanelComfParams.SetSizer(sizerComfParams)
491
subpanelComfParams.Fit()
492
493
# add folding panel to foldpanelbar
494
self.fold_panel.AddFoldPanelWindow(sectComfParams,
495
496
subpanelComfParams, fpb.FPB_ALIGN_LEFT)
self.fold_panel.AddFoldPanelSeparator(sectComfParams)
497
86
498
# Fold panel bar - display settings ---------------------------
499
sectDisplay = self.fold_panel.AddFoldPanel("Display Settings",
500
collapsed=False, cbstyle=cs)
501
subpanelDisplay = wx.Panel(sectDisplay, -1)
502
sizerDisplay = wx.BoxSizer(wx.VERTICAL)
503
504
# texts
505
scaleMinTxt = wx.StaticText(subpanelDisplay, -1, "Min",
506
style=wx.CENTER)
507
scaleMinTxt.SetFont(dateFont)
508
scaleMaxTxt = wx.StaticText(subpanelDisplay, -1, "Max",
509
style=wx.CENTER)
510
scaleMaxTxt.SetFont(dateFont)
511
scaleSizer.Add(scaleMinTxt, pos=(0,0),
512
513
514
flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=5)
scaleSizer.Add(scaleMaxTxt,
pos=(0,1),
flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=5)
515
516
# controls
517
# analysis cut selector
518
self.xy = wx.RadioButton(subpanelDisplay, -1, ’X-Y’,
519
style=wx.RB_GROUP)
520
self.xz = wx.RadioButton(subpanelDisplay, -1, ’X-Z’)
521
self.yz = wx.RadioButton(subpanelDisplay, -1, ’Y-Z’)
522
self.xy.SetFont(subFont)
523
self.xz.SetFont(subFont)
524
self.yz.SetFont(subFont)
525
self.xy.Bind(wx.EVT_RADIOBUTTON, self.OnCheck)
526
self.xz.Bind(wx.EVT_RADIOBUTTON, self.OnCheck)
527
self.yz.Bind(wx.EVT_RADIOBUTTON, self.OnCheck)
528
cutSizer.Add(self.xy, pos=(0,0), flag=wx.LEFT|wx.TOP, border=5)
529
cutSizer.Add(self.xz, pos=(0,1), flag=wx.LEFT|wx.TOP, border=5)
87
530
cutSizer.Add(self.yz, pos=(0,2), flag=wx.LEFT|wx.TOP, border=5)
531
self.xy.SetValue(True)
532
533
# slice selector
534
self.sliceSpin = wx.SpinCtrl(subpanelDisplay, -1, size=(60, -1))
535
self.sliceSpin.SetRange(0,self.gpt-1)
536
self.sliceSpin.SetValue(0)
537
self.sliceSpin.Bind(wx.EVT_SPIN, self.OnSlice)
538
self.sliceSpin.Bind(wx.EVT_TEXT, self.OnSlice)
539
cutSizer.Add(self.sliceSpin, pos=(0,3),
540
flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=8)
541
542
# scatterplot color selector
543
self.noColor = wx.RadioButton(subpanelDisplay, -1, ’None’,
544
style=wx.RB_GROUP)
545
self.monthColor = wx.RadioButton(subpanelDisplay, -1, ’Month’)
546
self.timeColor = wx.RadioButton(subpanelDisplay, -1, ’Hour’)
547
self.noColor.SetFont(subFont)
548
self.monthColor.SetFont(subFont)
549
self.timeColor.SetFont(subFont)
550
self.noColor.Bind(wx.EVT_RADIOBUTTON, self.runCmap)
551
self.monthColor.Bind(wx.EVT_RADIOBUTTON, self.runCmap)
552
self.timeColor.Bind(wx.EVT_RADIOBUTTON, self.runCmap)
553
colorSizer.Add(self.noColor, pos=(0,0), flag=wx.LEFT, border=5)
554
colorSizer.Add(self.monthColor, pos=(0,1), flag=wx.LEFT, border=15)
555
colorSizer.Add(self.timeColor, pos=(0,2), flag=wx.LEFT, border=15)
556
self.colorStart = wx.TextCtrl(subpanelDisplay, value=’0’,size=(30, -1))
557
self.colorStart.Bind(wx.EVT_TEXT, self.runUpdate)
558
colorSizer.Add(self.colorStart, pos=(0,3), flag = wx.LEFT, border=15)
559
self.colorEnd = wx.TextCtrl(subpanelDisplay, value=’0’,size=(30, -1))
560
self.colorEnd.Bind(wx.EVT_TEXT, self.runUpdate)
561
colorSizer.Add(self.colorEnd, pos=(0,4),flag = wx.LEFT, border=5)
88
562
self.noColor.SetValue(True)
563
564
# colors for plots
565
self.facecolorBlue = ’#0E689D’
566
self.facecolorRed = ’#AC1D34’
567
self.edgecolor = ’#ECE7F2’
568
self.gridcolor = ’#737373’
569
self.axescolor = ’#BDBDBD’
570
self.alpha = 1
571
self.lw = 0.2
572
self.scattersize = 15
573
self.cmap = cm.RdBu_r
574
575
# colormap scale selector
576
self.cmapMinDisplay = wx.TextCtrl(subpanelDisplay, value=’ ’,
577
578
579
580
581
582
583
584
585
size=(40, -1))
scaleSizer.Add(self.cmapMinDisplay, pos=(1,0), flag = wx.LEFT,
border=5)
self.cmapMaxDisplay = wx.TextCtrl(subpanelDisplay, value=’ ’,
size=(40, -1))
scaleSizer.Add(self.cmapMaxDisplay, pos=(1,1), flag = wx.LEFT,
border=5)
self.adjust = wx.Button(subpanelDisplay, -1, ’Adjust’,
size=(65, -1))
586
self.adjust.Bind(wx.EVT_BUTTON, self.OnAdjustScale)
587
scaleSizer.Add(self.adjust, pos=(1,2), flag = wx.LEFT,
588
589
590
border=5)
self.auto = wx.Button(subpanelDisplay, -1, ’Auto’,
size=(65, -1))
591
self.auto.Bind(wx.EVT_BUTTON, self.OnAutoScale)
592
scaleSizer.Add(self.auto, pos=(1,3), flag = wx.LEFT, border=0)
593
89
594
# put controls on folding sizer
595
sizerDisplay.AddSpacer(5)
596
sizerDisplay.Add(colorSizer, 0, wx.LEFT| wx.ALIGN_CENTER_VERTICAL, 5)
597
sizerDisplay.AddSpacer(7)
598
sizerDisplay.Add(cutSizer, 0, wx.LEFT| wx.ALIGN_CENTER_VERTICAL, 5)
599
sizerDisplay.AddSpacer(3)
600
sizerDisplay.Add(scaleTextSizer, 0, wx.LEFT|
601
wx.ALIGN_CENTER_VERTICAL, 10)
602
sizerDisplay.Add(scaleSizer, 0, wx.LEFT, 5)
603
sizerDisplay.AddSpacer(15)
604
605
# set folding sizer on folding panel
606
subpanelDisplay.SetSizer(sizerDisplay)
607
subpanelDisplay.Fit()
608
609
# add folding panel to foldpanelbar
610
self.fold_panel.AddFoldPanelWindow(sectDisplay, subpanelDisplay,
611
612
fpb.FPB_ALIGN_LEFT)
self.fold_panel.AddFoldPanelSeparator(sectDisplay)
613
614
# Fold panel bar - export data --------------------------------
615
sectExport = self.fold_panel.AddFoldPanel("Export Data",
616
collapsed=False, cbstyle=cs)
617
subpanelExport = wx.Panel(sectExport, -1)
618
sizerExport = wx.BoxSizer(wx.VERTICAL)
619
620
# controls
621
# print to .png
622
self.printPNG = wx.Button(subpanelExport, -1, ’Export Plots’,
623
size=(100, -1))
624
self.printPNG.Bind(wx.EVT_BUTTON, self.OnPrint)
625
printSizer.Add(self.printPNG, 0, wx.ALIGN_RIGHT, border=0)
90
626
627
# put controls on folding sizer
628
sizerExport.AddSpacer(5)
629
sizerExport.Add(printSizer, 0, wx.LEFT | wx.ALIGN_LEFT, 5)
630
sizerExport.AddSpacer(10)
631
632
# set folding sizer on folding panel
633
subpanelExport.SetSizer(sizerExport)
634
subpanelExport.Fit()
635
636
# add folding panel to foldpanelbar
637
self.fold_panel.AddFoldPanelWindow(sectExport, subpanelExport,
638
639
fpb.FPB_ALIGN_LEFT)
self.fold_panel.AddFoldPanelSeparator(sectExport)
640
641
# Set Sizers ---------------------------------------------------
642
# make a sizer for the scrolling panel
643
scroll_sizer = wx.BoxSizer(wx.VERTICAL)
644
scroll_sizer.Add(self.fold_panel,1,wx.EXPAND)
645
646
self.panel1.SetSizer(scroll_sizer)
647
self.panel1.Layout()
648
649
# panel2 sizer set-up
650
self.graphSizer = wx.BoxSizer(wx.VERTICAL)
651
self.panel2.SetSize((800, 760))
652
self.panel2.SetSizer(self.graphSizer)
653
self.panel2.Fit()
654
self.panel2.Centre()
655
656
# panel3 set-up
657
self.scatterSizer = wx.BoxSizer(wx.VERTICAL)
91
658
self.panel3.SetSize((800, 760))
659
self.panel3.SetSizer(self.scatterSizer)
660
self.panel3.Fit()
661
self.panel3.Centre()
662
663
# panel4 sizer set-up
664
self.keySizer = wx.BoxSizer(wx.VERTICAL)
665
self.panel4.SetSize((200, 150))
666
self.panel4.SetSizer(self.keySizer)
667
self.panel4.Fit()
668
self.panel4.Centre()
669
670
# Create matplotlib Object ------------------------------------
671
672
# plot containers for panel2 heatmap
673
self.chart = wx.Panel(self.panel2)
674
self.fig = Figure(dpi=100, facecolor=’none’)
675
self.canvas = FigureCanvas(self.chart, -1, self.fig)
676
self.graphSizer.Add(self.chart, 0, wx.ALL|wx.EXPAND, 5)
677
678
# link heatmap plot containers to resize functions
679
self._SetSize()
680
self.canvas.draw()
681
self.chart._resizeflag = False
682
self.chart.Bind(wx.EVT_IDLE, self._onIdle)
683
self.chart.Bind(wx.EVT_SIZE, self._onSize)
684
685
# plot containers for panel3 heatmap
686
self.scatterChart = wx.Panel(self.panel3)
687
self.scatterFig = Figure(dpi=100, facecolor=’none’)
688
self.scatterCanvas = FigureCanvas(self.scatterChart, -1,
689
self.scatterFig)
92
690
self.scatterSizer.Add(self.scatterChart, 0, wx.ALL|wx.EXPAND, 5)
691
692
# link scatter plot containers to resize functions
693
self._SetSizeScatter()
694
self.scatterCanvas.draw()
695
self.scatterChart._resizeflag = False
696
self.scatterChart.Bind(wx.EVT_IDLE, self._onIdleScatter)
697
self.scatterChart.Bind(wx.EVT_SIZE, self._onSizeScatter)
698
699
700
# default variable values
701
self.idf = self.idfSel.GetValue()
702
self.csv = self.csvSel.GetValue()
703
self.cutNum = 0
704
self.ind = 0
705
self.axH = ’X’
706
self.axV = ’Y’
707
708
# Calculation Functions -------------------------------------------
709
710
def readIDF(self):
711
’’’Parses .idf file to obtain zone geometry’’’
712
values = []
713
valx = []
714
valy = []
715
winx = []
716
winy = []
717
winz = []
718
719
# find line numbers
720
inidf = open(self.idf,’r’)
721
textidf = inidf.readlines()
93
722
zoneLN = textidf.index(’
723
floorLN = textidf.index \
724
725
726
727
728
(’
Zone,\r\n’)
FLOOR,
!- Surface Type\r\n’)
windowLN = textidf.index \
(’
WINDOW,
!- Surface Type\r\n’)
for i,line in enumerate(textidf):
if i >= zoneLN+3 and i < zoneLN+9 :
729
strip = line.lstrip();
730
split = strip.split(’,’);
731
new = split.pop(0)
732
values.append(new)
733
734
for i,line in enumerate(textidf):
if i >= floorLN+9 and i < floorLN+13 :
735
strip = line.lstrip();
736
split = strip.split(’,’);
737
xnew = split.pop(0)
738
valx.append(xnew)
739
ynew = split.pop(0)
740
valy.append(ynew)
741
for i,line in enumerate(textidf):
742
if i >= windowLN+9 and i < windowLN+13 :
743
strip = line.lstrip();
744
a = strip.replace(’;’, ’, ’)
745
split = a.split(’,’);
746
winxnew = split.pop(0)
747
winx.append(winxnew)
748
winynew = split.pop(0)
749
winy.append(winynew)
750
winznew = split.pop(0)
751
winz.append(winznew)
752
753
# pull zone dimensions
94
754
self.xmin = float(values.pop(0))
755
self.ymin = float(values.pop(0))
756
self.zmin = float(values.pop(0))
757
self.xmax = float(max(valx))
758
self.ymax = float(max(valy))
759
self.zmax = float(values.pop(2))
760
self.winxmax = float(max(winx))
761
self.winymax = float(max(winy))
762
self.winzmax = float(max(winz))
763
self.winxmin = float(min(winx))
764
self.winymin = float(min(winy))
765
self.winzmin = float(min(winz))
766
767
# create range of gridpoints within zone dimensions
768
# sets start point to match Ecotect grid method:
769
# http://naturalfrequency.com/articles/analysisgrid
770
xspace = self.xmax/self.gpt
771
yspace = self.ymax/self.gpt
772
zspace = self.zmax/self.gpt
773
xstart = xspace/2
774
ystart = yspace/2
775
zstart = zspace/2
776
777
# number of gridpoints same in all dimensions
778
self.pt = mgrid[xstart:self.xmax:xspace,ystart:self.ymax:yspace,
779
zstart:self.zmax:zspace]
780
self.xcoords = self.pt[0,:,0,0]
781
self.ycoords = self.pt[1,0,:,0]
782
self.zcoords = self.pt[2,0,0,:]
783
784
785
def getTemps(self):
’’’Locates surface temps and other variables within .csv file’’’
95
786
self.numbers = genfromtxt(self.csv, delimiter=’,’)
787
self.dates = genfromtxt(self.csv, delimiter=’,’, usecols=(0),
788
dtype=’S100’)
789
variables = (genfromtxt(self.csv, delimiter=’,’, dtype=’S100’))[0,:]
790
location = [i for i, item in enumerate(variables) \
791
if re.search(’Surface Inside Temperature’, item)]
792
reorder=[2,3,4,0,6,5]
793
self.relocation = [location[i] for i in reorder]
794
self.loc_oDB = [i for i, item in enumerate(variables) \
795
796
797
798
799
800
if re.search(’Outdoor Dry Bulb’, item)]
self.loc_oRH = [i for i, item in enumerate(variables) \
if re.search(’Outdoor Relative Humidity’, item)]
self.loc_oAirV = [i for i, item in enumerate(variables) \
if re.search(’Zone Outdoor Wind Speed’, item)]
self.loc_zDB = [i for i, item in enumerate(variables) \
801
if re.search(’Zone Mean Air Temperature’, item)]
802
self.loc_zMRT = [i for i, item in enumerate(variables) \
803
if re.search(’Zone Mean Radiant Temperature’, item)]
804
self.loc_zOpT = [i for i, item in enumerate(variables) \
805
806
807
808
809
810
811
812
813
if re.search(’Zone Operative Temperature’, item)]
self.loc_zRH = [i for i, item in enumerate(variables) \
if re.search(’Zone Air Relative Humidity’, item)]
self.loc_zActSch = [i for i, item in enumerate(variables) \
if re.search(’ACTIVITY_SCH:Schedule Value’, item)]
self.loc_zCloSch = [i for i, item in enumerate(variables) \
if re.search(’CLOTHING_SCH:Schedule Value’, item)]
self.loc_zAirVSch = [i for i, item in enumerate(variables) \
if re.search(’AIR_VELO_SCH:Schedule Value’, item)]
814
815
def setDate_Annual(self):
816
’’’Pulls data from appropriate located columns
817
in .csv file for every hour of the year’’’
96
818
self.DBs = []
819
self.zoneDBs = []
820
self.months = []
821
self.temps = []
822
for position, item in enumerate(self.dates):
823
tem = self.numbers[position,self.relocation]
824
rep = repeat(tem,4)
825
self.temps.append(rep)
826
self.oDB = self.numbers[position,self.loc_oDB]
827
self.DBs.append(self.oDB)
828
self.zDB = self.numbers[position,self.loc_zDB]
829
self.zoneDBs.append(self.zDB)
830
self.months.append(item[0:2])
831
print shape(self.temps)
832
print ’SetDateAnnual done’
833
834
def viewFactors(self):
835
’’’Determines view factors at every point in 3D grid’’’
836
# locates each point in the space by comparing to max and min
837
xMx = self.xmax - self.pt[0,:,:,:]
838
xMn = self.pt[0,:,:,:] - self.xmin
839
yMx = self.ymax - self.pt[1,:,:,:]
840
yMn = self.pt[1,:,:,:] - self.ymin
841
zMx = self.zmax - self.pt[2,:,:,:]
842
zMn = self.pt[2,:,:,:] - self.zmin
843
844
# for vertical surfaces (walls) -------------------------------
845
# array of a/c and b/c for each surface at each point
846
acbcVert = array([
847
# north wall
848
[[ xMn/yMx, zMn/yMx],[ xMn/yMx,
849
[ xMx/yMx, zMn/yMx]],
97
zMx/yMx],[ xMx/yMx,
zMx/yMx],
850
# east wall
851
[[ yMx/xMx, zMn/xMx],[ yMx/xMx,
zMx/xMx],[ yMn/xMx,
zMx/xMx],
zMx/yMn],[ xMx/yMn,
zMx/yMn],
zMx/xMn],[ yMx/xMn,
zMx/xMn],
[ yMn/xMx, zMn/xMx]],
852
853
# south wall
854
[[ xMn/yMn, zMn/yMn],[ xMn/yMn,
[ xMx/yMn, zMn/yMn]],
855
856
# west wall
857
[[ yMn/xMn, zMn/xMn],[ yMn/xMn,
[ yMx/xMn, zMn/xMn]],
858
859
])
860
# for all surfaces, all points, a/c only
861
acV = acbcVert[:,:,0,:,:,:]
862
# for all surfaces, all points, b/c only
863
bcV = acbcVert[:,:,1,:,:,:]
864
# calculate angle factors at each point
865
tauV = 1.24186+0.16730*(acV)
866
gammaV = 0.61648+(0.08165*(bcV))+(0.05128*(acV))
867
FV = 0.120*(1-exp(-(acV)/tauV))*(1-exp(-(bcV)/gammaV))
868
869
# for horizontal surfaces (floor, ceiling) --------------------
870
# ceiling
871
acbcHori = array([
872
[[ xMn/zMx, yMn/zMx],[ xMn/zMx,
[ xMx/zMx,
873
# floor
875
[[ xMn/zMn, yMn/zMn],[ xMn/zMn,
[ xMx/zMn,
yMx/zMx],
yMx/zMn],[ xMx/zMn,
yMx/zMn],
yMn/zMx]],
874
876
yMx/zMx],[ xMx/zMx,
yMn/zMn]],
877
])
878
# for all surfaces, all points, a/c only
879
acH = acbcHori[:,:,0,:,:,:]
880
# for all surfaces, all points, b/c only
881
bcH = acbcHori[:,:,1,:,:,:]
98
882
883
# calculate angle factors at each point
884
tauH = 1.59512+0.12788*(acH)
885
gammaH = 1.22643+(0.04621*(bcH))+(0.04434*(acH))
886
FH = 0.116*(1-exp(-(acH)/tauH))*(1-exp(-(bcH)/gammaH))
887
# combine FV and FH
888
self.F = concatenate((FV,FH),axis=0)
889
890
# for reference pull angle factors and find max and min
891
SS = size(self.pt[0,:,:,:])
892
AR = reshape(self.F,(24,SS))
893
aFacs = apply_along_axis(sum, 0, AR)
894
print ’calcViewFactors done’
895
896
# Metric Annual Calculation Functions -----------------------------
897
’’’ Calculates comfort values at every point at every hour of the
898
year for every metric.
899
for user-requested times.
900
Index functions then index off of that
’’’
901
902
def calcMRTS(self):
903
print ’Running Mean Radiant Temperature Calculation...’
904
# reshape afacs
905
SX = size(self.pt[0,:,0,0])
906
SY = size(self.pt[0,0,:,0])
907
SZ = size(self.pt[0,0,0,:])
908
RF = reshape(self.F,(24,SX,SY,SZ))
909
# reformat temps
910
AT = asarray(self.temps)
911
# multiply by surface temps and sum to get MRT at each point
912
listMRTS = ([])
913
for position, item in enumerate(AT):
99
914
ZR = RF*item[:,newaxis,newaxis,newaxis]
915
MRTResults = sum(ZR, axis=0)
916
listMRTS.append(MRTResults)
917
self.MRTS_Annual = asarray(listMRTS)
918
print ’SHAPE MRTS’, shape(self.MRTS_Annual)
919
print ’Finished Mean Radiant Temperature Calculation’
920
921
def calcOpTemp(self):
922
’’’Calculates operative temperature according to method in
923
ASHRAE 55-2004’’’
924
print ’Starting Operative Temperature Calculation...’
925
collectOpTemps = []
926
for i in range(0,len(self.numbers)):
927
928
if self.numbers[i,self.loc_zAirVSch] < 0.2:
OpTemp = (0.5*(self.numbers[i,self.loc_zDB]))+ \
(1-0.5)*(self.MRTS_Annual[i])
929
930
if self.numbers[i,self.loc_zAirVSch] >= 0.2 and \
931
self.numbers[i,self.loc_zAirVSch] < 0.6:
932
OpTemp = (0.5*(self.numbers[i,self.loc_zDB]))+ \
(1-0.5)*(self.MRTS_Annual[i])
933
934
935
936
937
else:
OpTemp = (0.5*(self.numbers[i,self.loc_zDB]))+ \
(1-0.5)*(self.MRTS_Annual[i])
collectOpTemps.append(OpTemp)
938
self.OpTemp_Annual = asarray(collectOpTemps)
939
print ’Finished Operative Temperature Calculation’
940
941
942
943
def calcPMVPPDRoomAverageAnnual(self):
’’’ provides values for PMV scatter plot for every hour of the year
944
PMV spatial calc done on-demand using ’Run cMap’ button
945
from ISO 7730-2005:
100
946
M is metabolic rate [W/m2]
947
W is effective mechanical power [W/m2]
948
Icl is clothing insulation [m2K/W]
949
fcl is clothing surface factor
950
ta is air temperature [C]
951
tr is mean radiant temperature [C]
952
var is relative air velocity [m/s]
953
pa is water vapour partial pressure [Pa]
954
hc is convective heat transfer coefficient [W/m2K]
955
tcl is clothing surface temperature [C]
956
note: 1 met = 58.2 W/m2; 1 clo = 0.155 m2C/W
957
958
959
PMV may be calculated for different combinations of
960
metabolic rate, clothin insulation, air temperature,
961
mean radiant temperature, air velocity, and humidity.
962
The equations for tcl and hc may be solved by iteration.
963
964
The PMV index should be used only for values of PMV
965
between -2 and +2, and when the six main parameters are
966
within the following intervals:
967
968
0.8 met < M < 4 met (46 W/m2 to 232 W/m2)
969
0 clo < Icl < 2 clo (0 m2K/W to 0.310 m2K/W)
970
10 oC < ta < 30 oC
971
10 oC < tr < 30 oC
972
0 m/s < var < 1 m/s
973
0 Pa < Pa < 2700 Pa
974
’’’
975
self.PMV_RoomAverageAnnual = []
976
self.PPD_RoomAverageAnnual = []
977
roomAveragesMRT = []
101
978
for i in range(0,len(self.MRTS_Annual)):
979
results = mean(self.MRTS_Annual[i])
980
roomAveragesMRT.append(results)
981
print ’len roomAverages MRT’,len(roomAveragesMRT)
982
983
for position, item in enumerate(self.dates):
984
# find variable values
985
self.zDB = self.numbers[position,self.loc_zDB]
986
self.zRH = self.numbers[position,self.loc_zRH]
987
self.zActSch = self.numbers[position,self.loc_zActSch]
988
self.zCloSch = self.numbers[position,self.loc_zCloSch]
989
self.zAirVSch = self.numbers[position,self.loc_zAirVSch]
990
991
# set variables
992
ta = self.zDB
993
RH = self.zRH/100
994
var = self.zAirVSch
995
met = self.zActSch/58.15
996
clo = self.zCloSch
997
print ’set variables ok’
998
999
# auto calc variables
1000
Icl = clo*0.155
1001
M = met*58.15
1002
W = 0
1003
MW = M-W
1004
print ’auto calc variables ok’
1005
1006
# steam table data - from 2.006 Property Data Tables
1007
steamTableTemps = [
1008
0.01, 5, 10, 15, 20, 25, 30, 35,
1009
40, 45, 50, 55, 60, 65, 70, 75,
102
1010
80, 85, 90, 95, 100
1011
]
1012
steamTablePsat = [
1013
611.66, 872.58, 1228.2, 1705.8, 2339.3, 3169.9, 4247, 5629,
1014
7384.9, 9595, 12352, 15762, 19946, 25042, 31201, 38595,
1015
47414, 57867, 70182, 84608, 101420
1016
]
1017
# calculate saturation pressure at ta
1018
Psat = interp(ta, steamTableTemps, steamTablePsat)
1019
# calculate water vapor partial pressure
1020
pa = RH*Psat
1021
print ’steam tables ok’
1022
1023
# set fcl
1024
if Icl <= 0.078:
1025
1026
fcl = 1.00 + 1.290*Icl
else:
1027
fcl = 1.05 + 0.645*Icl
1028
print ’set fcl ok’
1029
1030
def findTcl(tcl):
1031
g = 2.38*abs(tcl-ta)**0.25
1032
h = 12.1*sqrt(var)
1033
hc = min(g,h)
1034
b = 35.7-0.028*MW-Icl*(3.96*(10**-8)*fcl*(((tcl+273)**4)- \
1035
1036
((mrt+273)**4))+fcl*hc*(tcl-ta))-tcl
return b
1037
1038
# determine Tcl from bisection search (brentq method)
1039
mrt = roomAveragesMRT[position]
1040
Tcl = brentq(findTcl, 0, 100)
1041
g = 2.38*abs(Tcl-ta)**0.25
103
1042
h = 12.1*sqrt(var)
1043
hc = max(g,h)
1044
1045
# heat loss components by parts --------
1046
1047
# heat loss diff through skin
1048
HL1 = 3.05*0.001*(5733-6.99*MW-pa)
1049
# heat loss by sweating
1050
if MW > 58.15:
1051
1052
1053
HL2 = 0.42*(MW-58.15)
else:
HL2 = 0
1054
# latent respiration heat loss
1055
HL3 = 1.7*0.00001*M*(5867-pa)
1056
# dry respiration heat loss
1057
HL4 = 0.0014*M*(34-ta)
1058
# heat loss by radiation
1059
HL5 = 3.96*0.00000001*fcl*(((Tcl+273)**4)-((mrt+273)**4))
1060
# heat loss by convection
1061
HL6 = fcl*hc*(Tcl-ta)
1062
# thermal sensation trans coeff
1063
TS = 0.303*exp(-0.036*M)+0.028
1064
# calc PMV
1065
PMV = TS*(MW-HL1-HL2-HL3-HL4-HL5-HL6)
1066
self.PMV_RoomAverageAnnual.append(PMV)
1067
PPD = 100-95*exp(-0.03353*PMV**4-0.2179*PMV**2)
1068
self.PPD_RoomAverageAnnual.append(PPD)
1069
print ’shape PMVavg’, shape(self.PMV_RoomAverageAnnual)
1070
print ’calc PMV_RoomAverageAnnual ok’
1071
1072
1073
def calcAdaptiveASHRAE55(self):
print ’Starting ASHRAE 55 Adaptive Temperature Calculation...’
104
1074
# pull outdoor dry bulb temps and month names from CSV
1075
csvOutdoorDryBulbs=[]
1076
csvMonths=[]
1077
for i in range(0,len(self.dates)):
1078
oDB = self.numbers[i,self.loc_oDB]
1079
csvOutdoorDryBulbs.append(oDB)
1080
csvMonths.append(self.dates[i][0:2])
1081
print "len csvOutdoorDryBulb", len(csvOutdoorDryBulbs)
1082
print "len csvMonths", len(csvMonths)
1083
1084
# chunk data into months and days to find the monthly average
1085
# of the daily averages
1086
u = unique(csvMonths)
1087
chunks = []
1088
for i in range(0,len(u)-1):
1089
itemindex = csvMonths.index(u[i])
1090
print "itemindex", itemindex
1091
chunks.append(itemindex)
1092
chunks.append(len(csvMonths))
1093
meanMonthlyOutdoorDryBulbs = []
1094
for i in range(0,len(chunks)-1):
1095
x = (csvOutdoorDryBulbs[chunks[i]:chunks[i+1]])
1096
days = zip(*(iter(x),) * 24)
1097
dailyAverages = []
1098
for k in days:
1099
1100
dailyAverages.append(mean(k))
meanMonthlyOutdoorDryBulbs.append(mean(dailyAverages))
1101
1102
# replace months with corresponding average dry bulb
1103
dictionary = dict(zip(u, meanMonthlyOutdoorDryBulbs))
1104
self.replacedODBforMean = [dictionary.get(x,x) for x in csvMonths]
1105
self.replacedODBforMean = asarray(self.replacedODBforMean[1:],
105
1106
dtype=np.float)
1107
print "len replacedODBforMean", len(self.replacedODBforMean)
1108
dummyRow = np.array([0])
1109
self.concatReplacedODBforMean = \
1110
concatenate((dummyRow,self.replacedODBforMean))
1111
print self.concatReplacedODBforMean
1112
print "len concatenate", len(self.concatReplacedODBforMean)
1113
1114
# calculate Tcomf
1115
Tcomfs = []
1116
for i in range(0,len(self.concatReplacedODBforMean)):
1117
1118
1119
1120
1121
1122
if self.concatReplacedODBforMean[i] < 10:
Tcomf = 22
else:
Tcomf = 0.31*self.concatReplacedODBforMean[i]+17.8
Tcomfs.append(Tcomf)
print "len Tcomfs", len(Tcomfs)
1123
1124
# find deltaT
1125
collectDeltaT = []
1126
for i in range(0, len(Tcomfs)):
1127
deltaT = subtract(self.OpTemp_Annual[i],Tcomfs[i])
1128
collectDeltaT.append(deltaT)
1129
self.AdaptiveASHRAE55_Annual = asarray(collectDeltaT)
1130
print ’Finished ASHRAE 55 Adaptive Temperature Calculation’
1131
1132
1133
def calcAdaptiveEN15251(self):
1134
print ’Starting EN15251 Adaptive Temperature Calculation...’
1135
# chunk outdoor dry bulb temps from CSV into 24 hour blocks
1136
x = self.numbers[1:,self.loc_oDB]
1137
print len(x)
106
1138
print x[0]
1139
days = zip(*(iter(x),) * 24)
1140
print len(days)
1141
print days[0]
1142
print len(days[0])
1143
# for each day, find average temps
1144
dailyAverages = []
1145
for i in days:
1146
dailyAverages.append(mean(i))
1147
print len(dailyAverages)
1148
print "DA zero",dailyAverages[0]
1149
# perform weighted running mean every 7 days
1150
collectTrm7 = []
1151
for i in range(0,len(dailyAverages)):
1152
T1m = dailyAverages[i-1]
1153
T2m = dailyAverages[i-2]
1154
T3m = dailyAverages[i-3]
1155
T4m = dailyAverages[i-4]
1156
T5m = dailyAverages[i-5]
1157
T6m = dailyAverages[i-6]
1158
T7m = dailyAverages[i-7]
1159
Trm7 = (T1m + 0.8*T2m + 0.6*T3m + 0.5*T4m + 0.4*T5m + \
1160
1161
0.3*T6m + 0.2*T7m)/3.8
collectTrm7.append(Trm7)
1162
self.Trm7s = repeat(collectTrm7, 24)
1163
print "LEN self.Trm7s", len(self.Trm7s)
1164
# calculate Tcomf
1165
Tcomfs = []
1166
for i in range(0,len(collectTrm7)):
1167
1168
1169
if collectTrm7[i] < 10:
Tcomf = 22
else:
107
1170
1171
Tcomf = 0.33*collectTrm7[i]+18.8
Tcomfs.append(Tcomf)
1172
print "len OLD Tcomfs", len(Tcomfs)
1173
# repeat 24 times to make it the same length as the OpTemps
1174
Tcomfs = repeat(Tcomfs, 24)
1175
print "len NEW Tcomfs", len(Tcomfs)
1176
dummyRow = array([0])
1177
Tcomfs = concatenate((dummyRow,Tcomfs))
1178
print "len NEW CONCAT Tcomfs", len(Tcomfs)
1179
1180
# find deltaT
1181
collectDeltaT = []
1182
for i in range(0, len(Tcomfs)):
1183
deltaT = subtract(self.OpTemp_Annual[i],Tcomfs[i])
1184
collectDeltaT.append(deltaT)
1185
print ’Shape collect Delta T’, shape(collectDeltaT)
1186
self.AdaptiveEN15251_Annual = asarray(collectDeltaT)
1187
print ’Finished EN15251 Adaptive Temperature Calculation’
1188
1189
1190
def calcAdaptiveNPRCR1752Beta(self):
1191
print ’Starting NPR-CR 1752 Adaptive Temperature Calculation...’
1192
# chunk outdoor dry bulb temps from CSV into 24 hour blocks
1193
x = self.numbers[1:,self.loc_oDB]
1194
print len(x)
1195
print x[0]
1196
days = zip(*(iter(x),) * 24)
1197
print len(days)
1198
print days[0]
1199
print len(days[0])
1200
# for each day, find average temps
1201
dailyAverageofMaxMin = []
108
1202
for i in days:
1203
mx = max(i)
1204
mn = min(i)
1205
dailyAverageofMaxMin.append(mean([mx, mn]))
1206
print len(dailyAverageofMaxMin)
1207
print "DA zero",dailyAverageofMaxMin[0]
1208
# perform weighted running mean every 7 days
1209
collectTrm3 = []
1210
for i in range(0,len(dailyAverageofMaxMin)):
1211
T1m = dailyAverageofMaxMin[i]
1212
T2m = dailyAverageofMaxMin[i-1]
1213
T3m = dailyAverageofMaxMin[i-2]
1214
T4m = dailyAverageofMaxMin[i-3]
1215
Trm3 = (T1m + 0.8*T2m + 0.4*T3m + 0.2*T4m)/2.4
1216
collectTrm3.append(Trm3)
1217
self.Trm3s = repeat(collectTrm3, 24)
1218
print "LEN self.Trm3s", len(self.Trm3s)
1219
# calculate Tcomf
1220
Tcomfs = []
1221
for i in range(0,len(collectTrm3)):
1222
1223
1224
1225
1226
if collectTrm3[i] < 10:
Tcomf = 22
else:
Tcomf = 0.31*collectTrm3[i]+17.8
Tcomfs.append(Tcomf)
1227
print "len OLD Tcomfs", len(Tcomfs)
1228
# repeat 24 times to make it the same length as the OpTemps
1229
Tcomfs = repeat(Tcomfs, 24)
1230
print "len NEW Tcomfs", len(Tcomfs)
1231
dummyRow = array([0])
1232
Tcomfs = concatenate((dummyRow,Tcomfs))
1233
print "len NEW CONCAT Tcomfs", len(Tcomfs)
109
1234
1235
# find deltaT
1236
collectDeltaT = []
1237
for i in range(0, len(Tcomfs)):
1238
deltaT = subtract(self.OpTemp_Annual[i],Tcomfs[i])
1239
collectDeltaT.append(deltaT)
1240
print ’Shape collect Delta T’, shape(collectDeltaT)
1241
self.AdaptiveNPRCR1752Beta_Annual = asarray(collectDeltaT)
1242
print ’Finished NPR-CR 1752 Adaptive Temperature Calculation’
1243
1244
# Metric Indexing & Display Calculation Functions -----------------
1245
1246
# indexing functions for single point in time ---------------------
1247
def setDate_Point(self):
1248
self.StartMonth = ’%02d’ % (int(self.StartMonthSpin.GetValue()))
1249
self.StartDay = ’%02d’ % (int(self.StartDaySpin.GetValue()))
1250
self.StartHour = ’%02d’ % (int(self.StartHourSpin.GetValue()))
1251
i = self.calcDrop.GetSelection()
1252
# note that for PMV and PPD, currently reference off MRTS
1253
calctype = [ self.MRTS_Annual,
1254
self.OpTemp_Annual,
1255
self.MRTS_Annual,
1256
self.MRTS_Annual,
1257
self.AdaptiveASHRAE55_Annual,
1258
self.AdaptiveEN15251_Annual,
1259
self.AdaptiveNPRCR1752Beta_Annual
1260
]
1261
calcselection = calctype[i]
1262
calcselectionOpTemps = calctype[1]
1263
self.outdoorDB = []
1264
for position, item in enumerate(self.dates):
1265
if (item[0:2] == self.StartMonth) and \
110
1266
(item[3:5] == self.StartDay) and \
1267
(item[7:9] == self.StartHour) :
1268
self.position = position
1269
print self.position
1270
self.calcSelection_Point = calcselection[position,:,:,:]
1271
self.calcSelection_Point_OpTemps = calcselectionOpTemps \
1272
[position,:,:,:]
1273
# comfort parameters
1274
self.oDB = self.numbers[position,self.loc_oDB]
1275
self.outdoorDB.append(self.oDB)
1276
self.oRH = self.numbers[position,self.loc_oRH]
1277
self.oAirV = self.numbers[position,self.loc_oAirV]
1278
self.zDB = self.numbers[position,self.loc_zDB]
1279
self.zMRT = self.numbers[position,self.loc_zMRT]
1280
self.zOpT = self.numbers[position,self.loc_zOpT]
1281
self.zRH = self.numbers[position,self.loc_zRH]
1282
self.zActSch = self.numbers[position,self.loc_zActSch]
1283
self.zCloSch = self.numbers[position,self.loc_zCloSch]
1284
self.zAirVSch = self.numbers[position,self.loc_zAirVSch]
1285
self.monthlyOutdoorMean = \
1286
self.concatReplacedODBforMean[position]
1287
self.runningMean7day = self.Trm7s[position]
1288
self.runningMean3day = self.Trm3s[position]
1289
print ’SetDate_Point done’
1290
1291
def indexMRTS_Point(self):
1292
# call indexing function
1293
self.setDate_Point()
1294
# define heatmap and scatter results
1295
self.heatmapCalcResults = self.calcSelection_Point
1296
self.scatterCalcResults = mean(self.calcSelection_Point)
1297
# display settings
111
1298
self.format = ’%2.1f’
1299
self.xlabel = ’outdoor dry bulb temperature [C]’
1300
self.ylabel = ’indoor mean radiant temperature [C]’
1301
self.scattertitle = "Mean Radiant Temperature vs Outdoor Dry Bulb"
1302
self.scatterXlim = [-10,35]
1303
self.scatterYlim = [16,32]
1304
1305
def indexOpTemp_Point(self):
1306
# call indexing function
1307
self.setDate_Point()
1308
# define heatmap and scatter results
1309
self.heatmapCalcResults = self.calcSelection_Point
1310
self.scatterCalcResults = mean(self.calcSelection_Point)
1311
# display settings
1312
self.format = ’%2.1f’
1313
self.xlabel = ’outdoor dry bulb temperature [C]’
1314
self.ylabel = ’indoor operative temperature [C]’
1315
self.scattertitle = "Operative Temperature vs Outdoor Dry Bulb"
1316
self.scatterXlim = [-10,35]
1317
self.scatterYlim = [16,32]
1318
1319
1320
def calcPMV_Point(self):
’’’provides values for PMV heatmap for a single point
in time
1321
1322
’’’
1323
self.setDate_Point()
1324
self.scatterCalcResultsPMV = self.PMV_RoomAverageAnnual[self.position]
1325
self.scatterCalcResultsPPD = self.PPD_RoomAverageAnnual[self.position]
1326
1327
# set variables
1328
ta = self.zDB
1329
RH = self.zRH/100
112
1330
var = self.zAirVSch
1331
met = self.zActSch/58.15
1332
clo = self.zCloSch
1333
1334
# auto calc variables
1335
Icl = clo*0.155
1336
M = met*58.15
1337
W = 0
1338
MW = M-W
1339
1340
# steam table data - from 2.006 Property Data Tables
1341
steamTableTemps = [
1342
0.01, 5, 10, 15, 20, 25, 30, 35,
1343
40, 45, 50, 55, 60, 65, 70, 75,
1344
80, 85, 90, 95, 100
1345
]
1346
steamTablePsat = [
1347
611.66, 872.58, 1228.2, 1705.8, 2339.3, 3169.9, 4247, 5629,
1348
7384.9, 9595, 12352, 15762, 19946, 25042, 31201, 38595,
1349
47414, 57867, 70182, 84608, 101420
1350
]
1351
# calculate saturation pressure at ta
1352
Psat = interp(ta, steamTableTemps, steamTablePsat)
1353
# calculate water vapor partial pressure
1354
pa = RH*Psat
1355
1356
# set fcl
1357
if Icl <= 0.078:
1358
1359
1360
fcl = 1.00 + 1.290*Icl
else:
fcl = 1.05 + 0.645*Icl
1361
113
1362
def findTcl(tcl):
1363
g = 2.38*abs(tcl-ta)**0.25
1364
h = 12.1*sqrt(var)
1365
hc = min(g,h)
1366
b = 35.7-0.028*MW-Icl*(3.96*(10**-8)*fcl*(((tcl+273)**4)- \
1367
1368
((i+273)**4))+fcl*hc*(tcl-ta))-tcl
return b
1369
1370
# determine Tcl
1371
collectTcl = []
1372
collectHC = []
1373
for i in ravel(self.calcSelection_Point):
1374
Tcl = brentq(findTcl, 0, 100)
1375
collectTcl.append(Tcl)
1376
g = 2.38*abs(Tcl-ta)**0.25
1377
h = 12.1*sqrt(var)
1378
hc = max(g,h)
1379
collectHC.append(hc)
1380
1381
# heat loss components by parts ------------------------------
1382
1383
self.collectPMV = []
1384
self.collectPPD = []
1385
for i in range(0,len(ravel(self.calcSelection_Point))):
1386
tr = (ravel(self.calcSelection_Point))[i]
1387
Tcl = collectTcl[i]
1388
hc = collectHC[i]
1389
1390
# heat loss diff through skin
1391
HL1 = 3.05*0.001*(5733-6.99*MW-pa)
1392
# heat loss by sweating
1393
if MW > 58.15:
114
1394
1395
1396
HL2 = 0.42*(MW-58.15)
else:
HL2 = 0
1397
# latent respiration heat loss
1398
HL3 = 1.7*0.00001*M*(5867-pa)
1399
# dry respiration heat loss
1400
HL4 = 0.0014*M*(34-ta)
1401
# heat loss by radiation
1402
HL5 = 3.96*0.00000001*fcl*(((Tcl+273)**4)-((tr+273)**4))
1403
# heat loss by convection
1404
HL6 = fcl*hc*(Tcl-ta)
1405
# thermal sensation trans coeff
1406
TS = 0.303*exp(-0.036*M)+0.028
1407
# calc PMV
1408
PMV = TS*(MW-HL1-HL2-HL3-HL4-HL5-HL6)
1409
self.collectPMV.append(PMV)
1410
PPD = 100-95*exp(-0.03353*PMV**4-0.2179*PMV**2)
1411
self.collectPPD.append(PPD)
1412
1413
1414
self.heatmapCalcResults = reshape(self.collectPMV,
shape(self.calcSelection_Point))
1415
1416
# display items
1417
r = around(self.heatmapCalcResults, decimals=2)
1418
u = unique(r)
1419
self.Tickvalues = u
1420
self.Tickvalues = [-3.0, -2.0, -1.0, -0.5, 0, 0.5, 1.0, 2.0, 3.0]
1421
self.Ticklabels = [’-3’, ’-2’, ’-1’, ’-0.5’, ’0’, ’+0.5’,
1422
’+1’,’+2’,’+3’]
1423
self.Levels = len(u)
1424
self.format = ’%2.2f’
1425
self.scattertitle = \
115
"ASHRAE Standard 55 PPD as a function of PMV - Room Average"
1426
1427
self.xlabel = ’predicted mean vote (PMV)’
1428
self.ylabel = ’predicted percentage of dissatisfied (PPD)’
1429
self.scatterXlim = [-3,3]
1430
self.scatterYlim = [0,100]
1431
1432
1433
1434
def calcPPD_Point(self):
’’’provides values for PPD heatmap for a single point
in time
1435
’’’
1436
self.setDate_Point()
1437
self.calcPMV_Point()
1438
self.scatterCalcResultsPMV = \
1439
1440
1441
1442
1443
self.PMV_RoomAverageAnnual[self.position]
self.scatterCalcResultsPPD = \
self.PPD_RoomAverageAnnual[self.position]
self.heatmapCalcResults = \
reshape(self.collectPPD,shape(self.calcSelection_Point))
1444
1445
# display items
1446
r = around(self.heatmapCalcResults, decimals=2)
1447
u = unique(r)
1448
self.Tickvalues = u
1449
self.Tickvalues = [0, 5, 10, 15, 20, 50]
1450
self.Ticklabels = [’0%’, ’5%’, ’10%’, ’15%’, ’20%’, ’>20%’]
1451
self.Levels = len(u)
1452
self.format = ’%2.2f’
1453
self.scattertitle = \
1454
"ASHRAE Standard 55 PPD as a function of PMV - Room Average"
1455
self.xlabel = ’predicted mean vote (PMV)’
1456
self.ylabel = ’predicted percentage of dissatisfied (PPD)’
1457
self.scatterXlim = [-3,3]
116
1458
self.scatterYlim = [0,100]
1459
1460
def indexASHRAE55Adaptive_Point(self):
1461
# call indexing function
1462
self.setDate_Point()
1463
# define heatmap and scatter results
1464
self.heatmapCalcResults = self.calcSelection_Point
1465
self.scatterCalcResults = mean(self.calcSelection_Point_OpTemps)
1466
# display settings
1467
r = around(self.heatmapCalcResults, decimals=1)
1468
u = unique(r)
1469
self.Tickvalues = [-10, -3.5, -2.5, 2.5, 3.5, 10]
1470
self.Ticklabels = [
1471
’<80% acceptability’,
1472
’80% acceptability \n $\Delta$ T -3.5 [C]’,
1473
’90% acceptability \n $\Delta$ T -2.5 [C]’,
1474
’90% acceptability \n $\Delta$ T +2.5 [C]’,
1475
’80% acceptability \n $\Delta$ T +3.5 [C]’,
1476
’< 80% acceptability’]
1477
self.Levels = len(u)
1478
self.format = ’%2.1f’
1479
# scatter plot lines
1480
self.scattertitle = \
1481
"ASHRAE Standard 55 Adaptive Comfort - Room Average"
1482
self.xlabel = ’mean monthly outdoor temperature [C]’
1483
self.ylabel = ’indoor operative temperature [C]’
1484
self.scatterXlim = [5,35]
1485
self.scatterYlim = [16,32]
1486
# scatter plot boundary lines
1487
self.outdoor = arange(10,33)
1488
self.comfortASHRAE = (0.31*self.outdoor+17.8)
1489
self.lower80ASHRAE = (0.31*self.outdoor+17.8)-3.5
117
1490
self.lower90ASHRAE = (0.31*self.outdoor+17.8)-2.5
1491
self.upper90ASHRAE = (0.31*self.outdoor+17.8)+2.5
1492
self.upper80ASHRAE = (0.31*self.outdoor+17.8)+3.5
1493
1494
def indexEN15251Adaptive_Point(self):
1495
# call indexing function
1496
self.setDate_Point()
1497
# define heatmap and scatter results
1498
self.heatmapCalcResults = self.calcSelection_Point
1499
self.scatterCalcResults = mean(self.calcSelection_Point_OpTemps)
1500
# display settings
1501
r = around(self.heatmapCalcResults, decimals=1)
1502
u = unique(r)
1503
self.Tickvalues = [-10, -3.0, -2.0, 2.0, 3.0, 10]
1504
self.Ticklabels = [
1505
’<80% acceptability’,
1506
’80% acceptability \n $\Delta$ T -3.0 [C]’,
1507
’90% acceptability \n $\Delta$ T -2.0 [C]’,
1508
’90% acceptability \n $\Delta$ T +2.0 [C]’,
1509
’80% acceptability \n $\Delta$ T +3.0 [C]’,
1510
’< 80% acceptability’]
1511
self.Levels = len(u)
1512
self.format = ’%2.1f’
1513
# scatter plot lines
1514
self.scattertitle = \
1515
"European Standard EN 15251 Adaptive Comfort - Room Average"
1516
self.xlabel = ’7-day running mean outdoor temperature [C]’
1517
self.ylabel = ’indoor operative temperature [C]’
1518
self.scatterXlim = [5,35]
1519
self.scatterYlim = [16,32]
1520
# scatter plot boundary lines
1521
self.outdoor = arange(10,33)
118
1522
self.comfortEN15251 = (0.33*self.outdoor+18.8)
1523
self.lower80EN15251 = (0.33*self.outdoor+18.8)-3.0
1524
self.lower90EN15251 = (0.33*self.outdoor+18.8)-2.0
1525
self.upper90EN15251 = (0.33*self.outdoor+18.8)+2.0
1526
self.upper80EN15251 = (0.33*self.outdoor+18.8)+3.0
1527
1528
def indexNPRCR1752BetaAdaptive_Point(self):
1529
# call indexing function
1530
self.setDate_Point()
1531
# define heatmap and scatter results
1532
self.heatmapCalcResults = self.calcSelection_Point
1533
self.scatterCalcResults = mean(self.calcSelection_Point_OpTemps)
1534
# display settings
1535
r = around(self.heatmapCalcResults, decimals=1)
1536
u = unique(r)
1537
self.Tickvalues = [-10, -3.0, -2.0, 2.0, 3.0, 10]
1538
self.Ticklabels = [
1539
’<80% acceptability’,
1540
’80% acceptability \n $\Delta$ T -3.0 [C]’,
1541
’90% acceptability \n $\Delta$ T -2.0 [C]’,
1542
’90% acceptability \n $\Delta$ T +2.0 [C]’,
1543
’80% acceptability \n $\Delta$ T +3.0 [C]’,
1544
’< 80% acceptability’]
1545
self.Levels = len(u)
1546
self.format = ’%2.1f’
1547
# scatter plot lines
1548
self.scattertitle = \
1549
"Dutch Standard NPR-CR 1752 Type Beta Adaptive Comfort"
1550
self.xlabel = ’3-day running mean outdoor temperature [C]’
1551
self.ylabel = ’indoor operative temperature [C]’
1552
self.scatterXlim = [5,35]
1553
self.scatterYlim = [16,32]
119
1554
# scatter plot boundary lines
1555
self.outdoor = arange(10,33)
1556
self.comfortNPRCR1752 = (0.31*self.outdoor+17.8)
1557
self.lower80NPRCR1752 = (0.31*self.outdoor+17.8)-3.0
1558
self.lower90NPRCR1752 = (0.31*self.outdoor+17.8)-2.0
1559
self.upper90NPRCR1752 = (0.31*self.outdoor+17.8)+2.0
1560
self.upper80NPRCR1752 = (0.31*self.outdoor+17.8)+3.0
1561
1562
1563
# indexing functions for date/time range --------------------------
1564
def setDate_Range(self):
1565
self.StartMonth = ’%02d’ % (int(self.StartMonthSpin.GetValue()))
1566
self.StartDay = ’%02d’ % (int(self.StartDaySpin.GetValue()))
1567
self.StartHour = ’%02d’ % (int(self.StartHourSpin.GetValue()))
1568
self.EndMonth = ’%02d’ % (int(self.EndMonthSpin.GetValue()))
1569
self.EndDay = ’%02d’ % (int(self.EndDaySpin.GetValue()))
1570
self.EndHour = ’%02d’ % (int(self.EndHourSpin.GetValue()))
1571
i = self.calcDrop.GetSelection()
1572
calctype = [ self.MRTS_Annual,
1573
self.OpTemp_Annual,
1574
self.MRTS_Annual,
1575
self.MRTS_Annual,
1576
self.AdaptiveASHRAE55_Annual,
1577
self.AdaptiveEN15251_Annual,
1578
self.AdaptiveNPRCR1752Beta_Annual
1579
]
1580
calcselection = calctype[i]
1581
calcselectionOpTemps = calctype[1]
1582
print ’CALCSELECTION’, calcselection
1583
self.calcSelection_Range = []
1584
self.calcSelection_Range_OpTemps = []
1585
self.monthlyOutdoorMean = []
120
1586
##
1587
self.positions = []
1588
self.Hr = []
1589
self.Mn = []
1590
self.outdoorDB = []
1591
self.zoneDB = []
1592
self.zoneRH = []
1593
self.zoneActSch = []
1594
self.zoneCloSch = []
1595
self.zoneAirVSch = []
1596
self.months = []
1597
self.runningMean7day = []
1598
self.runningMean3day = []
1599
for position, item in enumerate(self.dates):
1600
1601
if (item[0:2] >= self.StartMonth) and \
(item[0:2] <= self.EndMonth) and \
1602
(item[3:5] >= self.StartDay) and \
1603
(item[3:5] <= self.EndDay) and \
1604
(item[7:9] >= self.StartHour) and \
1605
(item[7:9] <= self.EndHour) :
1606
self.Hr.append(int(item[7:9]))
1607
self.Mn.append(int(item[0:2]))
1608
self.positions.append(position)
1609
self.calcSelection_Point = calcselection[position,:,:,:]
1610
self.calcSelection_Range.append(self.calcSelection_Point)
1611
self.calcSelection_Point_OpTemps = \
1612
1613
1614
calcselectionOpTemps[position,:,:,:]
self.calcSelection_Range_OpTemps.append \
(self.calcSelection_Point_OpTemps)
1615
monthlyOutdoorMean = self.concatReplacedODBforMean[position]
1616
self.monthlyOutdoorMean.append(monthlyOutdoorMean)
1617
self.runningMean7day.append(self.Trm7s[position])
121
1618
self.runningMean3day.append(self.Trm3s[position])
1619
# pull parameters
1620
self.oDB = self.numbers[position,self.loc_oDB]
1621
self.outdoorDB.append(self.oDB)
1622
self.zDB = self.numbers[position,self.loc_zDB]
1623
self.zoneDB.append(self.zDB)
1624
self.zRH = self.numbers[position,self.loc_zRH]
1625
self.zoneRH.append(self.zRH)
1626
self.zActSch = self.numbers[position,self.loc_zActSch]
1627
self.zoneActSch.append(self.zActSch)
1628
self.zCloSch = self.numbers[position,self.loc_zCloSch]
1629
self.zoneCloSch.append(self.zCloSch)
1630
self.zAirVSch = self.numbers[position,self.loc_zAirVSch]
1631
self.zoneAirVSch.append(self.zAirVSch)
1632
self.months.append(item[0:2])
1633
print ’SetDate_Range done’
1634
# bin data for scatters
1635
self.binStart = (int(self.colorStart.GetValue()))
1636
self.binEnd = (int(self.colorEnd.GetValue()))
1637
MBins = array([0,self.binStart,self.binEnd,13])
1638
HBins = array([0,self.binStart,self.binEnd,25])
1639
self.MonthBins = digitize(self.Mn, MBins)
1640
self.HourBins = digitize(self.Hr, HBins)
1641
1642
def scatterResults(self):
1643
’’’Bin scatter results into user-specified periods
1644
’’’
1645
self.scatterCalcResults = []
1646
# for months
1647
self.scatterCalcResultsMonthBin1 = []
1648
self.MonthBin1 = []
1649
self.scatterCalcResultsMonthBin2 = []
122
1650
self.MonthBin2 = []
1651
self.scatterCalcResultsMonthBin3 = []
1652
self.MonthBin3 = []
1653
# for hours
1654
self.scatterCalcResultsHourBin1 = []
1655
self.HourBin1 = []
1656
self.scatterCalcResultsHourBin2 = []
1657
self.HourBin2 = []
1658
self.scatterCalcResultsHourBin3 = []
1659
self.HourBin3 = []
1660
i = self.calcDrop.GetSelection()
1661
if i >= 0 and i <= 1:
1662
variable1 = self.calcSelection_Range
1663
variable2 = self.outdoorDB
1664
if i == 4:
1665
variable1 = self.calcSelection_Range_OpTemps
1666
variable2 = self.monthlyOutdoorMean
1667
if i == 5:
1668
variable1 = self.calcSelection_Range_OpTemps
1669
variable2 = self.runningMean7day
1670
if i == 6:
1671
variable1 = self.calcSelection_Range_OpTemps
1672
variable2 = self.runningMean3day
1673
for i in range(0,len(variable1)):
1674
results = mean(variable1[i])
1675
self.scatterCalcResults.append(results)
1676
# for months
1677
if self.MonthBins[i]==1:
1678
self.scatterCalcResultsMonthBin1.append(results)
1679
self.MonthBin1.append(variable2[i])
1680
1681
if self.MonthBins[i]==2:
self.scatterCalcResultsMonthBin2.append(results)
123
1682
1683
self.MonthBin2.append(variable2[i])
if self.MonthBins[i]==3:
1684
self.scatterCalcResultsMonthBin3.append(results)
1685
self.MonthBin3.append(variable2[i])
1686
# for hours
1687
if self.HourBins[i]==1:
1688
self.scatterCalcResultsHourBin1.append(results)
1689
self.HourBin1.append(variable2[i])
1690
if self.HourBins[i]==2:
1691
self.scatterCalcResultsHourBin2.append(results)
1692
self.HourBin2.append(variable2[i])
1693
if self.HourBins[i]==3:
1694
self.scatterCalcResultsHourBin3.append(results)
1695
self.HourBin3.append(variable2[i])
1696
1697
def indexMRTS_Range(self):
1698
# call indexing function
1699
self.setDate_Range()
1700
# define heatmap and scatter results
1701
self.heatmapCalcResults = mean(self.calcSelection_Range, axis=0)
1702
self.scatterResults()
1703
# display settings
1704
self.format = ’%2.1f’
1705
self.xlabel = ’outdoor dry bulb temperature [C]’
1706
self.ylabel = ’indoor mean radiant temperature [C]’
1707
self.scattertitle = "Mean Radiant Temperature vs Outdoor Dry Bulb"
1708
self.scatterXlim = [-10,35]
1709
self.scatterYlim = [16,32]
1710
1711
def indexOpTemp_Range(self):
1712
# call indexing function
1713
self.setDate_Range()
124
1714
# define heatmap and scatter results
1715
self.heatmapCalcResults = mean(self.calcSelection_Range, axis=0)
1716
self.scatterResults()
1717
# display settings
1718
self.format = ’%2.1f’
1719
self.format = ’%2.1f’
1720
self.xlabel = ’outdoor dry bulb temperature [C]’
1721
self.ylabel = ’indoor operative temperature [C]’
1722
self.scattertitle = "Operative Temperature vs Outdoor Dry Bulb"
1723
self.scatterXlim = [-10,35]
1724
self.scatterYlim = [16,32]
1725
1726
1727
def calcPMV_Range(self):
’’’provides values for PMV heatmap over a range of time
1728
note that this calculation can take a very long time
1729
because of the bisection search
1730
’’’
1731
self.setDate_Range()
1732
1733
1734
1735
1736
self.scatterCalcResultsPMV = \
self.PMV_RoomAverageAnnual[self.positions[0]:self.positions[-1]]
self.scatterCalcResultsPPD = \
self.PPD_RoomAverageAnnual[self.positions[0]:self.positions[-1]]
1737
1738
self.collectPMVarrays = []
1739
self.collectPPDarrays = []
1740
for position, item in enumerate(self.positions):
1741
# set variables
1742
ta = self.zoneDB[position]
1743
RH = self.zoneRH[position]/100
1744
var = self.zoneAirVSch[position]
1745
met = self.zoneActSch[position]/58.15
125
1746
clo = self.zoneCloSch[position]
1747
1748
# auto calc variables
1749
Icl = clo*0.155
1750
M = met*58.15
1751
W = 0
1752
MW = M-W
1753
print ’set variables ok’
1754
1755
# steam table data - from 2.006 Property Data Tables
1756
steamTableTemps = [
1757
0.01, 5, 10, 15, 20, 25, 30, 35,
1758
40, 45, 50, 55, 60, 65, 70, 75,
1759
80, 85, 90, 95, 100
1760
]
1761
steamTablePsat = [
1762
611.66, 872.58, 1228.2, 1705.8, 2339.3, 3169.9, 4247, 5629,
1763
7384.9, 9595, 12352, 15762, 19946, 25042, 31201, 38595,
1764
47414, 57867, 70182, 84608, 101420
1765
]
1766
# calculate saturation pressure at ta
1767
Psat = interp(ta, steamTableTemps, steamTablePsat)
1768
# calculate water vapor partial pressure
1769
pa = RH*Psat
1770
print ’set steam tables ok’
1771
1772
# set fcl
1773
if Icl <= 0.078:
1774
1775
1776
1777
fcl = 1.00 + 1.290*Icl
else:
fcl = 1.05 + 0.645*Icl
print ’set fcl ok’
126
1778
1779
def findTcl(tcl):
1780
g = 2.38*abs(tcl-ta)**0.25
1781
h = 12.1*sqrt(var)
1782
hc = min(g,h)
1783
b = 35.7-0.028*MW-Icl*(3.96*(10**-8)*fcl*(((tcl+273)**4)- \
1784
1785
((i+273)**4))+fcl*hc*(tcl-ta))-tcl
return b
1786
1787
# determine Tcl by bisection search (brentq method)
1788
collectTcl = []
1789
collectHC = []
1790
for i in ravel(self.calcSelection_Range[position]):
1791
Tcl = brentq(findTcl, 0, 100)
1792
collectTcl.append(Tcl)
1793
g = 2.38*abs(Tcl-ta)**0.25
1794
h = 12.1*sqrt(var)
1795
hc = max(g,h)
1796
collectHC.append(hc)
1797
1798
# heat loss components by parts ---------------------------
1799
1800
self.collectPMV = []
1801
self.collectPPD = []
1802
for i in range(0,len(ravel(self.calcSelection_Range[position]))):
1803
tr = (ravel(self.calcSelection_Range))[i]
1804
Tcl = collectTcl[i]
1805
hc = collectHC[i]
1806
1807
# heat loss diff through skin
1808
HL1 = 3.05*0.001*(5733-6.99*MW-pa)
1809
# heat loss by sweating
127
1810
1811
1812
1813
if MW > 58.15:
HL2 = 0.42*(MW-58.15)
else:
HL2 = 0
1814
# latent respiration heat loss
1815
HL3 = 1.7*0.00001*M*(5867-pa)
1816
# dry respiration heat loss
1817
HL4 = 0.0014*M*(34-ta)
1818
# heat loss by radiation
1819
HL5 = 3.96*0.00000001*fcl*(((Tcl+273)**4)-((tr+273)**4))
1820
# heat loss by convection
1821
HL6 = fcl*hc*(Tcl-ta)
1822
# thermal sensation trans coeff
1823
TS = 0.303*exp(-0.036*M)+0.028
1824
# calc PMV
1825
PMV = TS*(MW-HL1-HL2-HL3-HL4-HL5-HL6)
1826
self.collectPMV.append(PMV)
1827
PPD = 100-95*exp(-0.03353*PMV**4-0.2179*PMV**2)
1828
self.collectPPD.append(PPD)
1829
self.collectPMVarrays.append(self.collectPMV)
1830
self.collectPPDarrays.append(self.collectPPD)
1831
1832
1833
1834
reshapeCollectArrays = \
reshape(self.collectPMVarrays,shape(self.calcSelection_Range))
self.heatmapCalcResults = mean(reshapeCollectArrays, axis=0)
1835
1836
# display items
1837
r = around(self.heatmapCalcResults, decimals=2)
1838
u = unique(r)
1839
self.Tickvalues = u
1840
self.Tickvalues = [-3.0, -2.0, -1.0, -0.5, 0, 0.5, 1.0, 2.0, 3.0]
1841
self.Ticklabels = [’-3’, ’-2’, ’-1’, ’-0.5’, ’0’, \
128
’+0.5’, ’+1’,’+2’,’+3’]
1842
1843
self.Levels = len(u)
1844
self.format = ’%2.2f’
1845
self.scattertitle = \
"ASHRAE Standard 55 PPD as a function of PMV - Room Average"
1846
1847
self.xlabel = ’predicted mean vote (PMV)’
1848
self.ylabel = ’predicted percentage of dissatisfied (PPD)’
1849
self.scatterlim = [-3,3]
1850
def calcPPD_Range(self):
1851
1852
# provides values for PMV scatter plot for every hour of the year
1853
# PMV spatial calc done on-demand using ’Run cMap’ button
1854
self.setDate_Range()
1855
self.calcPMV_Range()
1856
self.scatterCalcResultsPMV = \
1857
self.PMV_RoomAverageAnnual[self.positions[0]:self.positions[-1]]
1858
self.scatterCalcResultsPPD = \
1859
self.PPD_RoomAverageAnnual[self.positions[0]:self.positions[-1]]
1860
1861
reshapeCollectArrays = \
1862
reshape(self.collectPPDarrays,shape(self.calcSelection_Range))
1863
self.heatmapCalcResults = mean(reshapeCollectArrays, axis=0)
1864
1865
1866
# display items
1867
r = around(self.heatmapCalcResults, decimals=2)
1868
u = unique(r)
1869
self.Tickvalues = u
1870
self.Tickvalues = [0, 5, 10, 15, 20, 50]
1871
self.Ticklabels = [’0%’, ’5%’, ’10%’, ’15%’, ’20%’, ’>20%’]
1872
self.Levels = len(u)
1873
#
self.cmap = self.cmap5Step
129
1874
self.format = ’%2.2f’
1875
self.scattertitle = \
1876
"ASHRAE Standard 55 PPD as a function of PMV - Room Average"
1877
self.xlabel = ’predicted mean vote (PMV)’
1878
self.ylabel = ’predicted percentage of dissatisfied (PPD)’
1879
self.scatterlim = [-3,3]
1880
1881
def indexASHRAE55Adaptive_Range(self):
1882
# call indexing function
1883
self.setDate_Range()
1884
# define heatmap and scatter results
1885
self.heatmapCalcResults = mean(self.calcSelection_Range, axis=0)
1886
self.scatterResults()
1887
# display settings
1888
r = around(self.heatmapCalcResults, decimals=1)
1889
u = unique(r)
1890
self.Tickvalues = [-10, -3.5, -2.5, 2.5, 3.5, 10]
1891
self.Ticklabels = [
1892
’<80% acceptability’,
1893
’80% acceptability \n $\Delta$ T -3.5 [C]’,
1894
’90% acceptability \n $\Delta$ T -2.5 [C]’,
1895
’90% acceptability \n $\Delta$ T +2.5 [C]’,
1896
’80% acceptability \n $\Delta$ T +3.5 [C]’,
1897
’< 80% acceptability’]
1898
self.Levels = len(u)
1899
self.format = ’%2.1f’
1900
# scatter plot lines
1901
self.scattertitle = \
1902
"ASHRAE Standard 55 Adaptive Comfort - Room Average"
1903
self.xlabel = ’mean monthly outdoor temperature [C]’
1904
self.ylabel = ’indoor operative temperature [C]’
1905
self.scatterXlim = [5,35]
130
1906
self.scatterYlim = [16,32]
1907
# scatter plot boundary lines
1908
self.outdoor = arange(10,33)
1909
self.comfortASHRAE = (0.31*self.outdoor+17.8)
1910
self.lower80ASHRAE = (0.31*self.outdoor+17.8)-3.5
1911
self.lower90ASHRAE = (0.31*self.outdoor+17.8)-2.5
1912
self.upper90ASHRAE = (0.31*self.outdoor+17.8)+2.5
1913
self.upper80ASHRAE = (0.31*self.outdoor+17.8)+3.5
1914
1915
1916
def indexEN15251Adaptive_Range(self):
1917
# call indexing function
1918
self.setDate_Range()
1919
# define heatmap and scatter results
1920
self.heatmapCalcResults = mean(self.calcSelection_Range, axis=0)
1921
self.scatterResults()
1922
# display settings
1923
r = around(self.heatmapCalcResults, decimals=1)
1924
u = unique(r)
1925
self.Tickvalues = [-10, -3.0, -2.0, 2.0, 3.0, 10]
1926
self.Ticklabels = [
1927
’<80% acceptability’,
1928
’80% acceptability \n $\Delta$ T -3.0 [C]’,
1929
’90% acceptability \n $\Delta$ T -2.0 [C]’,
1930
’90% acceptability \n $\Delta$ T +2.0 [C]’,
1931
’80% acceptability \n $\Delta$ T +3.0 [C]’,
1932
’< 80% acceptability’]
1933
self.Levels = len(u)
1934
self.format = ’%2.1f’
1935
# scatter plot lines
1936
self.scattertitle = \
1937
"European Standard EN 15251 Adaptive Comfort - Room Average"
131
1938
self.xlabel = ’7-day running mean outdoor temperature [C]’
1939
self.ylabel = ’indoor operative temperature [C]’
1940
self.scatterXlim = [5,35]
1941
self.scatterYlim = [16,32]
1942
# scatter plot boundary lines
1943
self.outdoor = arange(10,33)
1944
self.comfortEN15251 = (0.33*self.outdoor+18.8)
1945
self.lower80EN15251 = (0.33*self.outdoor+18.8)-3.0
1946
self.lower90EN15251 = (0.33*self.outdoor+18.8)-2.0
1947
self.upper90EN15251 = (0.33*self.outdoor+18.8)+2.0
1948
self.upper80EN15251 = (0.33*self.outdoor+18.8)+3.0
1949
1950
1951
def indexNPRCR1752BetaAdaptive_Range(self):
1952
# call indexing function
1953
self.setDate_Range()
1954
# define heatmap and scatter results
1955
self.heatmapCalcResults = mean(self.calcSelection_Range, axis=0)
1956
self.scatterResults()
1957
# display settings
1958
r = around(self.heatmapCalcResults, decimals=1)
1959
u = unique(r)
1960
self.Tickvalues = [-10, -3.0, -2.0, 2.0, 3.0, 10]
1961
self.Ticklabels = [
1962
’<80% acceptability’,
1963
’80% acceptability \n $\Delta$ T -3.0 [C]’,
1964
’90% acceptability \n $\Delta$ T -2.0 [C]’,
1965
’90% acceptability \n $\Delta$ T +2.0 [C]’,
1966
’80% acceptability \n $\Delta$ T +3.0 [C]’,
1967
’< 80% acceptability’]
1968
self.Levels = len(u)
1969
self.format = ’%2.1f’
132
1970
# scatter plot lines
1971
self.scattertitle = \
1972
"Dutch Standard NPR-CR 1752 Type Beta Adaptive Comfort"
1973
self.xlabel = ’3-day running mean outdoor temperature [C]’
1974
self.ylabel = ’indoor operative temperature [C]’
1975
self.scatterXlim = [5,35]
1976
self.scatterYlim = [16,32]
1977
# scatter plot boundary lines
1978
self.outdoor = arange(10,33)
1979
self.comfortNPRCR1752 = (0.31*self.outdoor+17.8)
1980
self.lower80NPRCR1752 = (0.31*self.outdoor+17.8)-3.0
1981
self.lower90NPRCR1752 = (0.31*self.outdoor+17.8)-2.0
1982
self.upper90NPRCR1752 = (0.31*self.outdoor+17.8)+2.0
1983
self.upper80NPRCR1752 = (0.31*self.outdoor+17.8)+3.0
1984
1985
# Control Functions -----------------------------------------------
1986
1987
# run controls
1988
def loadFiles(self, event):
1989
self.idf = self.idfSel.GetValue()
1990
self.csv = self.csvSel.GetValue()
1991
# call calc functions
1992
self.readIDF()
1993
self.getTemps()
1994
self.setDate_Annual()
1995
self.viewFactors()
1996
# call annual metric calc functions
1997
self.calcMRTS()
1998
self.calcOpTemp()
1999
self.calcPMVPPDRoomAverageAnnual()
2000
self.calcAdaptiveASHRAE55()
2001
self.calcAdaptiveEN15251()
133
self.calcAdaptiveNPRCR1752Beta()
2002
2003
def runCmap(self, event):
2004
self.GetCalcType()
2005
2006
#
self.comfParamsSet()
if self.noColor.GetValue() == True:
2007
self.makeScatter()
2008
if self.monthColor.GetValue() == True:
2009
self.makeScatterMonth()
2010
if self.timeColor.GetValue() == True:
2011
self.makeScatterHour()
2012
self.makePlot()
2013
2014
def runUpdate(self, event):
2015
self.GetCalcType()
2016
2017
2018
2019
2020
2021
2022
2023
2024
#
self.comfParamsSet()
if self.noColor.GetValue() == True:
self.makeScatter()
if self.monthColor.GetValue() == True:
self.makeScatterMonth()
if self.timeColor.GetValue() == True:
self.makeScatterHour()
self.updatePlot()
2025
2026
def GetCalcType(self):
2027
i = self.calcDrop.GetSelection()
2028
j = self.timeframeDrop.GetSelection()
2029
# point in time calculation functions
2030
if i == 0 and j == 1:
2031
2032
2033
self.indexMRTS_Point()
if i == 1 and j == 1:
self.indexOpTemp_Point()
134
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
if i == 2 and j == 1:
self.calcPMV_Point()
if i == 3 and j == 1:
self.calcPPD_Point()
if i == 4 and j == 1:
self.indexASHRAE55Adaptive_Point()
if i == 5 and j == 1:
self.indexEN15251Adaptive_Point()
if i == 6 and j == 1:
self.indexNPRCR1752BetaAdaptive_Point()
2044
# range calculation functions
2045
if i == 0 and j == 0:
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
self.indexMRTS_Range()
if i == 1 and j == 0:
self.indexOpTemp_Range()
if i == 2 and j == 0:
self.calcPMV_Range()
if i == 3 and j == 0:
self.calcPPD_Range()
if i == 4 and j == 0:
self.indexASHRAE55Adaptive_Range()
if i == 5 and j == 0:
self.indexEN15251Adaptive_Range()
if i == 6 and j == 0:
self.indexNPRCR1752BetaAdaptive_Range()
2059
2060
def comfParamsSet(self):
2061
# get values
2062
self.oDBSpin.SetLabel((str(around(self.oDB,decimals=1))). \
2063
2064
2065
strip(’[’).strip(’]’))
self.oRHSpin.SetLabel((str(around(self.oRH,decimals=0))). \
strip(’[’).strip(’]’).strip(’.’))
135
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
self.oAirVSpin.SetLabel((str(around(self.oAirV,decimals=1))). \
strip(’[’).strip(’]’))
self.zDBSpin.SetLabel((str(around(self.zDB,decimals=1))). \
strip(’[’).strip(’]’))
self.zRHSpin.SetLabel((str(around(self.zRH,decimals=0))). \
strip(’[’).strip(’]’).strip(’.’))
self.zAirVSchSpin.SetLabel((str(around(self.zAirVSch,decimals=1))). \
strip(’[’).strip(’]’))
self.zCloSchSpin.SetLabel((str(around(self.zCloSch,decimals=1))). \
strip(’[’).strip(’]’))
self.zActSchSpin.SetLabel((str(around(self.zActSch/58.2,decimals=1))). \
strip(’[’).strip(’]’))
2078
2079
# scale controls
2080
def scaleSet(self):
2081
# get min & max to clip heatmaps
2082
i = self.calcDrop.GetSelection()
2083
if i >= 0 and i <= 1:
2084
r = around(self.heatmapCalcResults, decimals=1)
2085
u = unique(r)
2086
self.Tickvalues = around(linspace(min(u), max(u), num=10,
endpoint=True), decimals=1)
2087
2088
self.Ticklabels = self.Tickvalues
2089
self.Levels = self.Tickvalues
2090
self.Ticks = self.Tickvalues
2091
self.Norm = mpl.colors.Normalize(vmin = min(self.Tickvalues),
vmax = max(self.Tickvalues), clip = False)
2092
2093
self.cmapMaxDisplay.SetValue(str(max(self.Tickvalues)))
2094
self.cmapMinDisplay.SetValue(str(min(self.Tickvalues)))
2095
else:
2096
self.Ticks = self.Tickvalues
2097
self.Norm = mpl.colors.Normalize(vmin = min(self.Tickvalues),
136
2098
vmax = max(self.Tickvalues), clip = False)
2099
2100
def OnAdjustScale(self, event):
2101
clipMax = float(self.cmapMaxDisplay.GetValue())
2102
clipMin = float(self.cmapMinDisplay.GetValue())
2103
self.Tickvalues = around(linspace(clipMin, clipMax, num=10,
2104
endpoint=True), decimals=1)
2105
self.Ticklabels = self.Tickvalues
2106
self.Levels = self.Tickvalues
2107
self.Ticks = self.Tickvalues
2108
self.Norm = mpl.colors.Normalize(vmin = min(self.Tickvalues),
2109
2110
vmax = max(self.Tickvalues), clip = False)
self.updatePlot()
2111
2112
def OnAutoScale(self, event):
2113
self.scaleSet()
2114
self.updatePlot()
2115
2116
# cut controls
2117
def OnCheck(self, event):
2118
self.v = event.GetEventObject().GetLabel()
2119
cn = { ’X-Y’ : ’0’, ’Y-Z’ : ’1’, ’X-Z’ : ’2’, }
2120
self.cutNum = int(cn[self.v])
2121
self.axH = self.v[0]
2122
self.axV = self.v[2]
2123
self.updatePlot()
2124
2125
# grid controls
2126
def OnGrid(self, event):
2127
self.gpt = self.gridSpin.GetValue()
2128
self.sliceSpin.SetRange(0,self.gpt-1)
2129
self.readIDF()
137
2130
self.getTemps()
2131
self.GetCalcType()
2132
self.updatePlot()
2133
2134
def DisableAnnual(self,event):
2135
a = self.StartMonthSpin.GetValue()
2136
b = self.StartDaySpin.GetValue()
2137
c = self.StartHourSpin.GetValue()
2138
z = self.StartHourSpin.GetValue()+1
2139
d = self.EndMonthSpin.GetValue()
2140
e = self.EndDaySpin.GetValue()
2141
f = self.EndHourSpin.GetValue()
2142
j = self.timeframeDrop.GetSelection()
2143
if j == 1:
2144
self.EndMonthSpin.Hide()
2145
self.EndDaySpin.Hide()
2146
self.EndHourSpin.Hide()
2147
self.enTxt.Hide()
2148
self.noColor.SetValue(True)
2149
self.monthColor.Disable()
2150
self.timeColor.Disable()
2151
else:
2152
self.EndMonthSpin.Show()
2153
self.EndDaySpin.Show()
2154
self.EndHourSpin.Show()
2155
self.enTxt.Show()
2156
self.monthColor.Enable()
2157
self.timeColor.Enable()
2158
2159
# slice controls
2160
def OnSlice(self, event):
2161
self.ind = self.sliceSpin.GetValue()
138
2162
self.updatePlot()
2163
2164
# scatter plot controls
2165
def makeScatter(self):
2166
print ’Making scatter plot...’
2167
self.scatterFig.clf()
2168
self.scatteraxes = self.scatterFig.add_subplot(1,1,1)
2169
self.scatterFig.subplots_adjust(top=0.875)
2170
self.scatterFig.subplots_adjust(bottom=0.2)
2171
self.scatterFig.subplots_adjust(left=0.1)
2172
self.scatterFig.subplots_adjust(right=0.9)
2173
self.scatteraxes.set_ylim(self.scatterYlim)
2174
self.scatteraxes.set_xlim(self.scatterXlim)
2175
i = self.calcDrop.GetSelection()
2176
j = self.timeframeDrop.GetSelection()
2177
if i >= 0 and i <= 1:
2178
self.scatteraxes.scatter(self.outdoorDB,self.scatterCalcResults,
2179
color=self.facecolorBlue,edgecolor=self.edgecolor,
2180
lw = self.lw,alpha=self.alpha,s=self.scattersize)
2181
if i >= 2 and i <= 3:
2182
self.curvePMVS = arange(-3,3.1,0.1)
2183
self.curvePPDS = \
2184
100-95*exp(-0.03353*self.curvePMVS**4-0.2179*self.curvePMVS**2)
2185
self.scatteraxes.plot(self.curvePMVS,self.curvePPDS, ’k’)
2186
self.scatteraxes.plot(self.scatterCalcResultsPMV,
2187
self.scatterCalcResultsPPD,’o’,
2188
color=self.facecolorBlue,markersize=5)
2189
if i == 4:
2190
d = Line2D(self.outdoor,self.lower80ASHRAE, color=’black’)
2191
e = Line2D(self.outdoor,self.lower90ASHRAE, color=’black’,
2192
2193
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortASHRAE, color=’black’,
139
2194
2195
2196
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90ASHRAE, color=’black’,
linestyle = ’--’)
2197
h = Line2D(self.outdoor,self.upper80ASHRAE, color=’black’)
2198
self.scatteraxes.add_line(d)
2199
self.scatteraxes.add_line(e)
2200
self.scatteraxes.add_line(f)
2201
self.scatteraxes.add_line(g)
2202
self.scatteraxes.add_line(h)
2203
self.scatteraxes.scatter(self.monthlyOutdoorMean,
2204
self.scatterCalcResults,color=self.facecolorBlue,
2205
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2206
s=self.scattersize)
2207
# make legend and labels
2208
prop = fm.FontProperties(size=8)
2209
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2210
’Comfort Temperature’)
2211
legendseries = (d,e,f)
2212
self.scatteraxes.legend(legendseries, legendlabels,
2213
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2214
ncol=3, prop=prop).draw_frame(False)
2215
if i == 5:
2216
d = Line2D(self.outdoor,self.lower80EN15251, color=’black’)
2217
e = Line2D(self.outdoor,self.lower90EN15251, color=’black’,
2218
2219
2220
2221
2222
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortEN15251, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90EN15251, color=’black’,
linestyle = ’--’)
2223
h = Line2D(self.outdoor,self.upper80EN15251, color=’black’)
2224
self.scatteraxes.add_line(d)
2225
self.scatteraxes.add_line(e)
140
2226
self.scatteraxes.add_line(f)
2227
self.scatteraxes.add_line(g)
2228
self.scatteraxes.add_line(h)
2229
self.scatteraxes.scatter(self.runningMean7day,
2230
self.scatterCalcResults,color=self.facecolorBlue,
2231
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2232
s=self.scattersize)
2233
# make legend and labels
2234
prop = fm.FontProperties(size=8)
2235
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2236
’Comfort Temperature’)
2237
legendseries = (d,e,f)
2238
self.scatteraxes.legend(legendseries, legendlabels,
2239
2240
2241
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
ncol=3, prop=prop).draw_frame(False)
if i == 6:
2242
d = Line2D(self.outdoor,self.lower80NPRCR1752, color=’black’)
2243
e = Line2D(self.outdoor,self.lower90NPRCR1752, color=’black’,
2244
2245
2246
2247
2248
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortNPRCR1752, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90NPRCR1752, color=’black’,
linestyle = ’--’)
2249
h = Line2D(self.outdoor,self.upper80NPRCR1752, color=’black’)
2250
self.scatteraxes.add_line(d)
2251
self.scatteraxes.add_line(e)
2252
self.scatteraxes.add_line(f)
2253
self.scatteraxes.add_line(g)
2254
self.scatteraxes.add_line(h)
2255
self.scatteraxes.scatter(self.runningMean3day,
2256
self.scatterCalcResults,color=self.facecolorBlue,
2257
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
141
2258
s=self.scattersize)
2259
# make legend and labels
2260
prop = fm.FontProperties(size=8)
2261
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2262
’Comfort Temperature’)
2263
legendseries = (d,e,f)
2264
self.scatteraxes.legend(legendseries, legendlabels,
2265
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2266
ncol=3, prop=prop).draw_frame(False)
2267
self.scatteraxes.grid(True,color=self.gridcolor)
2268
self.scatteraxes.spines[’bottom’].set_color(self.axescolor)
2269
self.scatteraxes.spines[’top’].set_color(self.axescolor)
2270
self.scatteraxes.spines[’right’].set_color(self.axescolor)
2271
self.scatteraxes.spines[’left’].set_color(self.axescolor)
2272
self.scatteraxes.set_title(self.scattertitle,fontsize=12)
2273
self.scatteraxes.set_xlabel(self.xlabel, fontsize=8)
2274
self.scatteraxes.set_ylabel(self.ylabel, fontsize=8)
2275
# update the font size of the x and y axes
2276
for tick in self.scatteraxes.xaxis.get_major_ticks():
2277
2278
2279
tick.label1.set_fontsize(8)
for tick in self.scatteraxes.yaxis.get_major_ticks():
tick.label1.set_fontsize(8)
2280
# send resize event to refresh panel
2281
pix = tuple( self.panel3.GetClientSize() )
2282
set = (pix[0]*1.01, pix[1]*1.01)
2283
self.scatterChart.SetClientSize( set )
2284
self.scatterCanvas.SetClientSize( set )
2285
print ’Finished making scatter plot’
2286
2287
# scatter plot controls
2288
def makeScatterMonth(self):
2289
print ’Making scatter plot...’
142
2290
self.scatterFig.clf()
2291
self.scatteraxes = self.scatterFig.add_subplot(1,1,1)
2292
self.scatterFig.subplots_adjust(top=0.875)
2293
self.scatterFig.subplots_adjust(bottom=0.2)
2294
self.scatterFig.subplots_adjust(left=0.1)
2295
self.scatterFig.subplots_adjust(right=0.9)
2296
self.scatteraxes.set_ylim(self.scatterYlim)
2297
self.scatteraxes.set_xlim(self.scatterXlim)
2298
i = self.calcDrop.GetSelection()
2299
j = self.timeframeDrop.GetSelection()
2300
if i >= 0 and i <= 1:
2301
a = self.scatteraxes.scatter(self.MonthBin1,
2302
self.scatterCalcResultsMonthBin1,color=self.facecolorBlue,
2303
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2304
s=self.scattersize)
2305
b = self.scatteraxes.scatter(self.MonthBin2,
2306
self.scatterCalcResultsMonthBin2,color=self.facecolorRed,
2307
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2308
s=self.scattersize)
2309
c = self.scatteraxes.scatter(self.MonthBin3,
2310
self.scatterCalcResultsMonthBin3,color=self.facecolorBlue,
2311
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2312
s=self.scattersize)
2313
# make legend and labels
2314
prop = fm.FontProperties(size=8)
2315
legendlabels = (’Simulated Hours’,’Highlighted Range’)
2316
legendseries = (a,b)
2317
self.scatteraxes.legend(legendseries, legendlabels,
2318
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2319
ncol=2, prop=prop).draw_frame(False)
2320
# need PMV and PPD
2321
if i == 4:
143
2322
d = Line2D(self.outdoor,self.lower80ASHRAE, color=’black’)
2323
e = Line2D(self.outdoor,self.lower90ASHRAE, color=’black’,
2324
2325
2326
2327
2328
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortASHRAE, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90ASHRAE, color=’black’,
linestyle = ’--’)
2329
h = Line2D(self.outdoor,self.upper80ASHRAE, color=’black’)
2330
self.scatteraxes.add_line(d)
2331
self.scatteraxes.add_line(e)
2332
self.scatteraxes.add_line(f)
2333
self.scatteraxes.add_line(g)
2334
self.scatteraxes.add_line(h)
2335
a = self.scatteraxes.scatter(self.MonthBin1,
2336
self.scatterCalcResultsMonthBin1,color=self.facecolorBlue,
2337
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2338
s=self.scattersize)
2339
b = self.scatteraxes.scatter(self.MonthBin2,
2340
self.scatterCalcResultsMonthBin2,color=self.facecolorRed,
2341
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2342
s=self.scattersize)
2343
c = self.scatteraxes.scatter(self.MonthBin3,
2344
self.scatterCalcResultsMonthBin3,color=self.facecolorBlue,
2345
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2346
s=self.scattersize)
2347
# make legend and labels
2348
prop = fm.FontProperties(size=8)
2349
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2350
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
2351
legendseries = (d,e,f,a,b)
2352
self.scatteraxes.legend(legendseries, legendlabels,
2353
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
144
2354
2355
ncol=5, prop=prop).draw_frame(False)
if i == 5:
2356
d = Line2D(self.outdoor,self.lower80EN15251, color=’black’)
2357
e = Line2D(self.outdoor,self.lower90EN15251, color=’black’,
2358
2359
2360
2361
2362
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortEN15251, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90EN15251, color=’black’,
linestyle = ’--’)
2363
h = Line2D(self.outdoor,self.upper80EN15251, color=’black’)
2364
self.scatteraxes.add_line(d)
2365
self.scatteraxes.add_line(e)
2366
self.scatteraxes.add_line(f)
2367
self.scatteraxes.add_line(g)
2368
self.scatteraxes.add_line(h)
2369
a = self.scatteraxes.scatter(self.MonthBin1,
2370
self.scatterCalcResultsMonthBin1,color=self.facecolorBlue,
2371
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2372
s=self.scattersize)
2373
b = self.scatteraxes.scatter(self.MonthBin2,
2374
self.scatterCalcResultsMonthBin2,color=self.facecolorRed,
2375
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2376
s=self.scattersize)
2377
c = self.scatteraxes.scatter(self.MonthBin3,
2378
self.scatterCalcResultsMonthBin3,color=self.facecolorBlue,
2379
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2380
s=self.scattersize)
2381
# make legend and labels
2382
prop = fm.FontProperties(size=8)
2383
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2384
2385
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
legendseries = (d,e,f,a,b)
145
2386
self.scatteraxes.legend(legendseries, legendlabels,
2387
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2388
ncol=5, prop=prop).draw_frame(False)
2389
if i == 6:
2390
d = Line2D(self.outdoor,self.lower80NPRCR1752, color=’black’)
2391
e = Line2D(self.outdoor,self.lower90NPRCR1752, color=’black’,
2392
2393
2394
2395
2396
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortNPRCR1752, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90NPRCR1752, color=’black’,
linestyle = ’--’)
2397
h = Line2D(self.outdoor,self.upper80NPRCR1752, color=’black’)
2398
self.scatteraxes.add_line(d)
2399
self.scatteraxes.add_line(e)
2400
self.scatteraxes.add_line(f)
2401
self.scatteraxes.add_line(g)
2402
self.scatteraxes.add_line(h)
2403
a = self.scatteraxes.scatter(self.MonthBin1,
2404
self.scatterCalcResultsMonthBin1,color=self.facecolorBlue,
2405
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2406
s=self.scattersize)
2407
b = self.scatteraxes.scatter(self.MonthBin2,
2408
self.scatterCalcResultsMonthBin2,color=self.facecolorRed,
2409
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2410
s=self.scattersize)
2411
c = self.scatteraxes.scatter(self.MonthBin3,
2412
self.scatterCalcResultsMonthBin3,color=self.facecolorBlue,
2413
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2414
s=self.scattersize)
2415
# make legend and labels
2416
prop = fm.FontProperties(size=8)
2417
legendlabels = (’80% Acceptability’,’90% Acceptability’,
146
2418
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
2419
legendseries = (d,e,f,a,b)
2420
self.scatteraxes.legend(legendseries, legendlabels,
2421
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2422
ncol=5, prop=prop).draw_frame(False)
2423
self.scatteraxes.set_title(self.scattertitle,fontsize=12)
2424
self.scatteraxes.set_xlabel(self.xlabel, fontsize=8)
2425
self.scatteraxes.set_ylabel(self.ylabel, fontsize=8)
2426
self.scatteraxes.grid(True,color=self.gridcolor)
2427
self.scatteraxes.spines[’bottom’].set_color(self.axescolor)
2428
self.scatteraxes.spines[’top’].set_color(self.axescolor)
2429
self.scatteraxes.spines[’right’].set_color(self.axescolor)
2430
self.scatteraxes.spines[’left’].set_color(self.axescolor)
2431
# update the font size of the x and y axes
2432
for tick in self.scatteraxes.xaxis.get_major_ticks():
2433
2434
2435
tick.label1.set_fontsize(8)
for tick in self.scatteraxes.yaxis.get_major_ticks():
tick.label1.set_fontsize(8)
2436
# send resize event to refresh panel
2437
pix = tuple( self.panel3.GetClientSize() )
2438
print "PIX", pix
2439
set = (pix[0]*1.01, pix[1]*1.01)
2440
self.scatterChart.SetClientSize( set )
2441
self.scatterCanvas.SetClientSize( set )
2442
print ’Finished making scatter plot’
2443
2444
# scatter plot controls
2445
def makeScatterHour(self):
2446
print ’Making scatter plot...’
2447
self.scatterFig.clf()
2448
self.scatteraxes = self.scatterFig.add_subplot(1,1,1)
2449
self.scatterFig.subplots_adjust(top=0.875)
147
2450
self.scatterFig.subplots_adjust(bottom=0.2)
2451
self.scatterFig.subplots_adjust(left=0.1)
2452
self.scatterFig.subplots_adjust(right=0.9)
2453
self.scatteraxes.set_ylim(self.scatterYlim)
2454
self.scatteraxes.set_xlim(self.scatterXlim)
2455
i = self.calcDrop.GetSelection()
2456
j = self.timeframeDrop.GetSelection()
2457
if i >= 0 and i <= 1:
2458
a = self.scatteraxes.scatter(self.HourBin1,
2459
self.scatterCalcResultsHourBin1,color=self.facecolorBlue,
2460
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2461
s=self.scattersize)
2462
b = self.scatteraxes.scatter(self.HourBin2,
2463
self.scatterCalcResultsHourBin2,color=self.facecolorRed,
2464
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2465
s=self.scattersize)
2466
c = self.scatteraxes.scatter(self.HourBin3,
2467
self.scatterCalcResultsHourBin3,color=self.facecolorBlue,
2468
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2469
s=self.scattersize)
2470
# make legend and labels
2471
prop = fm.FontProperties(size=8)
2472
legendlabels = (’Simulated Hours’,’Highlighted Range’)
2473
legendseries = (a,b)
2474
self.scatteraxes.legend(legendseries, legendlabels,
2475
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2476
ncol=2, prop=prop).draw_frame(False)
2477
# need PMV and PPD
2478
if i == 4:
2479
d = Line2D(self.outdoor,self.lower80ASHRAE, color=’black’)
2480
e = Line2D(self.outdoor,self.lower90ASHRAE, color=’black’,
2481
linestyle = ’--’)
148
2482
2483
2484
2485
f = Line2D(self.outdoor,self.comfortASHRAE, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90ASHRAE, color=’black’,
linestyle = ’--’)
2486
h = Line2D(self.outdoor,self.upper80ASHRAE, color=’black’)
2487
self.scatteraxes.add_line(d)
2488
self.scatteraxes.add_line(e)
2489
self.scatteraxes.add_line(f)
2490
self.scatteraxes.add_line(g)
2491
self.scatteraxes.add_line(h)
2492
a = self.scatteraxes.scatter(self.HourBin1,
2493
self.scatterCalcResultsHourBin1,color=self.facecolorBlue,
2494
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2495
s=self.scattersize)
2496
b = self.scatteraxes.scatter(self.HourBin2,
2497
self.scatterCalcResultsHourBin2,color=self.facecolorRed,
2498
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2499
s=self.scattersize)
2500
c = self.scatteraxes.scatter(self.HourBin3,
2501
self.scatterCalcResultsHourBin3,color=self.facecolorBlue,
2502
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2503
s=self.scattersize)
2504
# make legend and labels
2505
prop = fm.FontProperties(size=8)
2506
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2507
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
2508
legendseries = (d,e,f,a,b)
2509
self.scatteraxes.legend(legendseries, legendlabels,
2510
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2511
ncol=5, prop=prop).draw_frame(False)
2512
2513
if i == 5:
d = Line2D(self.outdoor,self.lower80EN15251, color=’black’)
149
2514
2515
2516
2517
2518
2519
e = Line2D(self.outdoor,self.lower90EN15251, color=’black’,
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortEN15251, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90EN15251, color=’black’,
linestyle = ’--’)
2520
h = Line2D(self.outdoor,self.upper80EN15251, color=’black’)
2521
self.scatteraxes.add_line(d)
2522
self.scatteraxes.add_line(e)
2523
self.scatteraxes.add_line(f)
2524
self.scatteraxes.add_line(g)
2525
self.scatteraxes.add_line(h)
2526
a = self.scatteraxes.scatter(self.HourBin1,
2527
self.scatterCalcResultsHourBin1,color=self.facecolorBlue,
2528
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2529
s=self.scattersize)
2530
b = self.scatteraxes.scatter(self.HourBin2,
2531
self.scatterCalcResultsHourBin2,color=self.facecolorRed,
2532
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2533
s=self.scattersize)
2534
c = self.scatteraxes.scatter(self.HourBin3,
2535
self.scatterCalcResultsHourBin3,color=self.facecolorBlue,
2536
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2537
s=self.scattersize)
2538
# make legend and labels
2539
prop = fm.FontProperties(size=8)
2540
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2541
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
2542
legendseries = (d,e,f,a,b)
2543
self.scatteraxes.legend(legendseries, legendlabels,
2544
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2545
ncol=5, prop=prop).draw_frame(False)
150
2546
if i == 6:
2547
d = Line2D(self.outdoor,self.lower80NPRCR1752, color=’black’)
2548
e = Line2D(self.outdoor,self.lower90NPRCR1752, color=’black’,
2549
2550
2551
2552
2553
linestyle = ’--’)
f = Line2D(self.outdoor,self.comfortNPRCR1752, color=’black’,
linestyle = ’:’)
g = Line2D(self.outdoor,self.upper90NPRCR1752, color=’black’,
linestyle = ’--’)
2554
h = Line2D(self.outdoor,self.upper80NPRCR1752, color=’black’)
2555
self.scatteraxes.add_line(d)
2556
self.scatteraxes.add_line(e)
2557
self.scatteraxes.add_line(f)
2558
self.scatteraxes.add_line(g)
2559
self.scatteraxes.add_line(h)
2560
a = self.scatteraxes.scatter(self.HourBin1,
2561
self.scatterCalcResultsHourBin1,color=self.facecolorBlue,
2562
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2563
s=self.scattersize)
2564
b = self.scatteraxes.scatter(self.HourBin2,
2565
self.scatterCalcResultsHourBin2,color=self.facecolorRed,
2566
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2567
s=self.scattersize)
2568
c = self.scatteraxes.scatter(self.HourBin3,
2569
self.scatterCalcResultsHourBin3,color=self.facecolorBlue,
2570
edgecolor=self.edgecolor,lw = self.lw,alpha=self.alpha,
2571
s=self.scattersize)
2572
# make legend and labels
2573
prop = fm.FontProperties(size=8)
2574
legendlabels = (’80% Acceptability’,’90% Acceptability’,
2575
’Comfort Temperature’,’Simulated Hours’,’Highlighted Range’)
2576
legendseries = (d,e,f,a,b)
2577
self.scatteraxes.legend(legendseries, legendlabels,
151
2578
’upper center’, scatterpoints=1, bbox_to_anchor=(0.5, -0.085),
2579
ncol=5, prop=prop).draw_frame(False)
2580
self.scatteraxes.set_title(self.scattertitle,fontsize=12)
2581
self.scatteraxes.set_xlabel(self.xlabel, fontsize=8)
2582
self.scatteraxes.set_ylabel(self.ylabel, fontsize=8)
2583
self.scatteraxes.grid(True,color=self.gridcolor)
2584
self.scatteraxes.spines[’bottom’].set_color(self.axescolor)
2585
self.scatteraxes.spines[’top’].set_color(self.axescolor)
2586
self.scatteraxes.spines[’right’].set_color(self.axescolor)
2587
self.scatteraxes.spines[’left’].set_color(self.axescolor)
2588
# update the font size of the x and y axes
2589
for tick in self.scatteraxes.xaxis.get_major_ticks():
2590
2591
2592
tick.label1.set_fontsize(8)
for tick in self.scatteraxes.yaxis.get_major_ticks():
tick.label1.set_fontsize(8)
2593
# send resize event to refresh panel
2594
pix = tuple( self.panel3.GetClientSize() )
2595
print "PIX", pix
2596
set = (pix[0]*1.01, pix[1]*1.01)
2597
self.scatterChart.SetClientSize( set )
2598
self.scatterCanvas.SetClientSize( set )
2599
print ’Finished making scatter plot’
2600
2601
# plot controls
2602
def makePlot(self):
2603
print ’Making heatmap plot...’
2604
self.scaleSet()
2605
self.fig.clf()
2606
self.axes = self.fig.add_subplot(1,1,1)
2607
self.fig.subplots_adjust(top=0.95)
2608
self.fig.subplots_adjust(bottom=0.225)
2609
self.fig.subplots_adjust(left=0.1)
152
2610
self.graphCut1 = [self.pt[0,:,:,0], self.pt[1,0,:,:], self.pt[0,:,0,:]]
2611
self.graphCut2 = [self.pt[1,:,:,0], self.pt[2,0,:,:], self.pt[2,:,0,:]]
2612
self.graphCut3 = [
2613
self.heatmapCalcResults[:,:,self.ind],
2614
self.heatmapCalcResults[self.ind,:,:],
2615
self.heatmapCalcResults[:,self.ind,:]]
2616
self.t = self.axes.contourf \
2617
(self.graphCut1[self.cutNum],
2618
self.graphCut2[self.cutNum],
2619
self.graphCut3[self.cutNum],
2620
15,alpha=1, cmap=cm.get_cmap(self.cmap), norm = self.Norm)
2621
self.s = self.axes.contour \
2622
(self.graphCut1[self.cutNum],
2623
self.graphCut2[self.cutNum],
2624
self.graphCut3[self.cutNum],
2625
15, linewidths=0.25, colors=’k’, norm = self.Norm)
2626
pyplot.clabel(self.s, fmt = self.format, colors = ’0.15’, fontsize=9)
2627
self.axes.set_xlabel(’%s distance [m]’%(self.axH), fontsize=8)
2628
self.axes.set_ylabel(’%s distance [m]’%(self.axV), fontsize=8)
2629
# update the font size of the x and y axes
2630
for tick in self.axes.xaxis.get_major_ticks():
2631
2632
2633
tick.label1.set_fontsize(8)
for tick in self.axes.yaxis.get_major_ticks():
tick.label1.set_fontsize(8)
2634
# left,bottom,width,height
2635
cax = self.fig.add_axes([0.1, 0.1, 0.8, 0.02])
2636
cb = mpl.colorbar.ColorbarBase \
2637
(cax, cmap=cm.get_cmap(self.cmap), norm=self.Norm,
2638
orientation = ’horizontal’, boundaries = self.Ticks,
2639
format = ’%2.1f’)
2640
2641
cb.set_label((self.calcList[self.calcDrop.GetCurrentSelection()]),
fontsize=7)
153
2642
cb.ax.set_xticklabels(self.Ticklabels)
2643
for t in cb.ax.get_xticklabels():
2644
t.set_fontsize(7)
2645
# send resize event to refresh panel
2646
pix = tuple( self.panel2.GetClientSize() )
2647
set = (pix[0]*1.01, pix[1]*1.01)
2648
self.chart.SetClientSize( set )
2649
self.canvas.SetClientSize( set )
2650
self.makeKey()
2651
print ’Finished making heatmap plot’
2652
2653
def updatePlot(self):
2654
self.fig.clf()
2655
self.axes = self.fig.add_subplot(1,1,1)
2656
self.fig.subplots_adjust(top=0.95)
2657
self.fig.subplots_adjust(bottom=0.225)
2658
self.fig.subplots_adjust(left=0.1)
2659
self.graphCut1 = [self.pt[0,:,:,0], self.pt[1,0,:,:], self.pt[0,:,0,:]]
2660
self.graphCut2 = [self.pt[1,:,:,0], self.pt[2,0,:,:], self.pt[2,:,0,:]]
2661
self.graphCut3 = [
2662
self.heatmapCalcResults[:,:,self.ind],
2663
self.heatmapCalcResults[self.ind,:,:],
2664
self.heatmapCalcResults[:,self.ind,:]]
2665
self.t = self.axes.contourf \
2666
(self.graphCut1[self.cutNum],
2667
self.graphCut2[self.cutNum],
2668
self.graphCut3[self.cutNum],
2669
15, alpha=1, cmap=cm.get_cmap(self.cmap), norm = self.Norm)
2670
self.s = self.axes.contour \
2671
(self.graphCut1[self.cutNum],
2672
self.graphCut2[self.cutNum],
2673
self.graphCut3[self.cutNum],
154
2674
15, linewidths=0.25, colors=’k’, norm = self.Norm)
2675
pyplot.clabel(self.s, fmt = self.format, colors = ’0.15’, fontsize=9)
2676
self.axes.set_xlabel(’%s distance [m]’%(self.axH), fontsize=8)
2677
self.axes.set_ylabel(’%s distance [m]’%(self.axV), fontsize=8)
2678
# update the font size of the x and y axes
2679
for tick in self.axes.xaxis.get_major_ticks():
2680
2681
2682
tick.label1.set_fontsize(8)
for tick in self.axes.yaxis.get_major_ticks():
tick.label1.set_fontsize(8)
2683
#left,bottom,width,height
2684
cax = self.fig.add_axes([0.1, 0.1, 0.8, 0.02])
2685
cb = mpl.colorbar.ColorbarBase \
2686
(cax, cmap=cm.get_cmap(self.cmap), norm=self.Norm,
2687
orientation = ’horizontal’, boundaries = self.Ticks,
2688
format = ’%2.1f’)
2689
2690
cb.set_label((self.calcList[self.calcDrop.GetCurrentSelection()]),
fontsize=7)
2691
cb.ax.set_xticklabels(self.Ticklabels)
2692
for t in cb.ax.get_xticklabels():
2693
t.set_fontsize(7)
2694
# send resize event to refresh panel
2695
pix = tuple( self.panel2.GetClientSize() )
2696
set = (pix[0]*1.01, pix[1]*1.01)
2697
self.chart.SetClientSize( set )
2698
self.canvas.SetClientSize( set )
2699
self.makeKey()
2700
print ’updatePlot fine’
2701
2702
def makeKey(self):
2703
’’’function for creating 3-D slice key’’’
2704
# plot containers for slice key
2705
self.key = wx.Panel(self.panel4)
155
2706
self.keyfig = Figure(dpi=100, facecolor=’none’)
2707
self.keycanvas = FigureCanvas(self.key, -1, self.keyfig)
2708
self.keySizer.Add(self.key, 0, wx.ALL|wx.EXPAND, 5)
2709
self.keyaxes = axes3d.Axes3D(self.keyfig)
2710
self.keyaxes.disable_mouse_rotation()
2711
xL = [self.xmin,self.xmax]
2712
yL = [self.ymin,self.ymax]
2713
zL = [self.zmin,self.zmax]
2714
xJ = [self.winxmin,self.winxmax]
2715
yJ = [self.winymin,self.winymax]
2716
zJ = [self.winzmin,self.winzmax]
2717
# set plot limits
2718
self.keyaxes.set_xlim((xL[0],xL[1]))
2719
self.keyaxes.set_ylim((yL[0],yL[1]))
2720
self.keyaxes.set_zlim((zL[0],zL[1]))
2721
# plot room facades
2722
xN = [xL[0],xL[0],xL[1],xL[1]]
2723
yN = [yL[1],yL[1],yL[1],yL[1]]
2724
zN = [zL[0],zL[1],zL[1],zL[0]]
2725
verts = [zip(xN,yN,zN)]
2726
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2727
facecolor = (’1’),edgecolors = (’k’), linewidths=(0.5),
2728
alpha=0.05))
2729
# east wall
2730
xE = [xL[1],xL[1],xL[1],xL[1]]
2731
yE = [yL[0],yL[0],yL[1],yL[1]]
2732
zE = [zL[0],zL[1],zL[1],zL[0]]
2733
verts = [zip(xE,yE,zE)]
2734
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2735
facecolor = (’1’),edgecolors = (’k’), linewidths=(0.5),
2736
alpha=0.05))
2737
# south wall
156
2738
xS = [xL[0],xL[0],xL[1],xL[1]]
2739
yS = [yL[0],yL[0],yL[0],yL[0]]
2740
zS = [zL[0],zL[1],zL[1],zL[0]]
2741
verts = [zip(xS,yS,zS)]
2742
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2743
facecolor = (’1’), edgecolors = (’k’), linewidths=(0.5),
2744
alpha=0.05))
2745
# west wall
2746
xW = [xL[0],xL[0],xL[0],xL[0]]
2747
yW = [yL[0],yL[0],yL[1],yL[1]]
2748
zW = [zL[0],zL[1],zL[1],zL[0]]
2749
verts = [zip(xW,yW,zW)]
2750
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2751
facecolor = (’1’), edgecolors = (’k’), linewidths=(0.5),
2752
alpha=0.05))
2753
# floor
2754
xF = [xL[0],xL[0],xL[1],xL[1]]
2755
yF = [yL[0],yL[1],yL[1],yL[0]]
2756
zF = [zL[0],zL[0],zL[0],zL[0]]
2757
verts = [zip(xF,yF,zF)]
2758
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2759
facecolor = (’1’), edgecolors = (’k’), linewidths=(0.5),
2760
alpha=0.05))
2761
# ceiling
2762
xC = [xL[0],xL[0],xL[1],xL[1]]
2763
yC = [yL[0],yL[1],yL[1],yL[0]]
2764
zC = [zL[1],zL[1],zL[1],zL[1]]
2765
verts = [zip(xC,yC,zC)]
2766
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2767
facecolor = (’1’), edgecolors = (’k’), linewidths=(0.5),
2768
alpha=0.05))
2769
# window
157
2770
xWin = [xJ[0],xJ[0],xJ[1],xJ[1]]
2771
yWin = [yJ[1],yJ[1],yJ[1],yJ[1]]
2772
zWin = [zJ[0],zJ[1],zJ[1],zJ[0]]
2773
verts = [zip(xWin,yWin,zWin)]
2774
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2775
facecolor = (’w’), edgecolors = (’b’), linewidths=(0.65),
2776
alpha=0.05))
2777
# cut slice
2778
ct = [self.pt[2,0,0,self.ind], self.pt[0,self.ind,0,0],
2779
self.pt[1,0,self.ind,0]]
2780
self.TT = ct[self.cutNum]
2781
cn = [’X-Y’, ’Y-Z’, ’X-Z’]
2782
self.SS = cn[self.cutNum]
2783
if self.SS == ’X-Y’:
2784
xCut = [xL[0],xL[1],xL[1],xL[0]]
2785
yCut = [yL[0],yL[0],yL[1],yL[1]]
2786
zCut = [self.TT,self.TT,self.TT,self.TT]
2787
if self.SS == ’Y-Z’:
2788
xCut = [self.TT,self.TT,self.TT,self.TT]
2789
yCut = [yL[0],yL[0],yL[1],yL[1]]
2790
zCut = [zL[0],zL[1],zL[1],zL[0]]
2791
if self.SS == ’X-Z’:
2792
xCut = [xL[0],xL[0],xL[1],xL[1]]
2793
yCut = [self.TT,self.TT,self.TT,self.TT]
2794
zCut = [zL[0],zL[1],zL[1],zL[0]]
2795
verts = [zip(xCut,yCut,zCut)]
2796
self.keyaxes.add_collection3d(Poly3DCollection(verts,
2797
facecolor = (’#BDBDBD’), edgecolors = (’#969696’), alpha=1))
2798
# format plot
2799
self.keyaxes.set_xticks([])
2800
self.keyaxes.set_yticks([])
2801
self.keyaxes.set_zticks([])
158
2802
self.keyaxes.set_xlabel(’x’, fontsize=8)
2803
self.keyaxes.set_ylabel(’y’, fontsize=8)
2804
self.keyaxes.set_zlabel(’z’, fontsize=8 )
2805
# link key containers to resize functions
2806
self._SetSizeKey()
2807
self.keycanvas.draw()
2808
self.key._resizeflag = False
2809
self.key.Bind(wx.EVT_IDLE, self._onIdleKey)
2810
self.key.Bind(wx.EVT_SIZE, self._onSizeKey)
2811
2812
def OnChoice(self, event):
2813
choice = event.GetString()
2814
print choice
2815
2816
def OnClose(self, event):
2817
# deinitialize the frame manager
2818
self._mgr.UnInit()
2819
# delete the frame
2820
self.Destroy()
2821
2822
def OnPrint(self, event):
2823
’’’function for exporting images as .png files’’’
2824
cwd = os.getcwd()
2825
#make destination folders
2826
dirs = [os.path.join(cwd, ’images’)]
2827
for i in dirs:
2828
2829
2830
2831
2832
2833
try:
os.makedirs(i)
except OSError:
pass
ct = [self.pt[2,0,0,self.ind], self.pt[0,self.ind,0,0],
self.pt[1,0,self.ind,0]]
159
2834
self.TT = ct[self.cutNum]
2835
cn = [’XY’, ’YZ’, ’XZ’]
2836
self.SS = cn[self.cutNum]
2837
i = str(self.calcDrop.GetSelection())
2838
cn = { ’0’:’MRT’, ’1’:’OpTemp’, ’2’:’PMV’, ’3’:’PPD’,
2839
’4’:’AdaptASHRAE’, ’5’:’AdaptEN15251’, ’6’:’AdaptNPRCR1752’, }
2840
calc = (cn[i])
2841
j = self.timeframeDrop.GetSelection()
2842
if j == 0:
2843
timeStart = ’%s%s%s’ % \
(self.StartMonth,self.StartDay,self.StartHour)
2844
2845
timeEnd = ’-%s%s%s’ % \
(self.EndMonth,self.EndDay,self.EndHour)
2846
2847
2848
2849
2850
2851
2852
2853
2854
else:
timeStart = ’%s%s%s’ % \
(self.StartMonth,self.StartDay,self.StartHour)
timeEnd = ’’
heatmapFname = cwd + ’/images’ + ’/hmap%s_%s%s_%s%sm.png’ % \
(calc,timeStart,timeEnd,self.SS,self.TT)
scatterFname = cwd + ’/images’ + ’/sctr%s_%s%s_RmAvg.png’ % \
(calc,timeStart,timeEnd)
2855
print ’Saving image’, heatmapFname
2856
print ’Saving image’, scatterFname
2857
self.fig.savefig(heatmapFname, dpi = 300, format=’png’)
2858
self.scatterFig.savefig(scatterFname, dpi = 300, format=’png’)
2859
2860
# Resize Plot Functions - heatmap plot ----------------------------
2861
# plots are updated using these resize events
2862
2863
2864
def _onSize( self, event ):
self.chart._resizeflag = True
2865
160
2866
2867
def _onIdle( self, evt ):
if self.chart._resizeflag:
2868
self.chart._resizeflag = False
2869
self._SetSize()
2870
2871
def _SetSize( self ):
2872
pixels = tuple( self.panel2.GetClientSize() )
2873
self.chart.SetSize( pixels )
2874
self.canvas.SetSize( pixels )
2875
self.fig.set_size_inches \
2876
2877
( float( pixels[0] )/self.fig.get_dpi(),
float( pixels[1] )/self.fig.get_dpi() )
2878
2879
def draw(self): pass # abstract, to be overridden by child classes
2880
2881
# Resize Plot Functions - diagram key -----------------------------
2882
2883
2884
def _onSizeKey( self, event ):
self.key._resizeflag = True
2885
2886
def _onIdleKey( self, evt ):
2887
if self.key._resizeflag:
2888
self.key._resizeflag = False
2889
self._SetSizeKey()
2890
2891
def _SetSizeKey( self ):
2892
pixels = tuple( self.panel4.GetClientSize() )
2893
self.key.SetSize( [pixels[0]/1.1,pixels[1]/1.1] )
2894
self.keycanvas.SetSize( [pixels[0]/1.1,pixels[1]/1.1] )
2895
self.keyfig.set_size_inches \
2896
2897
( float( pixels[0]/1.1 )/self.keyfig.get_dpi(),
float( pixels[1]/1.1 )/self.keyfig.get_dpi() )
161
2898
2899
def draw(self): pass # abstract, to be overridden by child classes
2900
2901
# Resize Plot Functions - scatter plot ----------------------------
2902
2903
2904
def _onSizeScatter( self, event ):
self.scatterChart._resizeflag = True
2905
2906
2907
def _onIdleScatter( self, evt ):
if self.scatterChart._resizeflag:
2908
self.scatterChart._resizeflag = False
2909
self._SetSizeScatter()
2910
2911
def _SetSizeScatter( self ):
2912
pixels = tuple( self.panel3.GetClientSize() )
2913
self.scatterChart.SetSize( pixels )
2914
self.scatterCanvas.SetSize( pixels )
2915
self.scatterFig.set_size_inches \
2916
2917
( float( pixels[0] )/self.scatterFig.get_dpi(),
float( pixels[1] )/self.scatterFig.get_dpi() )
2918
2919
def draw(self): pass # abstract, to be overridden by child classes
2920
2921
# FileSelectorCombo class ---------------------------------------------
2922
class FileSelectorComboIDF(wx.combo.ComboCtrl):
2923
’’’class for control for selecting .idf file’’’
2924
def __init__(self, *args, **kw):
2925
wx.combo.ComboCtrl.__init__(self, *args, **kw)
2926
2927
# make a custom bitmap showing "..."
2928
bw, bh = 14, 16
2929
bmp = wx.EmptyBitmap(bw,bh)
162
2930
dc = wx.MemoryDC(bmp)
2931
2932
# clear to a specific background colour
2933
bgcolor = wx.Colour(255,254,255)
2934
dc.SetBackground(wx.Brush(bgcolor))
2935
dc.Clear()
2936
2937
# draw the label onto the bitmap
2938
label = "..."
2939
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2940
font.SetWeight(wx.FONTWEIGHT_BOLD)
2941
dc.SetFont(font)
2942
tw,th = dc.GetTextExtent(label)
2943
dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
2944
del dc
2945
2946
# now apply a mask using the bgcolor
2947
bmp.SetMaskColour(bgcolor)
2948
2949
# and tell the ComboCtrl to use it
2950
self.SetButtonBitmaps(bmp, True)
2951
2952
# Overridden from ComboCtrl, called when the combo button is clicked
2953
def OnButtonClick(self):
2954
path = ""
2955
name = ""
2956
if self.GetValue():
2957
path, name = os.path.split(self.GetValue())
2958
2959
2960
2961
dlg = wx.FileDialog(self, "Choose File", path, name,
".idf files (*.idf)|*.idf", wx.FD_OPEN)
if dlg.ShowModal() == wx.ID_OK:
163
2962
self.SetValue(dlg.GetPath())
2963
dlg.Destroy()
2964
self.SetFocus()
2965
2966
# Overridden from ComboCtrl to avoid assert since there is no ComboPopup
2967
def DoSetPopupControl(self, popup):
2968
pass
2969
2970
class FileSelectorComboCSV(wx.combo.ComboCtrl):
2971
’’’class for control for selecting .csv file’’’
2972
def __init__(self, *args, **kw):
2973
wx.combo.ComboCtrl.__init__(self, *args, **kw)
2974
2975
# make a custom bitmap showing "..."
2976
bw, bh = 14, 16
2977
bmp = wx.EmptyBitmap(bw,bh)
2978
dc = wx.MemoryDC(bmp)
2979
2980
# clear to a specific background colour
2981
bgcolor = wx.Colour(255,254,255)
2982
dc.SetBackground(wx.Brush(bgcolor))
2983
dc.Clear()
2984
2985
# draw the label onto the bitmap
2986
label = "..."
2987
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2988
font.SetWeight(wx.FONTWEIGHT_BOLD)
2989
dc.SetFont(font)
2990
tw,th = dc.GetTextExtent(label)
2991
dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
2992
del dc
2993
164
2994
# now apply a mask using the bgcolor
2995
bmp.SetMaskColour(bgcolor)
2996
2997
# and tell the ComboCtrl to use it
2998
self.SetButtonBitmaps(bmp, True)
2999
3000
3001
# Overridden from ComboCtrl, called when the combo button is clicked
3002
def OnButtonClick(self):
3003
path = ""
3004
name = ""
3005
if self.GetValue():
3006
path, name = os.path.split(self.GetValue())
3007
3008
dlg = wx.FileDialog(self, "Choose File", path, name,
".csv files (*.csv)|*.csv", wx.FD_OPEN)
3009
3010
3011
if dlg.ShowModal() == wx.ID_OK:
self.SetValue(dlg.GetPath())
3012
dlg.Destroy()
3013
self.SetFocus()
3014
3015
# Overridden from ComboCtrl to avoid assert since there is no ComboPopup
3016
def DoSetPopupControl(self, popup):
3017
pass
3018
3019
# End FileSelectorCombo class------------------------------------------
3020
3021
app = wx.PySimpleApp()
3022
frame = MyFrame(None, -1, "cMap - Thermal Comfort Spatial Mapping")
3023
frame.Show()
3024
app.MainLoop()
3025
165
3026
# END INTERFACE -------------------------------------------------------
166