Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Schedule:Compact as ScheduleRuleset #605

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 160 additions & 12 deletions honeybee_energy/schedule/ruleset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@
"""Complete annual schedule object built from ScheduleDay and rules for applying them."""
from __future__ import division

from .day import ScheduleDay
from .rule import ScheduleRule
from .typelimit import ScheduleTypeLimit
from ..reader import parse_idf_string
from ..writer import generate_idf_string
import os
import re

from honeybee._lockable import lockable
from honeybee.typing import valid_ep_string, tuple_with_length

from ladybug.datacollection import HourlyContinuousCollection
from ladybug.header import Header
from honeybee.typing import tuple_with_length, valid_ep_string
from ladybug.analysisperiod import AnalysisPeriod
from ladybug.dt import Date
from ladybug.datacollection import HourlyContinuousCollection
from ladybug.datatype.generic import GenericType
from ladybug.dt import Date, Time
from ladybug.header import Header

import re
import os
from ..reader import parse_idf_string
from ..writer import generate_idf_string
from .day import ScheduleDay
from .rule import ScheduleRule
from .typelimit import ScheduleTypeLimit


@lockable
Expand Down Expand Up @@ -1004,6 +1003,10 @@ def extract_all_from_idf_file(idf_file):
constant_pattern = re.compile(r"(?i)(Schedule:Constant,[\s\S]*?;)")
constant_props = tuple(parse_idf_string(idf_string) for
idf_string in constant_pattern.findall(file_contents))
# extract all of the Schedule:Compact objects and convert to ScheduleRuleset
conpact_pattern = re.compile(r"(?i)(Schedule:Compact,[\s\S]*?;)")
compact_props = tuple(parse_idf_string(idf_string) for
idf_string in conpact_pattern.findall(file_contents))
# compile all of the ScheduleRuleset objects from extracted properties
schedules = []
for year_sch in year_props:
Expand Down Expand Up @@ -1043,6 +1046,151 @@ def extract_all_from_idf_file(idf_file):
sch_ruleset = ScheduleRuleset.from_constant_value(
const_sch[0], sched_val, schedule_type)
schedules.append(sch_ruleset)
for compact_sch in compact_props:
schedule_type = (
sch_type_dict[compact_sch[1]] if compact_sch[1] != "" else None
)
schedule_rules = []
holiday_schedule = None
winter_designday_schedule = None
summer_designday_schedule = None
start_date = Date.from_doy(1)
end_date = Date.from_doy(365)
untils = [Time(0, 0)] # initialize list of until times.
n = -1 # initialize the n-th until time
rules = [] # initialize list of rules.
for field in compact_sch[2:]:
field = field.lower()
if "through" in field:
# Each `through` field generates a new ScheduleRule
# initialize rule with ScheduleDay as placeholder.
rule = ScheduleRule(ScheduleDay(compact_sch[0], [0], [Time(0, 0)]))

# start_date is either Jan 1st or end_date from previous
# `through` field
start_date = (
start_date if end_date == Date.from_doy(365) else end_date
)

_, date = field.split(":") # get end_date from field
month, day = date.split("/")
end_date = Date.from_dict({"month": int(month), "day": int(day)})
elif "for" in field:
# reset values for new set; each `for` is a new rule
n = -1 # reset the n-th until time
untils = [Time(0, 0)] # reset list of until times.
rules = [] # reset list of rules for this `for` field.

# Create a rule; all different `if` statements because we want to
# catch more than one case,
# eg. `For: Sunday Holidays AllOtherDays, !- Field 54`
if "alldays" in field:
rule = ScheduleRule(ScheduleDay("alldays", [0], [Time(0, 0)]))
rule.apply_all = True
rules.append(rule)
if "weekdays" in field:
rule = ScheduleRule(ScheduleDay("weekdays", [0], [Time(0, 0)]))
rule.apply_weekday = True
rules.append(rule)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whenever I see duplicated code like this, I know that there should be a function for it instead of copy/pasted code. I recommend making a hidden function on the class where you pass the type of day (eg. weekdays) and the rule list to which the rule would be appended.

