Psychro chart unit issue

Hi @devang - I think there is still an issue with psychro chart but it’s a new one actually -

The “use-ip” feature doesnt work at all per below and all the units and number remain in SI metric -

import ladybug_comfort
from ladybug.psychchart import PsychrometricChart
from ladybug_charts.utils import Strategy
from ladybug_comfort.chart.polygonpmv import PolygonPMV
from ladybug.epw import EPW, EPWFields
from ladybug.datacollection import HourlyContinuousCollection
from plotly.graph_objects import Figure
from ladybug.color import Colorset, Color
from ladybug.legend import LegendParameters

epw_file = 'sample.epw'
global_epw = EPW(epw_file)


lb_lp = LegendParameters(colors=Colorset.original())
lb_psy = PsychrometricChart(global_epw.dry_bulb_temperature,
                                global_epw.relative_humidity, legend_parameters = lb_lp, use_ip = True)
    
    
        
pmv = PolygonPMV(lb_psy,met_rate=[1.0],clo_value=[0.5])

figure = lb_psy.plot(data=global_epw.import_data_by_field(6), polygon_pmv=pmv,title='PSYCHROMETRIC CHART', show_title=True)

figure

Thanks
AT

Will try to take look this weekend.

1 Like

I took a look and it looks like a bug in ladybug @chriswmackey.

In the script shared by Amir, if you look at the temperature property of the PsychrometricChart object, you will see Celsius in the header. instead of Fahrenheit.

In my local installation of ladybug-charts, I get ladybug-core version 0.42.8 Which is fairly latest. So not sure if this is already fixed.

Hey @devang ,

This is not a bug in the PsychrometricChart class and it is very much intentional. You see that the description of the dry_bulb_temperature property on the PsychrometricChart docs says that the values are always supposed to be in Celsius and the use_ip is the only thing that should be changed in order to get a colored mesh and axes in Fahrenheit.

We need the data collection of dry_bulb_temperature to remain in Celsius because the PMV model is written in terms of Celsius temperature and we don’t want to convert it back when we plot the polygons.

So the bug is in ladybug-charts if the code there was expecting use_ip to change the units of the dry_bulb_temperature data collection. You’ll see that the use_ip option works as designed in the LBT component where we process the temperature like so:

and create the psychrometric chart like so:

I see. So @amirtabadkani, ladybug-charts is not designed to deal with unit as Chris has described. I will try to implement this weekend.

@chriswmackey, I don’t see the dry_bulb_temperature property on the PsychrometricChart object. I also see that you are using the z and original_temperature properties on this object that again I see neither on the documentation nor on the code base. What am I missing?

These are the properties I see.

Thanks @devang - FYI, I could find a workaround for use_ip function (replaced with a customized function) to change the legend units but still since the other functions like x_axis_text relies on use_ip function, I dont have the possibility to change the axis titles units and values on the chart - and apparently, the x-dim and y-dim function are not working either - but hopefully if you can fix the use_ip feature, many of these bugs should be fixed too - thanks

A post was split to a new topic: Windrose legend issue

@amirtabadkani, I have raised a PR to add support for IP unit on the Psychrometric chart. Will post here once it is merged.

And yes. Always best to create a separate post for a separate topic.

2 Likes

@amirtabadkani, please give it a try with version 1.19.2

1 Like

Thanks @devang - it perfectly works with annual datacollections now but still has the issue if you input single values for dbt and rh -

lb_psy_ext = PsychrometricChart(24,45, use_ip = True)

and additionally:

  1. shouldnt the legend be translated as number of hours instead of degrees? we can see the degrees on x-axis already

  1. when we change the units to IP, the PolygonPMV - evaluate_polygon function does not work
pmv = PolygonPMV(lb_psy,rad_temperature = None, met_rate=[psy_met_value],clo_value=[psy_clo_value], air_speed = [psy_air], comfort_parameter = pmv_param )
        
        #Extracting values for the report
        internal_heat_pc = round(sum(pmv.evaluate_polygon(pmv.internal_heat_polygon(),0.01)),2)/100
        Fan_pc = round(sum(pmv.evaluate_polygon(pmv.fan_use_polygon(1),0.01)),2)/100
        nightflush_pc = round(sum(pmv.evaluate_polygon(pmv.night_flush_polygon(),0.01)),2)/100
        evaluate_passive_solar = pmv.evaluate_passive_solar(global_epw.global_horizontal_radiation,50,8,12.8)
        passivesolar_pc = round(sum(pmv.evaluate_polygon(pmv.passive_solar_polygon(evaluate_passive_solar[1]),0.01)),2)/100
        evap_clg_pc = round(sum(pmv.evaluate_polygon(pmv.evaporative_cooling_polygon(),0.01)),2)/100
        
        comfort_value_pc = round((pmv.merged_comfort_data.total/8760)*100,2)
        
        strategies_percentages = [internal_heat_pc,Fan_pc,nightflush_pc,passivesolar_pc,evap_clg_pc,comfort_value_pc]

