Skip to content

Commit

Permalink
derConsumer: Added adhoc TOU rate function, dispatch schedule, and to…
Browse files Browse the repository at this point in the history
…tal solar cost functions
  • Loading branch information
astronobri committed May 30, 2024
1 parent 9af9a58 commit 051279d
Showing 1 changed file with 194 additions and 11 deletions.
205 changes: 194 additions & 11 deletions omf/models/derConsumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ def create_REopt_jl_jsonFile(modelDir, inputDict):


def get_tou_rates(modelDir, lat, lon): ## TODO: change lat,lon to inputDict once model is working
'''
Function that pulls rate information from the OEDI utility rate database via API functionality.
** Inputs
modelDir: (str) Currently model directory
lat: (float) Latitude of the utility
lon: (float) Longitude of the utility
##inputDict: (dict) Input dictionary containing latitude and longitude information (NOTE: this will replace lat and lon
** Outputs
data: (JSON file) Ouput JSON file containing the API response fields \
(see https://openei.org/services/doc/rest/util_rates/?version=3#json-output-format for more info)
'''

api_url = 'https://api.openei.org/utility_rates?parameters'
api_key = '5dFShfSVRt2XJpPYCbzBeLM6nHrOXc0VFPTWxfJJ' ## API key generated by following this website: 'https://openei.org/services/'

Expand All @@ -224,15 +238,17 @@ def get_tou_rates(modelDir, lat, lon): ## TODO: change lat,lon to inputDict once
'format': 'json',
'lat': lat,
'lon': lon,
'api_key': api_key
'api_key': api_key,
'getpage': '5b311c595457a3496d8367be',
}

try:
response = requests.get(api_url, params=params)
response.raise_for_status() ## Raise an exception for HTTP errors

data = response.json()

# Save the retrieved data as a JSON file
## Save the retrieved data as a JSON file
with open(pJoin(modelDir, 'OEDIrateData.json'), 'w') as json_file:
json.dump(data, json_file)

Expand All @@ -241,7 +257,137 @@ def get_tou_rates(modelDir, lat, lon): ## TODO: change lat,lon to inputDict once
except requests.exceptions.RequestException as e:
print('Error:', e)
return None


def get_tou_rates_adhoc():
'''
Ad-hoc function that arbitrarily assigns TOU rates for summer/winter on- and 0ff-peaks.
NOTE: This function is temporarily used in place of get_tou_rates() due to issues with API \
not returning rate data at this time.
'''

tou_rates = {
## Example TOU rates data for ad-hoc use
'summer_on_peak': 0.25,
'summer_off_peak': 0.15,
'winter_on_peak': 0.20,
'winter_off_peak': 0.12
}
return tou_rates

def generate_day_hours(monthHours):
'''
Create an array of start and stop hours for each day if given an array of start/stop hours for each month.
** Inputs
monthHours: (list) List of (start, stop) hours for each month in a year's worth of hourly data (length 8760)
** Outputs
dayHours: (list) List of (start, stop) hours for each day
'''

dayHours = []
for start_hour, end_hour in monthHours:
for day_start in range(start_hour, end_hour, 24):
day_end = min(day_start + 24, end_hour)
dayHours.append((day_start, day_end))
return dayHours

def create_dispatch_schedule(temperature_array, dayHours):
'''
Create a dispatch schedule for solar, battery, and grid technologies for the span of a year. \
The max temperature is determined every day during hotter months (March-August) and solar is \
dispatched for two hours starting on the start hour. For the rest of the colder months, the \
same is done but for the min temperature for that day. NOTE: normally, there would be 2 minimum \
peak temperatures in the winter months where DERs would be dispatched, but only 1 is modeled \
here for simplicity. The function returns a list of strings of either 'Grid', 'Battery', or 'Solar' \
to signify what technology is dispatched at that hour.
** Inputs
temperature_array: (arr; length 8760) Hourly temperature data for a year
dayHours: (list) List of (start, stop) times for each hour in a year
** Outputs
dispatch_schedule (list): List of strings that say either 'Grid', 'Battery', or 'Solar' to \
signify which technology is dispatched at that start hour. The duration of dispatch is \
at least 2 hours.
'''
## Initialize a dispatch schedule with "Grid" for all hours
dispatch_schedule = ["Grid"] * len(temperature_array)

## Assign hourly dispatch according to the max temp in hotter months and min temp in colder months
for start_hour, end_hour in dayHours:
day_temperatures = temperature_array[start_hour:end_hour]
max_temp = max(day_temperatures)
min_temp = min(day_temperatures)

## March to August (hotter months)
if start_hour in range(1416, 5832):
for i in range(start_hour, end_hour):
if temperature_array[i] == max_temp: ## max temperature peak for dispatch
for j in range(i, min(i + 2, end_hour)): ## Duration of dispatch >2hrs
dispatch_schedule[j] = "Solar"

## Other months (colder months)
else:
for i in range(start_hour, end_hour):
if temperature_array[i] == min_temp: ## min temperature peak for dispatch
for j in range(i, min(i + 2, end_hour)): ## Duration of dispatch >2hrs
dispatch_schedule[j] = "Battery"

return dispatch_schedule

def solar_demand(PV, hourDispatch, on_peak_hours, off_peak_hours):
'''
Extract solar demand while keeping track of on-peak or off-peak hours.
** Inputs
PV: (list) List of PV dispatch values (from REopt).
hour_types: (list) List indicating the type of each hour, where 'Solar' signifies solar demand.
on_peak_hours: (range) Range representing on-peak hours.
off_peak_hours: (list) List representing off-peak hours.
** Output
solar_demand: (list) List of tuples containing (PV value, hour_of_year, peak_type).
'''

