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

code that needs to be reviewed to see what is causing errors #3

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions data/parameter_validation.csv
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ electricity_rate,"int,float",0,1,,,,,
end_year,int,1998,2021,,,,,
existing,bool,,,,,,,
existing_components,dict,,,,,check_existing_components,,"Existing components can only include PV, Battery, Generator, or FuelTank objects, and have keys 'pv', 'batt', 'gen', or 'fuel_tank'."
existing_generator,bool,,,,,check_existing_generator,,
filter_constraints,list,,,,,check_filter_constraints,,"The filter_constraints list must contain dictionaries with the format: {parameter, type, value} where parameter can be any of the following: capital_cost_usd, pv_area_ft2, annual_benefits_usd, simple_payback_yr, fuel_used_gal mean, fuel_used_gal most-conservative, pv_capacity, or pv_percent mean and type can be [max, min] and value is the maximum or minimum allowable value."
fuel_curve_degree,int,1,3,,,,,
fuel_curve_model,dict,,,,,check_fuel_curve_model,,"The generator fuel curve model must be of the form {'1/4 Load (gal/hr)': val, '1/2 Load (gal/hr)': val, '3/4 Load (gal/hr)': val,
fuel_curve_model,dict,,,,,check_fuel_curve_model,,"The generator fuel curve model must be of the form {'1/4 Load (gal/hr)': val, '1/2 Load (gal/hr)': val, '3/4 Load (gal/hr)': val,
'Full Load (gal/hr)': val}"
fuel_used_gal,"int,float",0,,,,,,
gen_percent,"int,float",,,,,,,
Expand Down Expand Up @@ -142,4 +143,4 @@ om_cost,"int,float",0,500,,,,,
demand_rate_list,"int,float,list",,,,,check_demand_rate_list,,Demand charges must be in the form of a number or a list with 12 elements.
demand_rate,"int,float",0,50,,,,,
solar_source,str,,,"nsrdb,himawari",,check_solar_source,"start_year,end_year",The NSRDB dataset only contains data from 1998-2021. The Himawari dataset only contains data from 2016-2020.
scenario_criteria,str,,,"pv,gen",,,,
scenario_criteria,str,,,"pv,generator",,,,
15 changes: 13 additions & 2 deletions main_files/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def run_mcor(input_dict):
net_metering_rate=net_metering_inputs["net_metering_rate"],
demand_rate=demand_rate_inputs["demand_rate"],
existing_components=existing_components_inputs["existing_components"],
existing_generator='existing_generator' in input_dict['existing_components_inputs'],
output_tmy=True,
validate=True,
net_metering_limits=net_metering_inputs["net_metering_limits"],
Expand All @@ -101,7 +102,9 @@ def run_mcor(input_dict):
optim.get_load_profiles()

# Run all simulations
optim.run_sims_par()
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should change this back.

# optim.run_sims_par()
#run without multi-threading
optim.run_sims()

# Filter and rank results
optim.parse_results()
Expand Down Expand Up @@ -202,7 +205,15 @@ def run_mcor(input_dict):
# input_dict["existing_components_inputs"]["pv"] = PV(existing=True, pv_capacity=100, tilt=tilt, azimuth=azimuth,
# module_capacity=0.360, module_area=3, spacing_buffer=2,
# pv_tracking='fixed', pv_racking='ground')
# input_dict["existing_components_inputs"]["existing_components"] = {'pv': pv}
# input_dict["existing_components_inputs"]["existing_components"] = {"pv": ["pv"]}

input_dict["existing_components_inputs"]["existing_generator"] = Generator(existing=True, rated_power=500, num_units=1,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we'll want to specify all of the parameters that are passed into the Generator object in the input_dict and then create the Generator object separately, probably in microgrid_optimizer.define_grid(). The code that will be creating the input_dict in gridpiq will not have access to the Generator class, only the run_mcor function.

We should also do the same for any existing PV or Battery objects.

fuel_curve_model={'1/4 Load (gal/hr)': 11, '1/2 Load (gal/hr)': 18.5,
'3/4 Load (gal/hr)': 26.4, 'Full Load (gal/hr)': 35.7},
capital_cost=191000, ideal_minimum_load=0.3,
loading_level_to_add_unit=0.9,
loading_level_to_remove_unit=0.3, validate=True)
input_dict["existing_components_inputs"]["existing_components"] = {'generator': input_dict["existing_components_inputs"]["existing_generator"]}