Then, you don't even need an if statement and you essentially turn 50 lines of duplicated code into 3 lines.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that I should give a little more explanation of what this hidden method would look like if you wanted to get rid fo the if statement. You could just have a dictionary that maps the word "weekdays" to the name of the property on the ScheduleRule class ("apply_weekdays"). Then you can just use the native python setattr() to set the property to True on the ScheduleRule object instance.

And that will save you from a lot of duplicate code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point: Is this what you had in mind? 0436193

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is much cleaner. There's just a typo that I noticed in the code here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

if "weekends" in field:
rule = ScheduleRule(ScheduleDay("weekends", [0], [Time(0, 0)]))
rule.apply_weekend = True
rules.append(rule)
if "sunday" in field:
rule = ScheduleRule(ScheduleDay("sunday", [0], [Time(0, 0)]))
rule.apply_sunday = True
rules.append(rule)
if "monday" in field:
rule = ScheduleRule(ScheduleDay("monday", [0], [Time(0, 0)]))
rule.apply_monday = True
rules.append(rule)
if "tuesday" in field:
rule = ScheduleRule(ScheduleDay("tuesday", [0], [Time(0, 0)]))
rule.apply_tuesday = True
rules.append(rule)
if "wednesday" in field:
rule = ScheduleRule(ScheduleDay("wednesday", [0], [Time(0, 0)]))
rule.apply_wednesday = True
rules.append(rule)
if "thursday" in field:
rule = ScheduleRule(ScheduleDay("thursday", [0], [Time(0, 0)]))
rule.apply_thursday = True
rules.append(rule)
if "friday" in field:
rule = ScheduleRule(ScheduleDay("friday", [0], [Time(0, 0)]))
rule.apply_friday = True
rules.append(rule)
if "saturday" in field:
rule = ScheduleRule(ScheduleDay("saturday", [0], [Time(0, 0)]))
rule.apply_saturday = True
rules.append(rule)
if "holiday" in field:
rule = ScheduleRule(ScheduleDay("holiday", [0], [Time(0, 0)]))
holiday_schedule = rule.schedule_day
rules.append(rule)
if "summerdesignday" in field:
rule = ScheduleRule(ScheduleDay("summerdesignday", [0], [Time(0, 0)]))
summer_designday_schedule = rule.schedule_day
rules.append(rule)
if "winterdesignday" in field:
rule = ScheduleRule(ScheduleDay("winterdesignday", [0], [Time(0, 0)]))
winter_designday_schedule = rule.schedule_day
rules.append(rule)
if "allotherdays" in field:
rule = ScheduleRule(ScheduleDay("allotherdays", [0], [Time(0, 0)]))
apply_mtx = [rul.week_apply_tuple for rul in schedule_rules]
for j, dow in enumerate(zip(*apply_mtx)):
if not any(dow):
rule.apply_day_by_dow(j + 1)
rules.append(rule)

for rule in rules:
# for each rule in this `for` field, add rules to
# ScheduleRuleset list of rules and set start_date and end_date.
if len(rule.days_applied) != 0:
schedule_rules.append(rule)

# set range for rule (from previous `through` field)
rule.start_date = start_date
rule.end_date = end_date
elif "until" in field:
_, hour, min = field.split(":") # get hour and minutes

# value is applied `until` a certain Time, but `ScheduleDay` is
# applied `from` a certain Time. Also, Time is 0:23 Hours while IDF
# is 1:24 Hours.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nice to know that I am not the only one who struggles with this convention. Why in the world EnergyPlus couldn't obey how time works, I do not know. But, from the number of thumbs up on this issue, hopefully future developers may be able to avoid it.

until = Time(int(hour) - 1, int(min)) # to 0:23 Hours repr
untils.append(until)