solar_demand = []

for i, dispatchType in enumerate(hourDispatch):
if dispatchType == 'Solar':
## Determine the peak type (on-peak or off-peak)
peak_type = 'On-Peak' if i % 24 in on_peak_hours else 'Off-Peak'

## Append to the solar_demand list as a tuple
solar_demand.append((PV[i], i, peak_type))
#print(PV[i])

return solar_demand

def total_solar_cost(solar_demand, tou_rates):
'''
Calculate the total cost for solar demand based on the TOU rates.
** Inputs
solar_demand: (list) List of tuples containing (demand_value, hour_of_year, peak_type).
tou_rates: (dict) Dictionary containing the TOU rates for summer_on_peak and summer_off_peak hours.
** Output
total_cost: (float) Total cost for solar demand based on TOU rates.
'''

## Initialize total cost
total_cost = 0.0

## Loop through each tuple in solar_demand
for demand_value, hour_of_year, peak_type in solar_demand:
## Determine the rate based on the peak type
rate = tou_rates['summer_on_peak'] if peak_type == 'On-Peak' else tou_rates['summer_off_peak']
## Add the cost for this hour
total_cost += demand_value * rate

return total_cost

def work(modelDir, inputDict):
''' Run the model in its directory. '''
Expand Down Expand Up @@ -588,7 +734,7 @@ def makeGridLine(x,y,color,name):
outData['exportedPowerData'] = json.dumps(fig.data, cls=plotly.utils.PlotlyJSONEncoder)
outData['exportedPowerLayout'] = json.dumps(fig.layout, cls=plotly.utils.PlotlyJSONEncoder)

## DER Sharing Program options
########## DER Sharing Program options ##########
if (inputDict['utilityProgram']):
print('Considering utility DER sharing program \n')

Expand All @@ -605,16 +751,21 @@ def makeGridLine(x,y,color,name):
for name in filtered_names: ## Print the filtered names
print(name)

TOUname = 'Residential Time of Use'
TOUdata = []
TOUname = 'Residential Time of Use'
TOUlabel = '5b311c595457a3496d8367be'

for item in rate_info['items']:
if item['name'] == TOUname:
TOUdata.append(item)

print(TOUdata)

## NOTE: until the above section is working, use ad-hoc TOU rates
tou_rates = get_tou_rates_adhoc()

'''
## NOTE: The following dict is not being used because the the API is not returning these variables yet
rate_info = {
'rate_name': data['items'][0]['name'], ## Rate name
'fixed_monthly_charge': data['items'][0]['fixedmonthlycharge'], ## Fixed monthly charge ($)
Expand All @@ -628,13 +779,45 @@ def makeGridLine(x,y,color,name):
}
'''

## Generate peak shave schedule

## Determine area under the curve

## Apply rate compensations to DERs deployed
########## Generate peak shave schedule ##########

## List of (start, stop) hours for each month in a year
monthHours = [(0, 744), (744, 1416), (1416, 2160), (2160, 2880),
(2880, 3624), (3624, 4344), (4344, 5088), (5088, 5832),
(5832, 6552), (6552, 7296), (7296, 8016), (8016, 8760)]
dayHours = generate_day_hours(monthHours) ## Generate (start, stop) hours for each day
dispatch_schedule = create_dispatch_schedule(temperatures, dayHours)

## Plot dispatch schedule
import matplotlib.pyplot as plt
def plot_dispatch_schedule(dispatch_schedule):
plt.figure(figsize=(12, 4))
plt.plot(dispatch_schedule)
plt.xlabel("Hour of the Year")
plt.ylabel("Dispatch")
plt.title("Dispatch Schedule")
plt.grid(True)
plt.show()
plot_dispatch_schedule(dispatch_schedule)

########## Determine area under the curve ##########
## Typical on-peak and off-peak hours during hotter months (March to August)
on_peak_hours = range(14, 20) ## On-peak hours typically from 2:00 PM to 8:00 PM
off_peak_hours = list(range(0, 6)) + list(range(22, 24)) ## Off-peak hours typically from 10:00 PM to 6:00 AM

#print("Typical On-Peak Hours (24-hour format):", list(on_peak_hours))
#print("Typical Off-Peak Hours (24-hour format):", off_peak_hours)

## Get solar demand
solarDemand = solar_demand(PV, dispatch_schedule, on_peak_hours, off_peak_hours)
#print(solarDemand)

########## Apply rate compensations to DERs deployed ##########
## NOTE: the rateCompensation, fixedCompensation, and fixedCompensationFrequency variables are not used yet
total_cost = total_solar_cost(solarDemand, tou_rates) ## Calculate the total solar cost
print("Total cost for solar demand based on TOU rates: ${:.2f}".format(total_cost))

## Plot the peak shave schedule?
########## Plot the peak shave schedule? ##########

# Stdout/stderr.
outData['stdout'] = 'Success'
Expand Down Expand Up @@ -665,7 +848,7 @@ def new(modelDir):
'tempFileName': 'residential_extended_temperature_data.csv',
'demandCurve': demand_curve,
'tempCurve': temp_curve,
'PV': 'No',
'PV': 'Yes',
'BESS': 'Yes',
'generator': 'No',
'outage': True,
Expand Down

0 comments on commit 051279d

Please sign in to comment.