def test_wet_bulb_temperature_empiric(self): """Empiric wet bulb temperature from dry bulb and relative humidity.""" from psychrochart.equations import ( wet_bulb_temperature_empiric, wet_bulb_temperature) from psychrochart.util import f_range for dry_temp_c in f_range(-20, 50, 2.5): for relative_humid in f_range(0.05, 1.0001, 0.05): wet_temp_c_ref = wet_bulb_temperature( dry_temp_c, relative_humid) wet_temp_c = wet_bulb_temperature_empiric( dry_temp_c, relative_humid) if -2.33 * dry_temp_c + 28.33 < relative_humid: if abs(wet_temp_c - wet_temp_c_ref) > 1: print('DT: {:.2f}, HR: {:.2f} => WT: [Aprox: {:.2f}, ' 'Iter: {:.2f} -> ∆: {:.2f}]'.format( dry_temp_c, relative_humid, wet_temp_c, wet_temp_c_ref, abs(wet_temp_c - wet_temp_c_ref))) assert abs(wet_temp_c - wet_temp_c_ref) < 1.5 else: print('Difference between methods: {:.2f} vs ref={:.2f}' .format(wet_temp_c, wet_temp_c_ref)) # out of range assert abs(wet_temp_c - wet_temp_c_ref) > 0.
def test_wet_bulb_temperature(self): """Wet bulb temperature from dry bulb temp and relative humidity.""" from psychrochart.equations import ( wet_bulb_temperature, relative_humidity_from_temps, PRESSURE_STD_ATM_KPA) from psychrochart.util import f_range precision = 0.00001 p_atm = PRESSURE_STD_ATM_KPA for dry_temp_c in f_range(-10, 60, 2.5): for relative_humid in f_range(0.05, 1.0001, 0.05): wet_temp_c = wet_bulb_temperature( dry_temp_c, relative_humid, p_atm_kpa=p_atm, precision=precision) rh_calc = relative_humidity_from_temps( dry_temp_c, wet_temp_c, p_atm_kpa=p_atm) # print('wet_temp_c(dbt, rh): {:.3f} ºC ({:.1f} ºC, {:.1f} %)' # .format(wet_temp_c, dry_temp_c, relative_humid * 100)) self.assertAlmostEqual(relative_humid, rh_calc, delta=0.01) precision = 0.00001 p_atm = PRESSURE_STD_ATM_KPA * .75 for dry_temp_c in f_range(-5, 50, 5): for relative_humid in f_range(0.05, 1.0001, 0.1): wet_temp_c = wet_bulb_temperature( dry_temp_c, relative_humid, p_atm_kpa=p_atm, precision=precision) rh_calc = relative_humidity_from_temps( dry_temp_c, wet_temp_c, p_atm_kpa=p_atm) # print('wet_temp_c(dbt, rh): {:.3f} ºC ({:.1f} ºC, {:.1f} %)' # .format(wet_temp_c, dry_temp_c, relative_humid * 100)) self.assertAlmostEqual(relative_humid, rh_calc, delta=0.01)
def _gen_list_curves_range_temps( func_curve: Callable, dbt_min: float, dbt_max: float, increment: float, curves_values: list, p_atm_kpa: float=PRESSURE_STD_ATM_KPA) -> Tuple[List[float], List[List[float]]]: """Generate a curve from a range of temperatures.""" temps = f_range(dbt_min, dbt_max + increment, increment) curves = [func_curve(temps, value, p_atm_kpa) for value in curves_values] return temps, curves
def test_plot_curve(self): """Test the plotting of PsychroCurve objects.""" import matplotlib.pyplot as plt x_data = f_range(0, 50, 1) y_data = f_range(0, 50, 1) style = {"color": "k", "linewidth": 0.5, "linestyle": "-"} curve = PsychroCurve(x_data, y_data, style) # Plotting ax = plt.subplot() ax = curve.plot(ax) # Vertical line vertical_curve = PsychroCurve([25, 25], [2, 48]) vertical_curve.plot(ax) # Add label vertical_curve.add_label(ax, 'TEST', va='baseline', ha='center')
def test_curve(self): """Test the PsychroCurve object.""" x_data = f_range(0, 50, 1) y_data = f_range(0, 50, 1) style = {"color": "k", "linewidth": 0.5, "linestyle": "-"} empty_curve = PsychroCurve() self.assertDictEqual(empty_curve.to_dict(), {}) curve = PsychroCurve(x_data, y_data, style) # Dict export and import: d_curve = curve.to_dict() curve_d = PsychroCurve(**d_curve) self.assertCountEqual(curve.x_data, curve_d.x_data) self.assertListEqual(curve.x_data, curve_d.x_data) self.assertCountEqual(curve.y_data, curve_d.y_data) self.assertListEqual(curve.y_data, curve_d.y_data) self.assertDictEqual(curve.style, curve_d.style) self.assertDictEqual(d_curve, curve_d.to_dict()) # JSON import export json_curve = curve.to_json() curve_js = PsychroCurve() self.assertEqual(str(curve_js), '<Empty PsychroCurve (label: None)>') if curve_js: # Existence test raise AssertionError() curve_js = curve_js.from_json(json_curve) if not curve_js: # Existence test raise AssertionError() self.assertEqual(str(curve_js), '<PsychroCurve 50 values (label: None)>') self.assertCountEqual(curve.x_data, curve_js.x_data) self.assertListEqual(curve.x_data, curve_js.x_data) self.assertCountEqual(curve.y_data, curve_js.y_data) self.assertListEqual(curve.y_data, curve_js.y_data) self.assertDictEqual(curve.style, curve_js.style) self.assertDictEqual(json.loads(json_curve), json.loads(curve_js.to_json()))
def test_dew_point_temperature(self): """Dew point temperature testing.""" from psychrochart.equations import ( saturation_pressure_water_vapor, dew_point_temperature) from psychrochart.util import f_range temps = f_range(-20, 60, 1) for t in temps: p_sat = saturation_pressure_water_vapor(t) # w_sat = humidity_ratio(p_sat, p_atm_kpa=p_atm) temp_dp_sat = dew_point_temperature(p_sat) if temp_dp_sat < 0: self.assertAlmostEqual(temp_dp_sat, t, delta=3) else: self.assertAlmostEqual(temp_dp_sat, t, delta=2.5)
def _make_zone_dbt_rh( t_min: float, t_max: float, increment: float, rh_min: float, rh_max: float, p_atm_kpa: float=PRESSURE_STD_ATM_KPA, style: dict=None, label: str=None, logger=None) -> PsychroCurve: """Generate points for zone between constant dry bulb temps and RH.""" temps = f_range(t_min, t_max + increment, increment) curve_rh_up = curve_constant_humidity_ratio(temps, rh_max, p_atm_kpa) curve_rh_down = curve_constant_humidity_ratio(temps, rh_min, p_atm_kpa) abs_humid = (curve_rh_up + curve_rh_down[::-1] + [curve_rh_up[0]]) # type: List[float] temps_zone = temps + temps[::-1] + [temps[0]] # type: List[float] return PsychroCurve(temps_zone, abs_humid, style, type_curve='constant_rh_data', label=label, logger=logger)
def plot(self, ax: Axes=None) -> Axes: """Plot the psychrochart and return the matplotlib Axes instance.""" def _apply_spines_style(axes, style, location='right'): for key in style: if (key == 'color') or (key == 'c'): axes.spines[location].set_color(style[key]) elif (key == 'linewidth') or (key == 'lw'): axes.spines[location].set_linewidth(style[key]) elif (key == 'linestyle') or (key == 'ls'): axes.spines[location].set_linestyle(style[key]) else: # pragma: no cover try: getattr(axes.spines[location], 'set_{}'.format(key))(style[key]) except Exception as exc: self._print_err( "Error trying to apply spines attrs: %s. (%s)", exc, dir(axes.spines[location])) # Prepare fig & axis fig_params = self.figure_params.copy() figsize = fig_params.pop('figsize', (16, 9)) position = fig_params.pop('position', [0.025, 0.075, 0.925, 0.875]) fontsize = fig_params.pop('fontsize', 10) x_style = fig_params.pop('x_axis', {}) x_style_labels = fig_params.pop('x_axis_labels', {}) x_style_ticks = fig_params.pop('x_axis_ticks', {}) y_style = fig_params.pop('y_axis', {}) y_style_labels = fig_params.pop('y_axis_labels', {}) y_style_ticks = fig_params.pop('y_axis_ticks', {}) partial_axis = fig_params.pop('partial_axis', True) # Create figure and format axis self._fig = figure.Figure(figsize=figsize, dpi=150, frameon=False) self._canvas = FigureCanvas(self._fig) if ax is None: ax = self._fig.gca(position=position) ax.yaxis.tick_right() ax.yaxis.set_label_position("right") ax.set_xlim(self.dbt_min, self.dbt_max) ax.set_ylim(self.w_min, self.w_max) ax.grid(False, which='major', axis='both') ax.grid(False, which='minor', axis='both') # Apply axis styles if fig_params['x_label'] is not None: style_axis = x_style_labels.copy() style_axis['fontsize'] *= 1.2 ax.set_xlabel(fig_params['x_label'], **style_axis) if fig_params['y_label'] is not None: style_axis = y_style_labels.copy() style_axis['fontsize'] *= 1.2 ax.set_ylabel(fig_params['y_label'], **style_axis) if fig_params['title'] is not None: ax.set_title(fig_params['title'], fontsize=fontsize * 1.5, fontweight='bold') _apply_spines_style(ax, y_style, location='right') _apply_spines_style(ax, x_style, location='bottom') if partial_axis: # Hide left and top axis ax.spines['left'].set_visible(False) ax.spines['top'].set_visible(False) else: _apply_spines_style(ax, y_style, location='left') _apply_spines_style(ax, x_style, location='top') if x_style_ticks: ax.tick_params(axis='x', **x_style_ticks) if y_style_ticks: ax.tick_params(axis='y', **y_style_ticks) if self.chart_params.get("with_constant_dry_temp", True): step_label = self.chart_params.get( "constant_temp_label_step", None) if step_label: # Explicit xticks ticks = f_range(self.dbt_min, self.dbt_max + step_label / 10, step_label) if not self.chart_params.get( "constant_temp_label_include_limits", True): ticks = [t for t in ticks if t not in [self.dbt_min, self.dbt_max]] ax.set_xticks(ticks) ax.set_xticklabels( ['{:g}'.format(t) for t in ticks], **x_style_labels) else: ax.set_xticks([]) if self.chart_params.get("with_constant_humidity", True): step_label = self.chart_params.get( "constant_humid_label_step", None) if step_label: # Explicit xticks ticks = f_range(self.w_min, self.w_max + step_label / 10, step_label) if not self.chart_params.get( "constant_humid_label_include_limits", True): ticks = [t for t in ticks if t not in [self.w_min, self.w_max]] ax.set_yticks(ticks) ax.set_yticklabels( ['{:g}'.format(t) for t in ticks], **y_style_labels) else: ax.set_yticks([]) # Plot curves: [getattr(self, curve_family).plot(ax) for curve_family in PSYCHRO_CURVES_KEYS if getattr(self, curve_family) is not None] # Plot zones: [zone.plot(ax=ax) for zone in self.zones] # Set the Axes object self._axes = ax return ax
def _make_chart_data(self, styles: Union[dict, str]=None, zones_file: Union[dict, str]=None) -> None: """Generate the data to plot the psychrometric chart.""" # Get styling config = load_config(styles) self.d_config = config self.temp_step = config['limits']['step_temp'] self.figure_params = config['figure'] self.dbt_min, self.dbt_max = config['limits']['range_temp_c'] self.w_min, self.w_max = config['limits']['range_humidity_g_kg'] self.chart_params = config['chart_params'] # Base pressure if config['limits'].get('pressure_kpa') is not None: self.p_atm_kpa = config['limits']['pressure_kpa'] elif config['limits'].get('altitude_m') is not None: self.altitude_m = config['limits']['altitude_m'] self.p_atm_kpa = pressure_by_altitude(self.altitude_m) # Dry bulb constant lines (vertical): if self.chart_params["with_constant_dry_temp"]: step = self.chart_params["constant_temp_step"] style = config['constant_dry_temp'] temps_vl = f_range(self.dbt_min, self.dbt_max, step) heights = [1000 * humidity_ratio( saturation_pressure_water_vapor(t), p_atm_kpa=self.p_atm_kpa) for t in temps_vl] self.constant_dry_temp_data = PsychroCurves( [PsychroCurve([t, t], [self.w_min, h], style, type_curve='constant_dry_temp_data', label=None, logger=self._logger) for t, h in zip(temps_vl, heights)], family_label=self.chart_params["constant_temp_label"]) # Absolute humidity constant lines (horizontal): if self.chart_params["with_constant_humidity"]: step = self.chart_params["constant_humid_step"] style = config['constant_humidity'] ws_hl = f_range(self.w_min + step, self.w_max + step / 10, step) dew_points = solve_curves_with_iteration( 'DEW POINT', [x / 1000 for x in ws_hl], lambda x: dew_point_temperature( water_vapor_pressure( x, p_atm_kpa=self.p_atm_kpa)), lambda x: humidity_ratio( saturation_pressure_water_vapor(x), p_atm_kpa=self.p_atm_kpa)) self.constant_humidity_data = PsychroCurves( [PsychroCurve([t_dp, self.dbt_max], [w, w], style, type_curve='constant_humidity_data', label=None, logger=self._logger) for w, t_dp in zip(ws_hl, dew_points)], family_label=self.chart_params["constant_humid_label"]) # Constant relative humidity curves: if self.chart_params["with_constant_rh"]: rh_perc_values = self.chart_params["constant_rh_curves"] rh_label_values = self.chart_params.get("constant_rh_labels", []) label_loc = self.chart_params.get("constant_rh_labels_loc", .85) style = config["constant_rh"] temps_ct_rh, curves_ct_rh = _gen_list_curves_range_temps( curve_constant_humidity_ratio, self.dbt_min, self.dbt_max, self.temp_step, rh_perc_values, p_atm_kpa=self.p_atm_kpa) self.constant_rh_data = PsychroCurves( [PsychroCurve( temps_ct_rh, curve_ct_rh, style, type_curve='constant_rh_data', label_loc=label_loc, label='RH {:g} %'.format(rh) if round(rh, 1) in rh_label_values else None, logger=self._logger) for rh, curve_ct_rh in zip(rh_perc_values, curves_ct_rh)], family_label=self.chart_params["constant_rh_label"]) # Constant enthalpy lines: if self.chart_params["with_constant_h"]: step = self.chart_params["constant_h_step"] start, end = self.chart_params["range_h"] enthalpy_values = f_range(start, end, step) h_label_values = self.chart_params.get("constant_h_labels", []) label_loc = self.chart_params.get("constant_h_labels_loc", 1.) style = config["constant_h"] temps_max_constant_h = [ dry_temperature_for_enthalpy_of_moist_air( self.w_min / 1000, h) for h in enthalpy_values] sat_points = solve_curves_with_iteration( 'ENTHALPHY', enthalpy_values, lambda x: dry_temperature_for_enthalpy_of_moist_air( self.w_min / 1000 + 0.1, x), lambda x: enthalpy_moist_air( x, saturation_pressure_water_vapor(x), p_atm_kpa=self.p_atm_kpa)) self.constant_h_data = PsychroCurves( [PsychroCurve( [t_sat, t_max], [1000 * humidity_ratio( saturation_pressure_water_vapor(t_sat), self.p_atm_kpa), self.w_min], style, type_curve='constant_h_data', label_loc=label_loc, label='{:g} kJ/kg_da'.format(h) if round(h, 3) in h_label_values else None, logger=self._logger) for t_sat, t_max, h in zip( sat_points, temps_max_constant_h, enthalpy_values)], family_label=self.chart_params["constant_h_label"]) # Constant specific volume lines: if self.chart_params["with_constant_v"]: step = self.chart_params["constant_v_step"] start, end = self.chart_params["range_vol_m3_kg"] vol_values = f_range(start, end, step) vol_label_values = self.chart_params.get("constant_v_labels", []) label_loc = self.chart_params.get("constant_v_labels_loc", 1.) style = config["constant_v"] temps_max_constant_v = [ dry_temperature_for_specific_volume_of_moist_air( 0, specific_vol, p_atm_kpa=self.p_atm_kpa) for specific_vol in vol_values] sat_points = solve_curves_with_iteration( 'CONSTANT VOLUME', vol_values, lambda x: dry_temperature_for_specific_volume_of_moist_air( 0, x, p_atm_kpa=self.p_atm_kpa), lambda x: specific_volume( x, saturation_pressure_water_vapor(x), p_atm_kpa=self.p_atm_kpa)) self.constant_v_data = PsychroCurves( [PsychroCurve( [t_sat, t_max], [1000 * humidity_ratio( saturation_pressure_water_vapor(t_sat), self.p_atm_kpa), 0], style, type_curve='constant_v_data', label_loc=label_loc, label='{:g} m3/kg_da'.format(vol) if round(vol, 3) in vol_label_values else None, logger=self._logger) for t_sat, t_max, vol in zip( sat_points, temps_max_constant_v, vol_values)], family_label=self.chart_params["constant_v_label"]) # Constant wet bulb temperature lines: if self.chart_params["with_constant_wet_temp"]: step = self.chart_params["constant_wet_temp_step"] start, end = self.chart_params["range_wet_temp"] wbt_values = f_range(start, end, step) wbt_label_values = self.chart_params.get( "constant_wet_temp_labels", []) label_loc = self.chart_params.get( "constant_wet_temp_labels_loc", .05) style = config["constant_wet_temp"] w_max_constant_wbt = [humidity_ratio( saturation_pressure_water_vapor(wbt), self.p_atm_kpa) for wbt in wbt_values] self.constant_wbt_data = PsychroCurves( [PsychroCurve( [wbt, self.dbt_max], [1000 * w_max, 1000 * humidity_ratio( saturation_pressure_water_vapor(self.dbt_max) * relative_humidity_from_temps( self.dbt_max, wbt, p_atm_kpa=self.p_atm_kpa), p_atm_kpa=self.p_atm_kpa)], style, type_curve='constant_wbt_data', label_loc=label_loc, label='{:g} °C'.format(wbt) if wbt in wbt_label_values else None, logger=self._logger) for wbt, w_max in zip(wbt_values, w_max_constant_wbt)], family_label=self.chart_params["constant_wet_temp_label"]) # Saturation line: if True: sat_style = config["saturation"] temps_sat_line, w_sat_line = _gen_list_curves_range_temps( curve_constant_humidity_ratio, self.dbt_min, self.dbt_max, self.temp_step, [100], p_atm_kpa=self.p_atm_kpa) self.saturation = PsychroCurves( [PsychroCurve( temps_sat_line, w_sat_line[0], sat_style, type_curve='saturation', logger=self._logger)]) # Zones if self.chart_params["with_zones"] or zones_file is not None: self.append_zones(zones_file)
def test_custom_style_psychrochart_2(self): """Test the plot custom styling with dicts.""" import logging from psychrochart.util import f_range custom_style = { 'chart_params': { 'constant_h_label': None, 'constant_h_labels': [30, 40, 50, 60, 70, 80], 'constant_h_step': 5, 'constant_humid_label': None, 'constant_humid_label_include_limits': False, 'constant_humid_label_step': 5, 'constant_humid_step': 2.5, 'constant_rh_curves': [20, 40, 50, 60, 80], 'constant_rh_label': None, 'constant_rh_labels': [20, 30, 40, 50, 60], 'constant_rh_labels_loc': 0.5, 'constant_temp_label': None, 'constant_temp_label_include_limits': False, 'constant_temp_label_step': 10, 'constant_temp_step': 5, 'constant_v_label': None, 'constant_v_labels': [0.83, 0.84, 0.85, 0.86, 0.87, 0.88], 'constant_v_labels_loc': 0.1, 'constant_v_step': 0.01, 'constant_wet_temp_label': None, 'constant_wet_temp_labels': [10, 15, 20, 25], 'constant_wet_temp_step': 5, 'range_wet_temp': [10, 30], 'range_h': [10, 100], 'range_vol_m3_kg': [0.82, 0.9], 'with_constant_dry_temp': True, 'with_constant_h': True, 'with_constant_humidity': True, 'with_constant_rh': True, 'with_constant_v': True, 'with_constant_wet_temp': True, 'with_zones': False }, 'constant_dry_temp': { 'color': [0.855, 0.145, 0.114, 0.7], 'linestyle': ':', 'linewidth': 0.75 }, 'constant_h': { 'color': [0.251, 0.0, 0.502, 0.7], 'linestyle': '-', 'linewidth': 2 }, 'constant_humidity': { 'color': [0.0, 0.125, 0.376, 0.7], 'linestyle': ':', 'linewidth': 0.75 }, 'constant_rh': { 'color': [0.0, 0.498, 1.0, 0.7], 'linestyle': '-.', 'linewidth': 2 }, 'constant_v': { 'color': [0.0, 0.502, 0.337, 0.7], 'linestyle': '-', 'linewidth': 1 }, 'constant_wet_temp': { 'color': [0.498, 0.875, 1.0, 0.7], 'linestyle': '-', 'linewidth': 1 }, 'figure': { 'figsize': [16, 9], 'partial_axis': True, 'position': [0, 0, 1, 1], 'title': None, 'x_axis': { 'color': [0.855, 0.145, 0.114], 'linestyle': '-', 'linewidth': 2 }, 'x_axis_labels': { 'color': [0.855, 0.145, 0.114], 'fontsize': 10 }, 'x_axis_ticks': { 'color': [0.855, 0.145, 0.114], 'direction': 'in', 'pad': -20 }, 'x_label': None, 'y_axis': { 'color': [0.0, 0.125, 0.376], 'linestyle': '-', 'linewidth': 2 }, 'y_axis_labels': { 'color': [0.0, 0.125, 0.376], 'fontsize': 10 }, 'y_axis_ticks': { 'color': [0.0, 0.125, 0.376], 'direction': 'in', 'pad': -20 }, 'y_label': None }, # 'limits': {'range_humidity_g_kg': [2.5, 17.5], 'limits': { 'range_humidity_g_kg': [2.5, 20], 'range_temp_c': [15, 35], 'step_temp': 0.5, 'pressure_kpa': 101.42 }, 'saturation': { 'color': [0.855, 0.145, 0.114], 'linestyle': '-', 'linewidth': 5 }, 'zones': [{ 'label': 'Summer', 'points_x': [23, 28], 'points_y': [40, 60], 'style': { 'edgecolor': [1.0, 0.749, 0.0, 0.8], 'facecolor': [1.0, 0.749, 0.0, 0.2], 'linestyle': '--', 'linewidth': 2 }, 'zone_type': 'dbt-rh' }, { 'label': 'Winter', 'points_x': [18, 23], 'points_y': [35, 55], 'style': { 'edgecolor': [0.498, 0.624, 0.8], 'facecolor': [0.498, 0.624, 1.0, 0.2], 'linestyle': '--', 'linewidth': 2 }, 'zone_type': 'dbt-rh' }] } chart = PsychroChart(custom_style, logger=logging, verbose=True) chart.plot() chart.plot_legend() path_png = os.path.join(basedir, 'test_custom_psychrochart_2.png') chart.save(path_png, transparent=True) chart.close_fig() for p in f_range(90., 105., 1.): print('Trying with P: {} kPa:'.format(p)) custom_style['limits']['pressure_kpa'] = p PsychroChart(custom_style, logger=logging, verbose=True)