@amirtabadkani,

  1. If you observe the first error, it is coming out of ladybug library. This error happens before the pointer reaches the ladybug-charts library. Chris might be able to help. For that, I think you should share a minimum script that he will need to reproduce the error.
  2. You’ll see number of hours if you don’t pass data. Since you have passed dry-bulb temperature data, the legend plots that.
  3. I am not able to understand the issue here. And again, we’d appreciate if you can share a minimum script to re-produce the error. This error too is coming from ladybug-comfort library and not ladybug-charts.

Sure @devang -

1) I appreciate it if you can help on this @chriswmackey

import ladybug.psychrometrics
import ladybug_comfort
from ladybug.psychchart import PsychrometricChart
from ladybug_comfort.chart.polygonpmv import PolygonPMV

lb_psy_ext = PsychrometricChart(24,45, use_ip = True)
figure = lb_psy_ext.plot(title='PSYCHROMETRIC CHART', show_title=True)
st.plotly_chart(figure , use_container_width=True)

2) Got it - Thanks

3)

import streamlit as st
from ladybug.epw import EPW
import ladybug.psychrometrics
import ladybug_comfort
from ladybug.psychchart import PsychrometricChart
from ladybug_charts.utils import Strategy
from ladybug_comfort.chart.polygonpmv import PolygonPMV
from ladybug.color import Colorset, Color
from ladybug.legend import LegendParameters
from ladybug.analysisperiod import AnalysisPeriod
from ladybug.datacollection import HourlyContinuousCollection
from plotly.graph_objects import Figure



data_unit = st.radio("Choose the Unit for Analysis:", options= ['SI','IP'], key = 'units')

if data_unit == 'SI':
    use_ip_bool = False
elif data_unit == 'IP':
    use_ip_bool = True

epw_file = './assets/sample.epw'
global_epw = EPW(epw_file)

def get_psy_chart_figure(_epw: EPW, global_colorset: str, selected_strategy: str,
                         load_data: str, draw_polygons: bool,
                         _data: HourlyContinuousCollection, use_ip_bool: bool) -> Tuple[Figure, HourlyContinuousCollection, Tuple]:
    """Create psychrometric chart figure.
    Args:
        epw: An EPW object.
        global_colorset: A string representing the name of a Colorset.
        selected_strategy: A string representing the name of a psychrometric strategy.
        load_data: A boolean to indicate whether to load the data.
        draw_polygons: A boolean to indicate whether to draw the polygons.
        data: Hourly data to load on psychrometric chart.
        use_ip_bool: Boolean to have SI-IP conversions up on user selection.
    Returns:
        A plotly figure.
    """

    lb_lp = LegendParameters(colors=colorsets[global_colorset],)
    
    lb_psy = PsychrometricChart(_epw.dry_bulb_temperature,
                                _epw.relative_humidity, legend_parameters=lb_lp, max_humidity_ratio = 0.05, use_ip = use_ip_bool)

    if selected_strategy == 'All':
        strategies = [Strategy.comfort, Strategy.evaporative_cooling,
                      Strategy.mas_night_ventilation, Strategy.occupant_use_of_fans,
                      Strategy.capture_internal_heat, Strategy.passive_solar_heating]
    elif selected_strategy == 'Comfort':
        strategies = [Strategy.comfort]
    elif selected_strategy == 'Evaporative Cooling':
        strategies = [Strategy.evaporative_cooling]
    elif selected_strategy == 'Mass + Night Ventilation':
        strategies = [Strategy.mas_night_ventilation]
    elif selected_strategy == 'Occupant use of fans':
        strategies = [Strategy.occupant_use_of_fans]
    elif selected_strategy == 'Capture internal heat':
        strategies = [Strategy.capture_internal_heat]
    elif selected_strategy == 'Passive solar heating':
        strategies = [Strategy.passive_solar_heating]
        
    pmv_param = ladybug_comfort.parameter.pmv.PMVParameter(10,humid_ratio_upper = None, humid_ratio_lower=None)
    
    if load_data == 'Load Hourly Data':
        
        pmv = PolygonPMV(lb_psy,rad_temperature = None, met_rate=[psy_met_value],clo_value=[psy_clo_value], air_speed = [psy_air], comfort_parameter = pmv_param )
        
        #Extracting values for the report
        internal_heat_pc = round(sum(pmv.evaluate_polygon(pmv.internal_heat_polygon(),0.01)),2)/100
        Fan_pc = round(sum(pmv.evaluate_polygon(pmv.fan_use_polygon(1),0.01)),2)/100
        nightflush_pc = round(sum(pmv.evaluate_polygon(pmv.night_flush_polygon(),0.01)),2)/100
        evaluate_passive_solar = pmv.evaluate_passive_solar(global_epw.global_horizontal_radiation,50,8,12.8)
        passivesolar_pc = round(sum(pmv.evaluate_polygon(pmv.passive_solar_polygon(evaluate_passive_solar[1]),0.01)),2)/100
        evap_clg_pc = round(sum(pmv.evaluate_polygon(pmv.evaporative_cooling_polygon(),0.01)),2)/100
        
        comfort_value_pc = round((pmv.merged_comfort_data.total/8760)*100,2)
        
        strategies_percentages = [internal_heat_pc,Fan_pc,nightflush_pc,passivesolar_pc,evap_clg_pc,comfort_value_pc]
        
        if draw_polygons:

            figure = lb_psy.plot(data=_data, polygon_pmv=pmv,
                                 strategies=strategies,title='PSYCHROMETRIC CHART', show_title=True, solar_data =global_epw.global_horizontal_radiation)
            
        else:
            figure = lb_psy.plot(data=_data, show_title=True)
            
        return figure, strategies_percentages