# increment n
n += 1
elif "interpolate" in field:
# Set interpolate on all rules for this `for` field
_, interpolate = field.split(":")
for rule in rules:
rule.schedule_day.interpolate = interpolate != "no"
else:
begin = untils[n] # index list of `until` times
for rule in rules:
# apply field value for each rules; try to replace the
# placeholder value first, else add the value.
try:
rule.schedule_day.replace_value_by_time(begin, float(field))
except ValueError:
rule.schedule_day.add_value(float(field), begin)
default_day_schedule = schedule_rules[0].schedule_day
sch_ruleset = ScheduleRuleset(
default_day_schedule=default_day_schedule,
identifier=compact_sch[0],
schedule_type_limit=schedule_type,
schedule_rules=schedule_rules[1:]
)
ScheduleRuleset._apply_designdays_with_check(
sch_ruleset,
holiday_schedule,
summer_designday_schedule,
winter_designday_schedule)
schedules.append(sch_ruleset)
return schedules

@staticmethod
Expand Down
37 changes: 37 additions & 0 deletions tests/idf/OfficeOccupancySchedule_Compact.idf
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,40 @@ Office Occupancy Week Schedule, !- Schedule:Week Name 1
1, !- Start Day 1
12, !- End Month 1
31; !- End Day 1

Schedule:Compact,
ADMIN_OCC_SCH, !- Name
Fractional, !- Schedule Type Limits Name
Through: 12/31, !- Field 1
For: Weekdays, !- Field 2
Until: 04:00,0.05, !- Field 3
Until: 06:00,0.20, !- Field 5
Until: 07:00,0.50, !- Field 7
Until: 18:00,0.90, !- Field 9
Until: 20:00,0.50, !- Field 11
Until: 22:00,0.20, !- Field 13
Until: 24:00,0.05, !- Field 15
For: SummerDesignDay, !- Field 17
Until: 04:00,0.05, !- Field 18
Until: 06:00,0.20, !- Field 20
Until: 07:00,0.50, !- Field 22
Until: 18:00,0.90, !- Field 24
Until: 20:00,0.50, !- Field 26
Until: 22:00,0.20, !- Field 28
Until: 24:00,0.05, !- Field 30
For: WinterDesignDay, !- Field 32
Until: 07:00,0.05, !- Field 33
Until: 09:00,0.20, !- Field 35
Until: 15:00,0.30, !- Field 37
Until: 20:00,0.20, !- Field 39
Until: 24:00,0.05, !- Field 41
For: Saturday, !- Field 43
Until: 07:00,0.05, !- Field 44
Until: 09:00,0.20, !- Field 46
Until: 15:00,0.30, !- Field 48
Until: 20:00,0.20, !- Field 50
Until: 24:00,0.05, !- Field 52
For: Sunday Holidays AllOtherDays, !- Field 54
Until: 08:00,0.00, !- Field 55
Until: 17:00,0.05, !- Field 57
Until: 24:00,0.00; !- Field 59
8 changes: 7 additions & 1 deletion tests/schedule_ruleset_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ def test_schedule_ruleset_from_idf_file():


def test_schedule_ruleset_from_idf_file_compact():
"""Test the initalization of ScheduleRuleset from file with Schedule:Week:Compact."""
"""Test the initalization of ScheduleRuleset from file with Schedule:Week:Compact
and Schedule:Compact.
"""
office_sched_idf = './tests/idf/OfficeOccupancySchedule_Compact.idf'
office_scheds = ScheduleRuleset.extract_all_from_idf_file(office_sched_idf)

Expand All @@ -277,6 +279,10 @@ def test_schedule_ruleset_from_idf_file_compact():
assert office_occ.schedule_rules[1].schedule_day.identifier == \
'Medium Office Bldg Occ Sunday Schedule'

office_occ = office_scheds[1]
assert office_occ.schedule_rules[0].schedule_day.identifier == \
"saturday"

assert isinstance(office_occ.schedule_type_limit, ScheduleTypeLimit)


Expand Down