From 60fef1680e9484ca5c77f6848b935741ba9b2f21 Mon Sep 17 00:00:00 2001 From: Mikkel Pedersen Date: Fri, 16 Aug 2024 17:47:43 +0200 Subject: [PATCH 1/4] fix(mtx): Add binary option to command --- ladybug_comfort/cli/mtx.py | 22 +++++++++++++--------- ladybug_comfort/map/utci.py | 5 +++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/ladybug_comfort/cli/mtx.py b/ladybug_comfort/cli/mtx.py index 53fb8b83..45cf2b23 100644 --- a/ladybug_comfort/cli/mtx.py +++ b/ladybug_comfort/cli/mtx.py @@ -86,10 +86,14 @@ def mtx(): @click.option('--log-file', '-log', help='Optional log file to output the paths to the ' 'generated CSV files. By default this will be printed out to stdout', type=click.File('w'), default='-', show_default=True) +@click.option('--plain-text/--binary', ' /-b', help='Flag to note whether the ' + 'output should be formatted as a plain text CSV or whether it ' + 'should be formatted as a binary numpy array.', + default=True, show_default=True) def pmv_mtx( temperature_mtx, rel_humidity_mtx, rad_temperature_mtx, rad_delta_mtx, air_speed_mtx, air_speed_json, air_speed, - met_rate, clo_value, write_op_map, comfort_par, folder, log_file + met_rate, clo_value, write_op_map, comfort_par, folder, log_file, plain_text ): """Get CSV files with matrices of PMV comfort from matrices of PMV inputs. @@ -102,20 +106,19 @@ def pmv_mtx( """ try: # load up the matrices of values - air_temp = csv_to_num_matrix(temperature_mtx) - rel_h = csv_to_num_matrix(rel_humidity_mtx) - rad_temp = csv_to_num_matrix(rad_temperature_mtx) \ + air_temp = load_matrix(temperature_mtx) + rel_h = load_matrix(rel_humidity_mtx) + rad_temp = load_matrix(rad_temperature_mtx) \ if rad_temperature_mtx is not None else air_temp if rad_delta_mtx is not None and not os.path.getsize(rad_delta_mtx) == 0: - d_rad_temp = csv_to_num_matrix(rad_delta_mtx) - rad_temp = tuple(tuple(t + dt for t, dt in zip(t_pt, dt_pt)) - for t_pt, dt_pt in zip(rad_temp, d_rad_temp)) + d_rad_temp = load_matrix(rad_delta_mtx) + rad_temp = rad_temp + d_rad_temp mtx_len = len(air_temp[0]) # process any of the other inputs for air speed a_speed = None if air_speed_mtx is not None and os.path.isfile(air_speed_mtx): - a_speed = csv_to_num_matrix(air_speed_mtx) + a_speed = load_matrix(air_speed_mtx).tolist() if a_speed is None and air_speed_json is not None \ and os.path.isfile(air_speed_json): with open(air_speed_json) as json_file: @@ -166,7 +169,8 @@ def pmv_mtx( # write out the final results to CSV files if folder is None: folder = os.path.join(os.path.dirname(temperature_mtx), 'thermal_mtx') - result_file_dict = thermal_map_csv(folder, temper, cond, cond_intensity) + result_file_dict = thermal_map_csv( + folder, temper, cond, cond_intensity, plain_text=plain_text) log_file.write(json.dumps(result_file_dict)) except Exception as e: _logger.exception('Failed to run PMV matrix.\n{}'.format(e)) diff --git a/ladybug_comfort/map/utci.py b/ladybug_comfort/map/utci.py index 8e22d527..dba7d407 100644 --- a/ladybug_comfort/map/utci.py +++ b/ladybug_comfort/map/utci.py @@ -29,7 +29,8 @@ def universal_thermal_climate_index_np(ta, tr, vel, rh): vel = np.where(vel < 0.5, 0.5, np.where(vel > 17, 17, vel)) # metrics derived from the inputs used in the polynomial equation - eh_pa = saturated_vapor_pressure_hpa_np(ta) * (rh / 100.0).astype(np.float32) # partial vapor pressure + eh_pa = saturated_vapor_pressure_hpa_np( + ta) * (rh / 100.0).astype(np.float32) # partial vapor pressure pa_pr = eh_pa / 10.0 # convert vapour pressure to kPa d_tr = tr - ta # difference between radiant and air temperature @@ -70,7 +71,7 @@ def thermal_condition_np(utci, comfort_par): ] choices = [-1, 1] result = np.select(conditions, choices, default=0) - + return result def thermal_condition_eleven_point_np(utci, comfort_par): From f2f5d953bd9faeaf186ec34fcd392826f6c17077 Mon Sep 17 00:00:00 2001 From: Mikkel Pedersen Date: Sat, 17 Aug 2024 04:23:43 +0200 Subject: [PATCH 2/4] fix(helper): Improve binary_to_array It checks if the file has not already been converted to a NumPy file or converted to ASCII (through rmtxop). --- ladybug_comfort/map/_helper.py | 39 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/ladybug_comfort/map/_helper.py b/ladybug_comfort/map/_helper.py index 4ec5884c..1e1a2ae9 100644 --- a/ladybug_comfort/map/_helper.py +++ b/ladybug_comfort/map/_helper.py @@ -12,7 +12,7 @@ def binary_mtx_dimension(filepath): filepath: Full path to Radiance file. Returns: - nrows, ncols, ncomp, line_count + nrows, ncols, ncomp, line_count, fmt """ try: inf = open(filepath, 'rb', encoding='utf-8') @@ -39,6 +39,7 @@ def binary_mtx_dimension(filepath): if line[:6] == 'NCOMP=': ncomp = int(line.split('=')[-1]) if line[:7] == 'FORMAT=': + fmt = line.split('=')[-1] break if not nrows or not ncols: @@ -48,12 +49,14 @@ def binary_mtx_dimension(filepath): f'elements.' ) raise ValueError(error_message) - return nrows, ncols, ncomp, len(header_lines) + 1 + return nrows, ncols, ncomp, len(header_lines) + 1, fmt finally: inf.close() -def binary_to_array(binary_file, nrows=None, ncols=None, ncomp=None, line_count=0): +def binary_to_array( + binary_file, nrows=None, ncols=None, ncomp=None, fmt=None, + line_count=0): """Read a Radiance binary file as a NumPy array. Args: @@ -61,21 +64,43 @@ def binary_to_array(binary_file, nrows=None, ncols=None, ncomp=None, line_count= nrows: Number of rows in the Radiance file. ncols: Number of columns in the Radiance file. ncomp: Number of components of each element in the Radiance file. + fmt: Format of the Radiance file. Can be either "ascii", "float", or "double. line_count: Number of lines to skip in the input file. Usually used to skip the header. Returns: A NumPy array. """ + with open(binary_file, 'rb') as file: + # check if file is NumPy file + numpy_header = file.read(6) + if numpy_header.startswith(b'\x93NUMPY'): + file.seek(0) + array = np.load(file) + return array + file.seek(0) + # check if file has Radiance header, if not it is a text file + radiance_header = file.read(10).decode('utf-8') + if radiance_header != '#?RADIANCE': + file.seek(0) + array = np.genfromtxt(file, dtype=np.float32) + return array + + if (nrows or ncols or ncomp or fmt) is None: + # get nrows, ncols and header line count + nrows, ncols, ncomp, line_count, fmt = binary_mtx_dimension(binary_file) with open(binary_file, 'rb') as reader: - if (nrows or ncols or ncomp) is None: - # get nrows, ncols and header line count - nrows, ncols, ncomp, line_count = binary_mtx_dimension(binary_file) # skip first n lines from reader for i in range(line_count): reader.readline() - array = np.fromfile(reader, dtype=np.float32) + if fmt == 'ascii': + array = np.loadtxt(reader, dtype=np.float32) + elif fmt == 'float': + array = np.fromfile(reader, dtype=np.float32) + elif fmt == 'double': + array = np.fromfile(reader, dtype=np.float64) + if ncomp != 1: array = array.reshape(nrows, ncols, ncomp) else: From d1c26f32382c0ed029938681582b304709f5e64c Mon Sep 17 00:00:00 2001 From: Mikkel Pedersen Date: Sat, 17 Aug 2024 04:48:34 +0200 Subject: [PATCH 3/4] fix(irr): NumPy post-processing and binary output in command --- ladybug_comfort/cli/map.py | 21 +++++++++++++++++---- ladybug_comfort/map/irr.py | 24 +++++++++--------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/ladybug_comfort/cli/map.py b/ladybug_comfort/cli/map.py index b3141622..b70bfe78 100644 --- a/ladybug_comfort/cli/map.py +++ b/ladybug_comfort/cli/map.py @@ -427,9 +427,14 @@ def utci(result_sql, enclosure_info, epw_file, @click.option('--log-file', '-log', help='Optional log file to output the paths to the ' 'generated CSV files. By default this will be printed out to stdout', type=click.File('w'), default='-', show_default=True) +@click.option('--plain-text/--binary', ' /-b', help='Flag to note whether the ' + 'output should be formatted as a plain text CSV or whether it ' + 'should be formatted as a binary numpy array.', + default=True, show_default=True) def irradiance_contrib( result_sql, direct_specular, indirect_specular, ref_specular, - indirect_diffuse, ref_diffuse, sun_up_hours, aperture_id, folder, log_file + indirect_diffuse, ref_diffuse, sun_up_hours, aperture_id, folder, log_file, + plain_text ): """Get CSV files with irradiance contributions from dynamic windows. @@ -466,9 +471,17 @@ def irradiance_contrib( ref_file = os.path.join(out_folder, 'reflected.ill') # write the irradiance matrices into CSV files - _data_to_ill(direct_mtx, direct_file) - _data_to_ill(indirect_mtx, indirect_file) - _data_to_ill(ref_mtx, ref_file) + if plain_text: + _data_to_ill(direct_mtx, direct_file) + _data_to_ill(indirect_mtx, indirect_file) + _data_to_ill(ref_mtx, ref_file) + else: + with open(direct_file, 'wb') as fp: + np.save(fp, set_smallest_dtype(np.array(direct_mtx))) + with open(indirect_file, 'wb') as fp: + np.save(fp, set_smallest_dtype(np.array(indirect_mtx))) + with open(ref_file, 'wb') as fp: + np.save(fp, set_smallest_dtype(np.array(ref_mtx))) log_file.write(json.dumps([direct_file, indirect_file, ref_file])) except Exception as e: _logger.exception('Failed to run Shortwave MRT Delta map.\n{}'.format(e)) diff --git a/ladybug_comfort/map/irr.py b/ladybug_comfort/map/irr.py index 08978911..6fd4d6fe 100644 --- a/ladybug_comfort/map/irr.py +++ b/ladybug_comfort/map/irr.py @@ -2,8 +2,12 @@ """Methods for resolving MRT from Radiance and EnergyPlus output files.""" from __future__ import division +import numpy as np + from ladybug.sql import SQLiteResult +from ._helper import binary_to_array + def irradiance_contrib_map( sql, direct_specular, indirect_specular, ref_specular, indirect_diffuse, ref_diffuse, @@ -85,25 +89,15 @@ def irradiance_contrib_map( diff_trans.append(d_trans) # compute the direct irradiance contribution - direct_mtx = [] - for pt_irr in _ill_vals(direct_specular): - direct_mtx.append([irr * bt for irr, bt in zip(pt_irr, beam_trans)]) + direct_mtx = binary_to_array(direct_specular) * np.array(beam_trans) # compute the indirect irradiance contribution - indirect_mtx = [] - for s_irr, d_irr in zip(_ill_vals(indirect_specular), _ill_vals(indirect_diffuse)): - sen_mtx = [] - for si, di, bt, dt in zip(s_irr, d_irr, beam_trans, diff_trans): - sen_mtx.append((si * bt) + (di * dt)) - indirect_mtx.append(sen_mtx) + indirect_mtx = binary_to_array(indirect_specular) * np.array( + beam_trans) + binary_to_array(indirect_diffuse) * np.array(diff_trans) # compute the ground-reflected irradiance contribution - ref_mtx = [] - for s_irr, d_irr in zip(_ill_vals(ref_specular), _ill_vals(ref_diffuse)): - sen_mtx = [] - for si, di, bt, dt in zip(s_irr, d_irr, beam_trans, diff_trans): - sen_mtx.append((si * bt) + (di * dt)) - ref_mtx.append(sen_mtx) + ref_mtx = binary_to_array(ref_specular) * np.array( + beam_trans) + binary_to_array(ref_diffuse) * np.array(diff_trans) return direct_mtx, indirect_mtx, ref_mtx From b14d320f07057b4ac208acd16b928446baf1689f Mon Sep 17 00:00:00 2001 From: Mikkel Pedersen Date: Mon, 19 Aug 2024 13:45:10 +0200 Subject: [PATCH 4/4] fix(mtx): Add binary option to adaptive mtx --- ladybug_comfort/cli/mtx.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ladybug_comfort/cli/mtx.py b/ladybug_comfort/cli/mtx.py index 45cf2b23..88842119 100644 --- a/ladybug_comfort/cli/mtx.py +++ b/ladybug_comfort/cli/mtx.py @@ -219,9 +219,14 @@ def pmv_mtx( @click.option('--log-file', '-log', help='Optional log file to output the paths to the ' 'generated CSV files. By default this will be printed out to stdout', type=click.File('w'), default='-', show_default=True) +@click.option('--plain-text/--binary', ' /-b', help='Flag to note whether the ' + 'output should be formatted as a plain text CSV or whether it ' + 'should be formatted as a binary numpy array.', + default=True, show_default=True) def adaptive_mtx( temperature_mtx, prevail_temp, rad_temperature_mtx, rad_delta_mtx, - air_speed_mtx, air_speed_json, air_speed, comfort_par, folder, log_file + air_speed_mtx, air_speed_json, air_speed, comfort_par, folder, log_file, + plain_text ): """Get CSV files with matrices of Adaptive comfort from matrices of Adaptive inputs. @@ -234,20 +239,19 @@ def adaptive_mtx( """ try: # load up the matrices of values - air_temp = csv_to_num_matrix(temperature_mtx) + air_temp = load_matrix(temperature_mtx) prevail_temp = csv_to_num_matrix(prevail_temp)[0] - rad_temp = csv_to_num_matrix(rad_temperature_mtx) \ + rad_temp = load_matrix(rad_temperature_mtx) \ if rad_temperature_mtx is not None else air_temp if rad_delta_mtx is not None and not os.path.getsize(rad_delta_mtx) == 0: - d_rad_temp = csv_to_num_matrix(rad_delta_mtx) - rad_temp = tuple(tuple(t + dt for t, dt in zip(t_pt, dt_pt)) - for t_pt, dt_pt in zip(rad_temp, d_rad_temp)) + d_rad_temp = load_matrix(rad_delta_mtx) + rad_temp = rad_temp + d_rad_temp mtx_len = len(air_temp[0]) # process any of the other inputs for air speed a_speed = None if air_speed_mtx is not None and os.path.isfile(air_speed_mtx): - a_speed = csv_to_num_matrix(air_speed_mtx) + a_speed = load_matrix(air_speed_mtx).tolist() if a_speed is None and air_speed_json is not None \ and os.path.isfile(air_speed_json): with open(air_speed_json) as json_file: @@ -294,7 +298,8 @@ def adaptive_mtx( # write out the final results to CSV files if folder is None: folder = os.path.join(os.path.dirname(temperature_mtx), 'thermal_mtx') - result_file_dict = thermal_map_csv(folder, temper, cond, cond_intensity) + result_file_dict = thermal_map_csv( + folder, temper, cond, cond_intensity, plain_text=plain_text) log_file.write(json.dumps(result_file_dict)) except Exception as e: _logger.exception('Failed to run PMV matrix.\n{}'.format(e))