Skip to content

Commit

Permalink
fix(translate): Refactor commands to be call-able outside the CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey committed Dec 3, 2024
1 parent cb0abaa commit aa5f09e
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 151 deletions.
316 changes: 171 additions & 145 deletions honeybee_doe2/cli/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import json
import logging

from ladybug.futil import write_to_file_by_name
from honeybee.typing import clean_doe2_string
from honeybee.model import Model
from honeybee_energy.schedule.ruleset import ScheduleRuleset
Expand Down Expand Up @@ -47,110 +46,94 @@ def translate():
'note whether interior ceilings should be excluded from the export.',
default=True, show_default=True)
@click.option(
'--equest-version', '-eq', help='optional text string to denote the version '
'--equest-version', '-eq', help='Optional text string to denote the version '
'of eQuest for which the INP definition will be generated. If unspecified '
'or unrecognized, the latest version of eQuest will be used.',
default='3.65', show_default=True, type=str)
@click.option(
'--output-file', '-o', help='Optional INP file path to output the INP string '
'of the translation. By default this will be printed out to stdout.',
type=click.File('w'), default='-', show_default=True)
def model_to_inp_file(
def model_to_inp_cli(
model_file, sim_par_json, hvac_mapping, include_interior_walls,
include_interior_ceilings, equest_version, output_file
):
"""Translate a Model (HBJSON) file to an INP file.
"""Translate a Honeybee Model to an INP file.
\b
Args:
model_file: Full path to a Honeybee Model file (HBJSON or HBpkl)."""
model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
"""
try:
# load simulation parameters if specified
sim_par = None
if sim_par_json is not None:
with open(sim_par_json) as json_file:
data = json.load(json_file)
sim_par = SimulationPar.from_dict(data)

# re-serialize the Model to Python
model = Model.from_file(model_file)
x_int_w = not include_interior_walls
x_int_c = not include_interior_ceilings

# create the strings for the model
inp_str = model.to.inp(model, sim_par, hvac_mapping, x_int_w, x_int_c,
equest_version)

# write out the INP file
output_file.write(inp_str)
exclude_interior_walls = not include_interior_walls
exclude_interior_ceilings = not include_interior_ceilings
model_to_inp(
model_file, sim_par_json, hvac_mapping,
exclude_interior_walls, exclude_interior_ceilings,
equest_version, output_file)
except Exception as e:
_logger.exception(f'Model translation failed:\n{e}')
sys.exit(1)
else:
sys.exit(0)


@translate.command('hbjson-to-inp')
@click.argument('model-file', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option(
'--hvac-mapping', '-hm', help='Text to indicate how HVAC systems should be '
'assigned to the exported model. Story will assign one HVAC system for each '
'distinct level polygon, Model will use only one HVAC system for the whole model '
'and AssignedHVAC will follow how the HVAC systems have been assigned to the'
'Rooms.properties.energy.hvac. Choose from: Room, Story, Model, AssignedHVAC',
default='Story', show_default=True, type=str)
@click.option(
'--include-interior-walls/--exclude-interior-walls', ' /-xw', help='Flag to note '
'whether interior walls should be excluded from the export.',
default=True, show_default=True)
@click.option(
'--include-interior-ceilings/--exclude-interior-ceilings', ' /-xc', help='Flag to '
'note whether interior ceilings should be excluded from the export.',
default=True, show_default=True)
@click.option(
'--verbose-properties/--switch-statements', ' /-ss', help='Flag to note whether '
'program types should be written with switch statements so that they can easily '
'be edited in eQuest or a verbose definition of loads should be written for '
'each Room/Space.', default=True, show_default=True)
@click.option(
'--name', '-n', help='Deprecated option to set the name of the output file.',
default=None, show_default=True)
@click.option(
'--folder', '-f', help='Deprecated option to set the path to target folder.',
type=click.Path(file_okay=False, resolve_path=True, dir_okay=True), default=None)
@click.option(
'--output-file', '-o', help='Optional INP file path to output the INP string '
'of the translation. By default this will be printed out to stdout.',
type=click.File('w'), default='-', show_default=True)
def hbjson_to_inp_file(
model_file, hvac_mapping, include_interior_walls,
include_interior_ceilings, verbose_properties, name, folder, output_file
):
"""Translate a Model (HBJSON) file to an INP file.
def model_to_inp(
model_file, sim_par_json=None, hvac_mapping='Story',
exclude_interior_walls=False, exclude_interior_ceilings=False,
equest_version='3.65', output_file=None,
include_interior_walls=True, include_interior_ceilings=True):
"""Translate a Honeybee Model to an INP file.
\b
Args:
model_file: Full path to a Honeybee Model file (HBJSON or HBpkl)."""
try:
print('This method is deprecated and you should use model-to-inp instead.')
model = Model.from_file(model_file)
x_int_w = not include_interior_walls
x_int_c = not include_interior_ceilings
inp_str = model.to.inp(
model, hvac_mapping=hvac_mapping, exclude_interior_walls=x_int_w,
exclude_interior_ceilings=x_int_c)
if folder is not None and name is not None:
if not name.lower().endswith('.inp'):
name = name + '.inp'
write_to_file_by_name(folder, name, inp_str, True)
else:
output_file.write(inp_str)
except Exception as e:
_logger.exception(f'Model translation failed:\n{e}')
sys.exit(1)
model_file: Full path to a Honeybee Model file (HBJSON or HBpkl).
sim_par_json: Full path to a honeybee-doe2 SimulationPar JSON that
describes all of the settings for the simulation. If None,
default parameters will be generated. (Default: None).
hvac_mapping: Text to indicate how HVAC systems should be assigned to
the exported model. Story will assign one HVAC system for each distinct
level polygon, Model will use only one HVAC system for the whole model
and AssignedHVAC will follow how the HVAC systems have been assigned
to the Rooms.properties.energy.hvac. Choose from the following.
* Room
* Story
* Model
* AssignedHVAC
exclude_interior_walls: Boolean to note whether interior walls should
be excluded from the export. (Default: False).
exclude_interior_ceilings: Boolean to note whether interior ceilings
should be excluded from the export. (Default: False).
equest_version: Optional text string to denote the version of eQuest for
which the INP definition will be generated. If unspecified or
unrecognized, the latest version of eQuest will be used. (Default: False).
output_file: Optional INP file path to output the INP string of the
translation. If None, the string will be returned from this function.
"""
# load simulation parameters if specified
sim_par = None
if sim_par_json is not None:
with open(sim_par_json) as json_file:
data = json.load(json_file)
sim_par = SimulationPar.from_dict(data)

# re-serialize the Model to Python
model = Model.from_file(model_file)

# create the strings for the model
inp_str = model.to.inp(
model, sim_par, hvac_mapping,
exclude_interior_walls, exclude_interior_ceilings, equest_version)

# write out the INP file
if output_file is None:
return inp_str
elif isinstance(output_file, str):
with open(output_file, 'w') as of:
of.write(inp_str)
else:
sys.exit(0)
output_file.write(inp_str)


@translate.command('schedules-to-inp')
Expand All @@ -159,7 +142,7 @@ def hbjson_to_inp_file(
@click.option('--output-file', '-f', help='Optional INP file to output the INP '
'string of the translation. By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True)
def schedule_to_inp(schedule_json, output_file):
def schedule_to_inp_cli(schedule_json, output_file):
"""Translate a Schedule JSON file to an INP.
\b
Expand All @@ -169,70 +152,89 @@ def schedule_to_inp(schedule_json, output_file):
the values are non-abridged Schedules.
"""
try:
# re-serialize the Schedule to Python
with open(schedule_json) as json_file:
data = json.load(json_file)
sch_list = data.values() if isinstance(data, dict) else data
sch_objs = [dict_to_schedule(sch) for sch in sch_list]
type_objs = set()
for sch in sch_objs:
type_objs.add(sch.schedule_type_limit)

# create the INP strings
all_day_scheds, all_week_scheds, all_year_scheds = [], [], []
used_day_sched_ids, used_day_count = {}, 1
all_scheds = sch_objs
for sched in all_scheds:
if isinstance(sched, ScheduleRuleset):
year_schedule, week_schedules = sched.to_inp()
# check that day schedules aren't referenced by other model schedules
day_scheds = []
for day in sched.day_schedules:
if day.identifier not in used_day_sched_ids:
day_scheds.append(day.to_inp(sched.schedule_type_limit))
used_day_sched_ids[day.identifier] = day
elif day != used_day_sched_ids[day.identifier]:
new_day = day.duplicate()
new_day.identifier = 'Schedule Day {}'.format(used_day_count)
day_scheds.append(new_day.to_inp(sched.schedule_type_limit))
for i, week_sch in enumerate(week_schedules):
old_day_id = clean_doe2_string(day.identifier, RES_CHARS)
new_day_id = clean_doe2_string(new_day.identifier, RES_CHARS)
week_schedules[i] = week_sch.replace(old_day_id, new_day_id)
used_day_count += 1
all_day_scheds.extend(day_scheds)
all_week_scheds.extend(week_schedules)
all_year_scheds.append(year_schedule)
else: # ScheduleFixedInterval
year_schedule, week_schedules, year_schedule = sched.to_inp()
all_day_scheds.extend(day_scheds)
all_week_scheds.extend(week_schedules)
all_year_scheds.append(year_schedule)
inp_str_list = ['INPUT ..\n\n']
inp_str_list.append(header_comment_minor('Day Schedules'))
inp_str_list.extend(all_day_scheds)
inp_str_list.append(header_comment_minor('Week Schedules'))
inp_str_list.extend(all_week_scheds)
inp_str_list.append(header_comment_minor('Annual Schedules'))
inp_str_list.extend(all_year_scheds)
inp_str_list.append('END ..\nCOMPUTE ..\nSTOP ..\n')
inp_str = '\n'.join(inp_str_list)

# write out the INP file
output_file.write(inp_str)
schedule_to_inp(schedule_json, output_file)
except Exception as e:
_logger.exception('Schedule translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)


def schedule_to_inp(schedule_json, output_file=None):
"""Translate a Schedule JSON file to an INP.
Args:
schedule_json: Full path to a Schedule JSON file. This file should
either be an array of non-abridged Schedules or a dictionary where
the values are non-abridged Schedules.
output_file: Optional INP file path to output the INP string of the
translation. If None, the string will be returned from this function.
"""
# re-serialize the Schedule to Python
with open(schedule_json) as json_file:
data = json.load(json_file)
sch_list = data.values() if isinstance(data, dict) else data
sch_objs = [dict_to_schedule(sch) for sch in sch_list]
type_objs = set()
for sch in sch_objs:
type_objs.add(sch.schedule_type_limit)

# create the INP strings
all_day_scheds, all_week_scheds, all_year_scheds = [], [], []
used_day_sched_ids, used_day_count = {}, 1
all_scheds = sch_objs
for sched in all_scheds:
if isinstance(sched, ScheduleRuleset):
year_schedule, week_schedules = sched.to_inp()
# check that day schedules aren't referenced by other model schedules
day_scheds = []
for day in sched.day_schedules:
if day.identifier not in used_day_sched_ids:
day_scheds.append(day.to_inp(sched.schedule_type_limit))
used_day_sched_ids[day.identifier] = day
elif day != used_day_sched_ids[day.identifier]:
new_day = day.duplicate()
new_day.identifier = 'Schedule Day {}'.format(used_day_count)
day_scheds.append(new_day.to_inp(sched.schedule_type_limit))
for i, week_sch in enumerate(week_schedules):
old_day_id = clean_doe2_string(day.identifier, RES_CHARS)
new_day_id = clean_doe2_string(new_day.identifier, RES_CHARS)
week_schedules[i] = week_sch.replace(old_day_id, new_day_id)
used_day_count += 1
all_day_scheds.extend(day_scheds)
all_week_scheds.extend(week_schedules)
all_year_scheds.append(year_schedule)
else: # ScheduleFixedInterval
year_schedule, week_schedules, year_schedule = sched.to_inp()
all_day_scheds.extend(day_scheds)
all_week_scheds.extend(week_schedules)
all_year_scheds.append(year_schedule)
inp_str_list = ['INPUT ..\n\n']
inp_str_list.append(header_comment_minor('Day Schedules'))
inp_str_list.extend(all_day_scheds)
inp_str_list.append(header_comment_minor('Week Schedules'))
inp_str_list.extend(all_week_scheds)
inp_str_list.append(header_comment_minor('Annual Schedules'))
inp_str_list.extend(all_year_scheds)
inp_str_list.append('END ..\nCOMPUTE ..\nSTOP ..\n')
inp_str = '\n'.join(inp_str_list)

# write out the INP file
if output_file is None:
return inp_str
elif isinstance(output_file, str):
with open(output_file, 'w') as of:
of.write(inp_str)
else:
output_file.write(inp_str)


@translate.command('schedules-from-inp')
@click.argument('schedule-inp', type=click.Path(
exists=True, file_okay=True, dir_okay=False, resolve_path=True))
@click.option(
'--dictionary/--list', ' /-l', help='Flag to note whether a the output JSON '
'should be a list of schedule objects or whether it should be a dictionary '
'--dictionary/--array', ' /-a', help='Flag to note whether a the output JSON '
'should be an array of schedule objects or whether it should be a dictionary '
'where each key is the identifier of the schedule and each value is the '
'schedule object. The dictionary format is the one used by honeybee-standards '
'and is recommended when writing INP schedules into the user standards library.',
Expand All @@ -241,25 +243,49 @@ def schedule_to_inp(schedule_json, output_file):
'--output-file', '-f', help='Optional JSON file to output the JSON string of '
'the translation. By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True)
def schedule_from_inp(schedule_inp, dictionary, output_file):
def schedule_from_inp_cli(schedule_inp, dictionary, output_file):
"""Translate a schedule INP file to a honeybee JSON as an array of schedules.
\b
Args:
schedule_inp: Full path to a Schedule INP file.
"""
try:
# re-serialize the schedules to Python
schedules = extract_all_schedule_ruleset_from_inp_file(schedule_inp)
# create the honeybee dictionaries
if dictionary:
hb_objs = {sch.identifier: sch.to_dict() for sch in schedules}
else:
hb_objs = [sch.to_dict() for sch in schedules]
# write out the JSON file
output_file.write(json.dumps(hb_objs))
array = not dictionary
schedule_from_inp(schedule_inp, array, output_file)
except Exception as e:
_logger.exception('Schedule translation failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)


def schedule_from_inp(schedule_inp, array=False, output_file=None, dictionary=True):
"""Translate a schedule INP file to a honeybee JSON as an array of schedules.
Args:
schedule_inp: Full path to a Schedule INP file.
array: Boolean to note whether a the output JSON should be an array of
schedule objects or whether it should be a dictionary where each key
is the identifier of the schedule and each value is the schedule object.
The dictionary format is the one used by honeybee-standards and is
recommended when writing INP schedules into the user standards
library. (Default: False).
output_file: Optional JSON file path to output the JSON string of the
translation. If None, the string will be returned from this function.
"""
# re-serialize the schedules to Python
schedules = extract_all_schedule_ruleset_from_inp_file(schedule_inp)
# create the honeybee dictionaries
if array:
hb_objs = [sch.to_dict() for sch in schedules]
else:
hb_objs = {sch.identifier: sch.to_dict() for sch in schedules}
# write out the JSON file
if output_file is None:
return json.dumps(hb_objs)
elif isinstance(output_file, str):
with open(output_file, 'w') as of:
of.write(json.dumps(hb_objs))
else:
output_file.write(json.dumps(hb_objs))
Loading

0 comments on commit aa5f09e

Please sign in to comment.