From a50f91ee0c55a1ee8dcb0702934b0b7e983ebf8e Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Tue, 5 Mar 2019 22:18:55 -0500 Subject: [PATCH 1/2] feat(datatype): Add SpecificEnergy and Enthalpy --- ladybug/datatype/specificenergy.py | 77 ++++++++++++++++++++++++++++++ ladybug/datatype/temperature.py | 4 ++ tests/datatype_test.py | 24 +++++++++- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 ladybug/datatype/specificenergy.py diff --git a/ladybug/datatype/specificenergy.py b/ladybug/datatype/specificenergy.py new file mode 100644 index 00000000..28b79b4e --- /dev/null +++ b/ladybug/datatype/specificenergy.py @@ -0,0 +1,77 @@ +# coding=utf-8 +"""Energy data type.""" +from __future__ import division + +from .base import DataTypeBase + + +class SpecificEnergy(DataTypeBase): + """Energy""" + _units = ('kWh/kg', 'kBtu/lb', 'Wh/kg', 'Btu/lb', 'J/kg', 'kJ/kg') + _si_units = ('kWh/kg', 'Wh/kg', 'J/kg', 'kJ/kg') + _ip_units = ('Btu/lb', 'kBtu/lb') + _abbreviation = 'E/m' + _point_in_time = False + _cumulative = True + + def _kWh_kg_to_kBtu_lb(self, value): + return value * 1.54772 + + def _kWh_kg_to_Wh_kg(self, value): + return value * 1000. + + def _kWh_kg_to_Btu_lb(self, value): + return value * 1547.72 + + def _kWh_kg_to_J_kg(self, value): + return value * 3600000. + + def _kWh_kg_to_kJ_kg(self, value): + return value * 3600. + + def _kBtu_lb_to_kWh_kg(self, value): + return value / 1.54772 + + def _Wh_kg_to_kWh_kg(self, value): + return value / 1000. + + def _Btu_lb_to_kWh_kg(self, value): + return value / 1547.72 + + def _J_kg_to_kWh_kg(self, value): + return value / 3600000. + + def _kJ_kg_to_kWh_kg(self, value): + return value / 3600. + + def to_unit(self, values, unit, from_unit): + """Return values converted to the unit given the input from_unit.""" + return self._to_unit_base('kWh/kg', values, unit, from_unit) + + def to_ip(self, values, from_unit): + """Return values in IP and the units to which the values have been converted.""" + if from_unit in self.ip_units: + return values, from_unit + elif from_unit == 'kJ/kg': + return self.to_unit(values, 'Btu/lb', from_unit), 'Btu/lb' + else: + return self.to_unit(values, 'kBtu/lb', from_unit), 'kBtu/lb' + + def to_si(self, values, from_unit): + """Return values in SI and the units to which the values have been converted.""" + if from_unit in self.si_units: + return values, from_unit + elif from_unit == 'Btu/lb': + return self.to_unit(values, 'kJ/kg', from_unit), 'kJ/kg' + else: + return self.to_unit(values, 'kWh/kg', from_unit), 'kWh/kg' + + @property + def isSpecificEnergy(self): + """Return True.""" + return True + + +class Enthalpy(SpecificEnergy): + _abbreviation = 'Enth' + _min = 0 diff --git a/ladybug/datatype/temperature.py b/ladybug/datatype/temperature.py index f971f46e..358aea05 100644 --- a/ladybug/datatype/temperature.py +++ b/ladybug/datatype/temperature.py @@ -63,6 +63,10 @@ class DewPointTemperature(Temperature): _missing_epw = 99.9 +class WetBulbTemperature(Temperature): + _abbreviation = 'WBT' + + class SkyTemperature(Temperature): _abbreviation = 'Tsky' diff --git a/tests/datatype_test.py b/tests/datatype_test.py index f29d6533..602d5a48 100644 --- a/tests/datatype_test.py +++ b/tests/datatype_test.py @@ -6,7 +6,7 @@ from ladybug.datatype import angle, area, distance, energy, energyflux, \ energyintensity, generic, illuminance, luminance, mass, massflowrate, \ percentage, power, pressure, rvalue, speed, temperature, temperaturedelta, \ - thermalcondition, uvalue, volume, volumeflowrate + thermalcondition, specificenergy, uvalue, volume, volumeflowrate import unittest import pytest @@ -484,6 +484,28 @@ def test_thermal_condition(self): assert tc_type.to_unit([1], 'PMV', 'condition')[0] == 1 assert tc_type.to_unit([1], 'condition', 'PMV')[0] == 1 + def test_specific_energy(self): + """Test ThermalCondition type.""" + tc_type = specificenergy.SpecificEnergy() + for unit in tc_type.units: + assert tc_type.to_unit([1], unit, unit)[0] == pytest.approx(1, rel=1e-5) + ip_vals, ip_u = tc_type.to_ip([1], unit) + assert len(ip_vals) == 1 + si_vals, si_u = tc_type.to_si([1], unit) + assert len(si_vals) == 1 + for other_unit in tc_type.units: + assert len(tc_type.to_unit([1], other_unit, unit)) == 1 + assert tc_type.to_unit([1], 'kBtu/lb', 'kWh/kg')[0] == pytest.approx(1.54772, rel=1e-4) + assert tc_type.to_unit([1], 'Btu/lb', 'kWh/kg')[0] == pytest.approx(1547.72, rel=1e-2) + assert tc_type.to_unit([1], 'Wh/kg', 'kWh/kg')[0] == pytest.approx(1000, rel=1e-1) + assert tc_type.to_unit([1], 'J/kg', 'kWh/kg')[0] == pytest.approx(3600000, rel=1e-1) + assert tc_type.to_unit([1], 'kJ/kg', 'kWh/kg')[0] == pytest.approx(3600, rel=1e-1) + assert tc_type.to_unit([1], 'kWh/kg', 'kBtu/lb')[0] == pytest.approx(0.646111699, rel=1e-5) + assert tc_type.to_unit([1], 'kWh/kg', 'Btu/lb')[0] == pytest.approx(0.00064611169979, rel=1e-7) + assert tc_type.to_unit([1], 'kWh/kg', 'Wh/kg')[0] == pytest.approx(0.001, rel=1e-3) + assert tc_type.to_unit([1], 'kWh/kg', 'J/kg')[0] == pytest.approx(2.7777777777777776e-07, rel=1e-9) + assert tc_type.to_unit([1], 'kWh/kg', 'kJ/kg')[0] == pytest.approx(0.0002777777777777778, rel=1e-7) + if __name__ == "__main__": unittest.main() From 8a5c5d9207d49aa00eae432c37cd9206b7c4bbe5 Mon Sep 17 00:00:00 2001 From: Chris Mackey Date: Wed, 6 Mar 2019 23:25:17 -0500 Subject: [PATCH 2/2] feat(datatype): Add Degree-Days and Degree-Hours I also really started regretting that the data type for handling fractional values was written as 'percentage' instead of 'fraction.' It seems that I was narrow-mindedly thinking of relative humidity in EPW files when I set up that type. So I am fixing it here before we start having more dependencies on it. Right now, it shouldn't really break anything outdside of the API. --- .../datatype/{percentage.py => fraction.py} | 86 +++++++++---------- ladybug/datatype/temperaturetime.py | 66 ++++++++++++++ ladybug/designday.py | 6 +- ladybug/epw.py | 14 +-- tests/datacollection_test.py | 2 +- tests/datatype_test.py | 28 ++++-- 6 files changed, 143 insertions(+), 59 deletions(-) rename ladybug/datatype/{percentage.py => fraction.py} (57%) create mode 100644 ladybug/datatype/temperaturetime.py diff --git a/ladybug/datatype/percentage.py b/ladybug/datatype/fraction.py similarity index 57% rename from ladybug/datatype/percentage.py rename to ladybug/datatype/fraction.py index 11c7e28e..c6beea93 100644 --- a/ladybug/datatype/percentage.py +++ b/ladybug/datatype/fraction.py @@ -1,44 +1,44 @@ # coding=utf-8 -"""Percentage data type.""" +"""Fraction data type.""" from __future__ import division from .base import DataTypeBase -class Percentage(DataTypeBase): - """Percentage""" - _units = ('%', 'fraction', 'tenths', 'thousandths', 'okta') - _si_units = ('%', 'fraction', 'tenths', 'thousandths', 'okta') - _ip_units = ('%', 'fraction', 'tenths', 'thousandths', 'okta') +class Fraction(DataTypeBase): + """Fraction""" + _units = ('fraction', '%', 'tenths', 'thousandths', 'okta') + _si_units = ('fraction', '%', 'tenths', 'thousandths', 'okta') + _ip_units = ('fraction', '%', 'tenths', 'thousandths', 'okta') _abbreviation = 'Pct' - def _pct_to_fraction(self, value): - return value / 100. - - def _pct_to_tenths(self, value): - return value / 10. + def _fraction_to_pct(self, value): + return value * 100. - def _pct_to_thousandths(self, value): + def _fraction_to_tenths(self, value): return value * 10. - def _pct_to_okta(self, value): - return value / 12.5 + def _fraction_to_thousandths(self, value): + return value * 1000. - def _fraction_to_pct(self, value): - return value * 100. + def _fraction_to_okta(self, value): + return value * 12.5 - def _tenths_to_pct(self, value): - return value * 10. + def _pct_to_fraction(self, value): + return value / 100. - def _thousandths_to_pct(self, value): + def _tenths_to_fraction(self, value): return value / 10. - def _okta_to_pct(self, value): - return value * 12.5 + def _thousandths_to_fraction(self, value): + return value / 1000. + + def _okta_to_fraction(self, value): + return value / 12.5 def to_unit(self, values, unit, from_unit): """Return values converted to the unit given the input from_unit.""" - return self._to_unit_base('%', values, unit, from_unit) + return self._to_unit_base('fraction', values, unit, from_unit) def to_ip(self, values, from_unit): """Return values in IP and the units to which the values have been converted.""" @@ -49,72 +49,72 @@ def to_si(self, values, from_unit): return values, from_unit @property - def isPercentage(self): + def isFraction(self): """Return True.""" return True -class PercentagePeopleDissatisfied(Percentage): +class PercentagePeopleDissatisfied(Fraction): _min = 0 - _max = 100 + _max = 1 _abbreviation = 'PPD' -class RelativeHumidity(Percentage): +class RelativeHumidity(Fraction): _min = 0 _abbreviation = 'RH' _min_epw = 0 - _max_epw = 110 + _max_epw = 1.1 _missing_epw = 999 -class HumidityRatio(Percentage): +class HumidityRatio(Fraction): _min = 0 - _max = 100 + _max = 1 _abbreviation = 'HR' -class TotalSkyCover(Percentage): +class TotalSkyCover(Fraction): # (used if Horizontal IR Intensity missing) _min = 0 - _max = 100 + _max = 1 _abbreviation = 'CC' _min_epw = 0 - _max_epw = 100 + _max_epw = 1 _missing_epw = 99 -class OpaqueSkyCover(Percentage): +class OpaqueSkyCover(Fraction): # (used if Horizontal IR Intensity missing) _min = 0 - _max = 100 + _max = 1 _abbreviation = 'OSC' _min_epw = 0 - _max_epw = 100 + _max_epw = 1 _missing_epw = 99 -class AerosolOpticalDepth(Percentage): +class AerosolOpticalDepth(Fraction): _min = 0 - _max = 100 + _max = 1 _abbreviation = 'AOD' _min_epw = 0 - _max_epw = 100 + _max_epw = 1 _missing_epw = 0.999 -class Albedo(Percentage): +class Albedo(Fraction): _min = 0 - _max = 100 + _max = 1 _abbreviation = 'a' _min_epw = 0 - _max_epw = 100 + _max_epw = 1 _missing_epw = 0.999 -class LiquidPrecipitationQuantity(Percentage): +class LiquidPrecipitationQuantity(Fraction): _min = 0 _abbreviation = 'LPQ' _min_epw = 0 - _max_epw = 100 + _max_epw = 1 _missing_epw = 99 diff --git a/ladybug/datatype/temperaturetime.py b/ladybug/datatype/temperaturetime.py new file mode 100644 index 00000000..6f85934b --- /dev/null +++ b/ladybug/datatype/temperaturetime.py @@ -0,0 +1,66 @@ +# coding=utf-8 +"""Temperature-Time data type.""" +from __future__ import division + +from .base import DataTypeBase + + +class TemperatureTime(DataTypeBase): + """Temperature-Time""" + _units = ('degC-days', 'degF-days', 'degC-hours', 'degF-hours') + _si_units = ('degC-days', 'degC-hours') + _ip_units = ('degF-days', 'degF-hours') + _abbreviation = 'degTime' + + def _degCdays_to_degFdays(self, value): + return value * 9. / 5. + + def _degCdays_to_degChours(self, value): + return value * 24. + + def _degCdays_to_degFhours(self, value): + return value * 24. * 9. / 5. + + def _degFdays_to_degCdays(self, value): + return value * 5. / 9. + + def _degChours_to_degCdays(self, value): + return value / 24. + + def _degFhours_to_degCdays(self, value): + return (value / 24.) * 5. / 9. + + def to_unit(self, values, unit, from_unit): + """Return values converted to the unit given the input from_unit.""" + return self._to_unit_base('degC-days', values, unit, from_unit) + + def to_ip(self, values, from_unit): + """Return values in IP and the units to which the values have been converted.""" + if from_unit in self._ip_units: + return values, from_unit + elif from_unit == 'degC-hours': + return self.to_unit(values, 'degF-hours', from_unit), 'degF-hours' + else: + return self.to_unit(values, 'degF-days', from_unit), 'degF-days' + + def to_si(self, values, from_unit): + """Return values in SI and the units to which the values have been converted.""" + if from_unit in self._si_units: + return values, from_unit + elif from_unit == 'degF-hours': + return self.to_unit(values, 'degC-hours', from_unit), 'degC-hours' + else: + return self.to_unit(values, 'degC-days', from_unit), 'degC-days' + + @property + def isTemperatureTime(self): + """Return True.""" + return True + + +class CoolingDegreeTime(TemperatureTime): + _abbreviation = 'coolTime' + + +class HeatingDegreeTime(TemperatureTime): + _abbreviation = 'heatTime' diff --git a/ladybug/designday.py b/ladybug/designday.py index 1086460c..dfd7f9b3 100644 --- a/ladybug/designday.py +++ b/ladybug/designday.py @@ -10,7 +10,7 @@ from .sunpath import Sunpath from .datatype import angle, energyflux, energyintensity, \ - percentage, pressure, speed, temperature + fraction, pressure, speed, temperature from .skymodel import ashrae_revised_clear_sky, \ ashrae_clear_sky, calc_horizontal_infrared @@ -663,7 +663,7 @@ def hourly_relative_humidity(self): rh_data = [rel_humid_from_db_dpt(x, y) for x, y in zip( self._dry_bulb_condition.hourly_values, dpt_data)] return self._get_daily_data_collections( - percentage.RelativeHumidity(), '%', rh_data) + fraction.RelativeHumidity(), '%', rh_data) @property def hourly_barometric_pressure(self): @@ -705,7 +705,7 @@ def hourly_solar_radiation(self): def hourly_sky_cover(self): """A data collection containing hourly sky cover values in tenths.""" return self._get_daily_data_collections( - percentage.TotalSkyCover(), 'tenths', self._sky_condition.hourly_sky_cover) + fraction.TotalSkyCover(), 'tenths', self._sky_condition.hourly_sky_cover) @property def hourly_horizontal_infrared(self): diff --git a/ladybug/epw.py b/ladybug/epw.py index 368b555d..f25b8dc7 100644 --- a/ladybug/epw.py +++ b/ladybug/epw.py @@ -8,7 +8,7 @@ from .header import Header from .analysisperiod import AnalysisPeriod from .datatype import angle, distance, energyflux, energyintensity, generic, \ - illuminance, luminance, percentage, pressure, speed, temperature + illuminance, luminance, fraction, pressure, speed, temperature from .skymodel import calc_sky_temperature from .futil import write_to_file @@ -1433,7 +1433,7 @@ class EPWFields(object): 'unit': 'C' }, - 8: {'name': percentage.RelativeHumidity(), + 8: {'name': fraction.RelativeHumidity(), 'type': int, 'unit': '%' }, @@ -1503,12 +1503,12 @@ class EPWFields(object): 'unit': 'm/s' }, - 22: {'name': percentage.TotalSkyCover(), # used if Horizontal IR is missing + 22: {'name': fraction.TotalSkyCover(), # used if Horizontal IR is missing 'type': int, 'unit': 'tenths' }, - 23: {'name': percentage.OpaqueSkyCover(), # used if Horizontal IR is missing + 23: {'name': fraction.OpaqueSkyCover(), # used if Horizontal IR is missing 'type': int, 'unit': 'tenths' }, @@ -1540,7 +1540,7 @@ class EPWFields(object): 'unit': 'mm' }, - 29: {'name': percentage.AerosolOpticalDepth(), + 29: {'name': fraction.AerosolOpticalDepth(), 'type': float, 'unit': 'fraction' }, @@ -1556,7 +1556,7 @@ class EPWFields(object): 'unit': 'day' }, - 32: {'name': percentage.Albedo(), + 32: {'name': fraction.Albedo(), 'type': float, 'unit': 'fraction' }, @@ -1566,7 +1566,7 @@ class EPWFields(object): 'unit': 'mm' }, - 34: {'name': percentage.LiquidPrecipitationQuantity(), + 34: {'name': fraction.LiquidPrecipitationQuantity(), 'type': float, 'unit': 'fraction' } diff --git a/tests/datacollection_test.py b/tests/datacollection_test.py index 6af419d9..52f059c1 100644 --- a/tests/datacollection_test.py +++ b/tests/datacollection_test.py @@ -10,7 +10,7 @@ from ladybug.dt import DateTime from ladybug.datatype.generic import GenericType from ladybug.datatype.temperature import Temperature -from ladybug.datatype.percentage import RelativeHumidity, HumidityRatio +from ladybug.datatype.fraction import RelativeHumidity, HumidityRatio from ladybug.epw import EPW from ladybug.psychrometrics import humid_ratio_from_db_rh diff --git a/tests/datatype_test.py b/tests/datatype_test.py index 602d5a48..d3a9c604 100644 --- a/tests/datatype_test.py +++ b/tests/datatype_test.py @@ -4,8 +4,8 @@ from ladybug import datatype from ladybug.datatype import base from ladybug.datatype import angle, area, distance, energy, energyflux, \ - energyintensity, generic, illuminance, luminance, mass, massflowrate, \ - percentage, power, pressure, rvalue, speed, temperature, temperaturedelta, \ + energyintensity, fraction, generic, illuminance, luminance, mass, massflowrate, \ + power, pressure, rvalue, speed, temperature, temperaturedelta, temperaturetime, \ thermalcondition, specificenergy, uvalue, volume, volumeflowrate import unittest @@ -94,9 +94,27 @@ def test_temperaturedelta(self): assert temp_type.to_unit([1], 'C', 'F')[0] == pytest.approx(0.5555, rel=1e-1) assert temp_type.to_unit([1], 'C', 'K')[0] == pytest.approx(1, rel=1e-1) - def test_percentage(self): - """Test Percentage type.""" - pct_type = percentage.Percentage() + def test_temperaturetime(self): + """Test TemperatureTime type.""" + temp_type = temperaturetime.TemperatureTime() + for unit in temp_type.units: + assert temp_type.to_unit([1], unit, unit)[0] == pytest.approx(1, rel=1e-5) + ip_vals, ip_u = temp_type.to_ip([1], unit) + assert len(ip_vals) == 1 + si_vals, si_u = temp_type.to_si([1], unit) + assert len(si_vals) == 1 + for other_unit in temp_type.units: + assert len(temp_type.to_unit([1], other_unit, unit)) == 1 + assert temp_type.to_unit([1], 'degF-days', 'degC-days')[0] == pytest.approx(1.8, rel=1e-1) + assert temp_type.to_unit([1], 'degC-hours', 'degC-days')[0] == pytest.approx(24, rel=1e-3) + assert temp_type.to_unit([1], 'degF-hours', 'degC-days')[0] == pytest.approx(43.2, rel=1e-1) + assert temp_type.to_unit([1], 'degC-days', 'degF-days')[0] == pytest.approx(0.5555, rel=1e-1) + assert temp_type.to_unit([1], 'degC-days', 'degC-hours')[0] == pytest.approx(1 / 24, rel=1e-1) + assert temp_type.to_unit([1], 'degC-days', 'degF-hours')[0] == pytest.approx(0.023148, rel=1e-1) + + def test_fraction(self): + """Test Fraction type.""" + pct_type = fraction.Fraction() for unit in pct_type.units: assert pct_type.to_unit([1], unit, unit)[0] == pytest.approx(1, rel=1e-5) ip_vals, ip_u = pct_type.to_ip([1], unit)