NREL / OpenStudio

OpenStudio is a cross-platform collection of software tools to support whole building energy modeling using EnergyPlus and advanced daylight analysis using Radiance.
https://www.openstudio.net/
Other
501 stars 191 forks source link

ScheduleDay::getValue applies interpolation differently than EnergyPlus #5001

Closed eringold closed 5 months ago

eringold commented 1 year ago

Issue overview

For a ScheduleDay with 'Interpolate to Timestep' = 'Yes', calling getValue with a Time value that does not equal one of the scheduled Hour+Minute values will return an interpolated value, which is inconsistent with how EnergyPlus interprets that input.

Current Behavior

EnergyPlus interprets 'Interpolate to Timestep' = 'Average' to mean "when the simulation timestep doesn't coincide with the scheduled interval, the value at the intermediate timestep is interpolated". I.e., for this schedule:

Schedule:Day:Interval,
    Day Schedule,            !- Name
    Fractional,              !- Schedule Type Limits Name
    Average,                 !- Interpolate to Timestep
    8:15,                    !- Time 1 {hh:mm}
    0,                       !- Value Until Time 1
    21:45,                   !- Time 2 {hh:mm}
    1,                       !- Value Until Time 2
    24:00,                   !- Time 3 {hh:mm}
    0;                       !- Value Until Time 3

If the simulation timesteps per hour are 4, will evaluate like: image

With 'Average' interpolation, for 10 timesteps per hour, it would evaluate like: image

However ScheduleDay::getValue in OpenStudio interpolates between schedule times (i.e., 8:15 to 21:45), so calling getValue with intermediate times results in a plot like this: image

Expected Behavior

I expect getValue to return the same value that EnergyPlus would for a schedule at a given time.

Steps to Reproduce

Create a ScheduleDay with 'Interpolate To Timestep' = 'Yes', and make successive calls to getValue like:

def print_vals(sch)
  (0..24).each do |hr|
    ["00","15","30","45"].each do |min|
      hr_pad = "%02d" % hr
      time = OpenStudio::Time.new("#{hr_pad}:#{min}:00")
      val = day_sch.getValue(time)
      puts "#{time}, #{val}"
    end
  end
end

model = OpenStudio::Model::Model.new
sch = OpenStudio::Model::ScheduleDay.new(model)
sch.setInterpolatetoTimestep(true)
sch.addValue(OpenStudio::Time.new("08:15:00"),0)
sch.addValue(OpenStudio::Time.new("21:45:00"),1)
print_vals(sch)

-> ... 08:00:00, 0.0 08:15:00, 0.0 08:30:00, 0.01851851851851845 08:45:00, 0.03703703703703701 09:00:00, 0.05555555555555555 ... 21:00:00, 0.9444444444444444 21:15:00, 0.9629629629629629 21:30:00, 0.9814814814814814 21:45:00, 1.0 22:00:00, 0.8888888888888893 22:15:00, 0.7777777777777786

Some additional details about your environment for this issue (if relevant):

Context

This impacts multiple uses of getValue, e.g. for determining HVAC occupancy schedules in openstudio-standards, schedule reporting in OpenStudio Results, etc.

joseph-robertson commented 1 year ago

For users, is the alternative/workaround here to add every single value to the day schedule for all timesteps?

eringold commented 1 year ago

@joseph-robertson I suppose. Alternatively, the user could store the value of interpolatetoTimestep, set it to false before calling getValue, and reset it after. That still wouldn't exactly match EnergyPlus, but would be closer than the existing implementation.

eringold commented 7 months ago

@joseph-robertson

For users, is the alternative/workaround here to add every single value to the day schedule for all timesteps?

How about a new OS:ScheduleDay method getValuesatTimestep(OS:Timestep) that returns a vector of schedule values, which you could then interpolate for a given OS:Time? It would be useful to replace this standards method, and (ideally) extend it up to ScheduleRuleset, like this

@DavidGoldwasser @mdahlhausen

joseph-robertson commented 7 months ago

Hi @eringold, can you have a look at https://github.com/NREL/OpenStudio/pull/5111 and tell me if this is what you had in mind?

This is a method to return all values of a day schedule, regardless of the interpolatetoTimestep setting. Users would then have the ability, for a given timestep interval, to apply whatever interpolation approach they want?

jmarrec commented 7 months ago

Just noticed this https://github.com/NREL/OpenStudio/blob/171ca2b63b77285c2aee744592c8719b4132a312/src/model/ScheduleDay.hpp#L57

Maybe it'd be good to change it to be a choice field like E+, and bring the os-standards average method into C++

  A3 , \field Interpolate to Timestep
       \note when the interval does not match the user specified timestep a Average choice will average between the intervals request (to
       \note timestep resolution. A No choice will use the interval value at the simulation timestep without regard to if it matches
       \note the boundary or not. A Linear choice will interpolate linearly between successive values.
       \type choice
       \key Average
       \key Linear
       \key No
jmarrec commented 7 months ago

https://github.com/NREL/OpenStudio/blob/171ca2b63b77285c2aee744592c8719b4132a312/src/utilities/data/Vector.hpp#L56-L63

jmarrec commented 7 months ago

https://github.com/NREL/OpenStudio/blob/7a987d3fa223bd8319aac2e09db9ff7ce09a9258/src/model/ScheduleDay.cpp#L174-L213

https://github.com/NREL/OpenStudio/blob/171ca2b63b77285c2aee744592c8719b4132a312/src/utilities/data/Vector.cpp#L102-L149

doesn't the getValues routine look inefficient? If I'm understanding correctly, you only need to find the time before and the time after and the corresponding, you can break early. I know, this is a scheduleday, so we're talking about 60 (timestep max in E+ If I recall correctly) * 24 hours datapoints max, so it's not noticeable, but still