psy_chart_figure, strategies_percentages= get_psy_chart_figure(
        global_epw, global_colorset, psy_selected_strategy, psy_radio,
        psy_draw_polygons,psy_data, use_ip_bool)
    
    
st.plotly_chart(psy_chart_figure, use_container_width=True, config=get_figure_config(f'Psychrometric_chart_{global_epw.location.city}'))

Thanks for fixing this, @devang .

You aren’t missing anything here, @devang and sorry if I made things more confusing.

I mistakenly referred to the PsychrometricChart.temperature as dry_bulb_temperature in my previous post. I forgot that I just call it temperature since it can be interpreted either as operative temperature or air (dry bulb) temperature.

As for the properties of z and original_temperature, these are not official properties on the PsychrometricChart class, which is why they don’t appear in the docs. The class in ladybug-core is written purely as a 2D object and so a z property has no meaning. However, in Grasshopper world, I wanted to have some way to pass the Z coordinate of the psychrometric chart base point to the component that plots the PMV or UTCI comfort polygons on the chart. So I just assigned it as an unofficial attribute to the object that gets passed between the two. The same thing is true of original_temperature. Long story short, you shouldn’t be expecting these properties to exist anywhere else other than Grasshopper since they are things that I added specifically to help with GH workflows.

Hey, @amirtabadkani .

The primary issue with your sample code is that you are trying to plot a temperature value of 24C on a chart that has default min/max temperatures of -20F to 50F. Since 24C is equal to 72.5F, this temperature value does not fit on the chart and so you end up with a chart that has nothing on it. Hence, the exception being thrown.

Change your code to something like this where the min and max temperatures are specified in terms of Farenheit:

import ladybug.psychrometrics
import ladybug_comfort
from ladybug.psychchart import PsychrometricChart
from ladybug_comfort.chart.polygonpmv import PolygonPMV

import ladybug.psychrometrics
import ladybug_comfort
from ladybug.psychchart import PsychrometricChart
from ladybug_comfort.chart.polygonpmv import PolygonPMV

lb_psy_ext = PsychrometricChart(
    24, 45, min_temperature=0, max_temperature=120, use_ip=True)
figure = lb_psy_ext.plot(title='PSYCHROMETRIC CHART', show_title=True)

Then you should get the desired result.

Thanks @chriswmackey - you re right I was confused with units as you keep the main dbt and rh inputs in C while you change the min/max temps based on F unit - can you also please advise on the 3rd issue above when you get the chance?

Cheers

Hey @amirtabadkani ,

Yea, I realize some decisions I made about how to support IP may make perfect sense from inside the class but can be confusing from the outside. I tried to document in the docstings what units are supposed to be used where for the different input arguments.

Issue 3 is a bug in your code. pmv.fan_use_polygon(1) returns None for your case, perhaps because it does not fit on the chart. The docstring is clear that this is a possible outcome:

So I recommend checking that the result is not None before you go to evaluate_polygon.

Update - I revised that section with a conditional statement to avoid getting that error and its fine now - Thanks a lot @chriswmackey

internal_heat_pc = round(sum(pmv.evaluate_polygon(pmv.internal_heat_polygon(),0.01)),2)/100
        if pmv.fan_use_polygon(psy_air) is not None:
            Fan_pc = round(pmv.evaluate_polygon(pmv.fan_use_polygon(psy_air),0.01),2)/100
        else:
            Fan_pc = 0
        if pmv.night_flush_polygon() is not None:
            nightflush_pc = round(sum(pmv.evaluate_polygon(pmv.night_flush_polygon(),0.01)),2)/100
        else:
            nightflush_pc = 0
        evaluate_passive_solar = pmv.evaluate_passive_solar(global_epw.global_horizontal_radiation,50,8,12.8)
        passivesolar_pc = round(sum(pmv.evaluate_polygon(pmv.passive_solar_polygon(evaluate_passive_solar[1]),0.01)),2)/100
        if pmv.evaporative_cooling_polygon() is not None:
            evap_clg_pc = round(sum(pmv.evaluate_polygon(pmv.evaporative_cooling_polygon(),0.01)),2)/100
        else:
            evap_clg_pc = 0

        comfort_value_pc = round((pmv.merged_comfort_data.total/8760)*100,2)
        
        strategies_percentages = [internal_heat_pc,Fan_pc,nightflush_pc,passivesolar_pc,evap_clg_pc,comfort_value_pc]```
1 Like