# Specific PV and battery sizes dictionary
input_dict["specific_pv_battery_sizes_inputs"] = {}
Expand Down
68 changes: 39 additions & 29 deletions microgrid_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ def __init__(self, power_profiles, temp_profiles, night_profiles,
net_metering_limits=None,
generator_buffer=1.1,
gen_power_percent=(), existing_components={},
existing_generator=False,
off_grid_load_profile=None,
output_tmy=False, validate=True):

Expand All @@ -301,6 +302,7 @@ def __init__(self, power_profiles, temp_profiles, night_profiles,
self.generator_buffer = generator_buffer
self.gen_power_percent = gen_power_percent
self.existing_components = existing_components
self.existing_generator = existing_generator
self.off_grid_load_profile = off_grid_load_profile
self.output_tmy = output_tmy
self.load_profiles = []
Expand All @@ -321,6 +323,7 @@ def __init__(self, power_profiles, temp_profiles, night_profiles,
'battery_params': battery_params,
'duration': duration,
'gen_power_percent': gen_power_percent,
'existing_generator': existing_generator,
'dispatch_strategy': dispatch_strategy,
'batt_sizing_method': batt_sizing_method,
'system_costs': system_costs}
Expand Down Expand Up @@ -391,7 +394,7 @@ def size_PV_for_netzero(self):
# Calculate round-trip efficiency based on battery and inverter
# efficiency
system_rte = self.battery_params['one_way_battery_efficiency'] ** 2 \
* self.battery_params['one_way_inverter_efficiency'] ** 2
* self.battery_params['one_way_inverter_efficiency'] ** 2

# Calculate the amount of pv energy lost through
# charging/discharging batteries at the net zero capacity
Expand Down Expand Up @@ -464,7 +467,7 @@ def size_batt_for_no_pv_export(self, pv_sizes, load_profile):
excess_pv['pv_base'] = self.tmy_solar.values
for size in pv_sizes:
excess_pv[int(size)] = excess_pv['pv_base'] * size
excess_pv['{}_exported'.format(int(size))] = excess_pv[int(size)]\
excess_pv['{}_exported'.format(int(size))] = excess_pv[int(size)] \
- excess_pv['load']
excess_pv[excess_pv < 0] = 0

Expand Down Expand Up @@ -860,9 +863,14 @@ def aggregate_by_system(self, system, validate=True):
log_error(message)
raise Exception(message)

# Size and dispatch generator
simulation.size_single_generator(
self.system_costs['generator_costs'], validate=False)
if not self.existing_generator:
# Size and dispatch generator
simulation.size_single_generator(
self.system_costs['generator_costs'], validate=False)
else:
simulation.calc_existing_generator_dispatch(
self.system_costs['generator_costs'], validate=False
)

# Add the results to the lists
results_summary['pv_percent'] += \
Expand Down Expand Up @@ -923,13 +931,14 @@ def aggregate_by_system(self, system, validate=True):
results_summary['load_duration']['max_%_kW_not_met_max'] = \
max_kW_not_met.fillna(0).max(axis=1)

# Find the simulation with the largest generator and add that
# generator object to the system
max_gen_sim_num = \
np.where(results_summary['generator_power_kW']
== max(results_summary['generator_power_kW']))[0][0]
max_gen_sim = system.get_simulation(max_gen_sim_num)
system.add_component(max_gen_sim.generator_obj, validate=False)
if not self.existing_generator:
# Find the simulation with the largest generator and add that
# generator object to the system
max_gen_sim_num = \
np.where(results_summary['generator_power_kW']
== max(results_summary['generator_power_kW']))[0][0]
max_gen_sim = system.get_simulation(max_gen_sim_num)
system.add_component(max_gen_sim.generator_obj, validate=False)

return results_summary, system

Expand Down Expand Up @@ -988,7 +997,7 @@ def parse_results(self):
system.set_outputs(results_summary, validate=False)

# Turn results into a dataframe
system_row = pd.DataFrame.from_dict(results_summary).transpose().\
system_row = pd.DataFrame.from_dict(results_summary).transpose(). \
stack()
system_row.index = [' '.join(col).strip()
for col in system_row.index.values]
Expand Down Expand Up @@ -1194,12 +1203,12 @@ def plot_best_system(self, scenario_criteria='pv', scenario_num=None, validate=T
if scenario_criteria == 'pv':
# Find the outage periods with the max and min PV
criteria_dict = {key: val.sum() for key, val
in enumerate(self.power_profiles)}
in enumerate(self.power_profiles)}
scenario_label = 'Solar Irradiance Scenario'
else:
# Find the outage periods with the max and min generator runtime
criteria_dict = {key: val for key, val
in enumerate(system.outputs['fuel_used_gal'])}
in enumerate(system.outputs['fuel_used_gal'])}
scenario_label = 'Fuel Consumption Scenario'
max_scenario = max(criteria_dict, key=criteria_dict.get)
min_scenario = min(criteria_dict, key=criteria_dict.get)
Expand Down Expand Up @@ -1297,7 +1306,7 @@ def plot_system_dispatch(self, num_systems=None, plot_per_fig=3,
for plot_num, (system_name, _) in enumerate(
self.results_grid.iloc[
fig_num * plot_per_fig:fig_num * plot_per_fig + num_plots].
iterrows()):
iterrows()):
# Plot the maximum PV outage dispatch
ax = fig.add_subplot(num_plots, 2, plot_num * 2 + 1)
self.get_system(system_name).plot_dispatch(max_pv, ax=ax)
Expand Down Expand Up @@ -1396,13 +1405,13 @@ def format_inputs(self, spg):
if 'generator' in self.existing_components:
inputs['Existing Equipment'].loc['Generator'] = \
'{} units of {}kW'.format(
self.existing_components['generator'].num_units,
self.existing_components['generator'].rated_power)
self.existing_components['generator'].num_units,
self.existing_components['generator'].rated_power)
if 'battery' in self.existing_components:
inputs['Existing Equipment'].loc['Battery'] = \
'{}kW, {}kWh'.format(
self.existing_components['battery'].power,
self.existing_components['battery'].batt_capacity)
self.existing_components['battery'].power,
self.existing_components['battery'].batt_capacity)
if 'fuel_tank' in self.existing_components:
inputs['Existing Equipment'].loc['FuelTank'] = \
'{}gal'.format(self.existing_components['fuel_tank'].tank_size)
Expand All @@ -1429,7 +1438,7 @@ def format_inputs(self, spg):

assumptions['Generator'] = pd.DataFrame.from_dict(
{'sizing buffer': '{:.0f}%'.format(
self.generator_buffer*100-100)}, orient='index')
self.generator_buffer * 100 - 100)}, orient='index')

return inputs, assumptions

Expand Down Expand Up @@ -1515,6 +1524,7 @@ def save_results_to_file(self, spg, filename='simulation_results'):
for _ in self.gen_power_percent:
units += ['kW', 'gallons', 'gallons', '$', '', '', '', 'kWh',
'kWh', 'kW', 'kW']

format_results.loc['units'] = units

format_results.columns = [col.replace('_', ' ').capitalize()
Expand Down Expand Up @@ -1671,20 +1681,20 @@ def plot_compare_metrics(self, x_var='simple_payback_yr',

# Check that vars exist in results grid
if x_var not in self.results_grid.columns or y_var not in self.results_grid.columns:
return('ERROR: {} or {} are not valid output metrics, please '
'choose one of the following options: {}'.format(
x_var, y_var, ', '.join(self.results_grid.columns.values)))
return ('ERROR: {} or {} are not valid output metrics, please '
'choose one of the following options: {}'.format(
x_var, y_var, ', '.join(self.results_grid.columns.values)))

# Make pv and batt sizes categorical, so exact sizes are shown
# in the legend
results_mod = self.results_grid.copy()
results_mod['pv_capacity'] = results_mod['pv_capacity'].apply(
lambda x: str(int(x))+'kW')
pv_order = [str(elem2)+'kW' for elem2 in np.flipud(
lambda x: str(int(x)) + 'kW')
pv_order = [str(elem2) + 'kW' for elem2 in np.flipud(
np.sort([int(elem[:-2]) for elem in results_mod['pv_capacity'].unique()]))]
results_mod['battery_power'] = results_mod['battery_power'].apply(
lambda x: str(int(x))+'kW')
batt_order = [str(elem2)+'kW' for elem2 in np.flipud(
lambda x: str(int(x)) + 'kW')
batt_order = [str(elem2) + 'kW' for elem2 in np.flipud(
np.sort([int(elem[:-2]) for elem in results_mod['battery_power'].unique()]))]

# Create plot
Expand Down Expand Up @@ -1767,7 +1777,7 @@ def get_electricity_rate(location, validate=True):

# Return the electricity rate for that state in $/kWh
return rates.set_index('Name').loc[
state, 'Average retail price (cents/kWh)'] / 100
state, 'Average retail price (cents/kWh)'] / 100

except Exception as e:
# If there is an error, return the median electricity rate
Expand Down
77 changes: 11 additions & 66 deletions microgrid_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,7 @@ def size_single_generator(self, generator_options, validate=True):
self.dispatch_df[['load_not_met']], self.duration, validate=False)
self.load_duration_df = calculate_load_duration(grouped_load, validate=False)

def calc_existing_generator_dispatch(self, generator_options,
add_additional_generator=True,
validate=True):
def calc_existing_generator_dispatch(self, generator_options, validate=True):
"""
If there is an existing generator, determine how it meets the load and consumes fuel.

Expand All @@ -412,12 +410,12 @@ def calc_existing_generator_dispatch(self, generator_options,

# Validate input parameters
if validate:
args_dict = {'generator_options': generator_options,
'add_additional_generator': add_additional_generator}
args_dict = {'generator_costs': generator_options}
validate_all_parameters(args_dict)

# Get info from existing generator
gen = self.system.components['generator']

self.generator_power_kW = gen.rated_power

# Create a temporary dataframe to hold load not met cropped at the existing generator
Expand All @@ -435,68 +433,14 @@ def calc_existing_generator_dispatch(self, generator_options,
self.dispatch_df.loc[self.dispatch_df['load_not_met_by_generator'] < 0,
'load_not_met_by_generator'] = 0

# If the load cannot be fully met by the existing generator
if self.dispatch_df['load_not_met_by_generator'].sum() > 0:

# If another generator can be added
if add_additional_generator:

# Total rated power (including multiple units together) based on max unmet
# power
max_power = self.dispatch_df['load_not_met_by_generator'].max()

# Find the smallest generator with sufficent rated power, assumes generators
# are sorted from smallest to largest
addl_gen = None
num_gen = 1
while addl_gen is None:
# Find an appropriately sized generator
best_gen = generator_options[generator_options.index
* num_gen >= max_power
* self.generator_buffer]

# If no single generator is large enough, increase the number of
# generators
if not len(best_gen):
num_gen += 1
else:
# Create generator object
best_gen = best_gen.iloc[0]
self.generator_power_kW += best_gen.name*num_gen
addl_gen = Generator(
existing=False, rated_power=best_gen.name,
num_units=num_gen,
fuel_curve_model=best_gen[
['1/4 Load (gal/hr)', '1/2 Load (gal/hr)',
'3/4 Load (gal/hr)', 'Full Load (gal/hr)']].to_dict(),
capital_cost=best_gen['Cost (USD)'],
validate=False)
self.generator_obj = addl_gen

# Calculate the load duration curve and fuel consumption for the additional
# generator
grouped_load, addl_fuel_used = \
addl_gen.calculate_fuel_consumption(
self.dispatch_df[['load_not_met_by_generator']],
self.duration, validate=False)
self.load_duration_df = calculate_load_duration(grouped_load,
validate=False)
self.fuel_used_gal = existing_gen_fuel_used + addl_fuel_used

# If another generator cannot be dispatched
else:
self.load_duration_df = pd.DataFrame(
0, index=temp_load_duration_curve.index,
columns=temp_load_duration_curve.columns)
self.fuel_used_gal = existing_gen_fuel_used


# If the existing generator can meet load, use empty load duration curve and existing
# fuel used
else:
self.load_duration_df = pd.DataFrame(
0, index=temp_load_duration_curve.index,
columns=temp_load_duration_curve.columns)
self.fuel_used_gal = existing_gen_fuel_used

self.load_duration_df = pd.DataFrame(
0, index=temp_load_duration_curve.index,
columns=temp_load_duration_curve.columns)
self.fuel_used_gal = existing_gen_fuel_used

def get_load_breakdown(self):
return self.load_breakdown
Expand Down Expand Up @@ -639,7 +583,8 @@ def calculate_load_duration(grouped_load, validate=True):
# Run the simulation
sim.scale_power_profile()
sim.calc_dispatch()
sim.size_single_generator(generator_options, validate=False)
# sim.size_single_generator(generator_options, validate=False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

One of these two lines needs to but uncommented for it to run, right?

# sim.calc_existing_generator_dispatch(generator_options, validate=False)

# Plot dispatch
sim.dispatch_df[['load', 'pv_power', 'delta_battery_power', 'load_not_met']].plot()
Loading