Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Run ruff
run: |
ruff check .
ruff format
ruff format --check
- name: Run tests and collect coverage
run: |
# -rA displays the captured output for all tests after they're run
Expand Down
81 changes: 46 additions & 35 deletions examples/battery_control_comparison/runscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
df = pd.read_csv("../example_inputs/lmp_rt.csv")
df = df.rename(columns={"interval_start_utc": "time_utc"}).drop(columns=["market", "lmp"])
# Create reference that steps up and down each five minutes
reference_input_sequence = np.tile(np.array([20000, 0]), int(len(df)/2))
reference_input_sequence = np.tile(np.array([20000, 0]), int(len(df) / 2))
df["battery_power_reference"] = reference_input_sequence
# Add end of step info
df["time_utc"] = pd.to_datetime(df["time_utc"])
Expand All @@ -26,9 +26,9 @@
df = pd.merge(df, df_2, how="outer").sort_values("time_utc").reset_index(drop=True)
df.to_csv("power_reference.csv", index=False)


# Create some functions for simulating for simplicity
def simulate(soc_0, clipping_thresholds, gain):

h_dict = load_hercules_input("hercules_input.yaml")
h_dict["battery"]["initial_conditions"]["SOC"] = soc_0

Expand All @@ -42,11 +42,9 @@ def simulate(soc_0, clipping_thresholds, gain):
controller_parameters={"k_batt": gain, "clipping_thresholds": clipping_thresholds},
)
controller = HybridSupervisoryControllerMultiRef(
battery_controller=battery_controller,
interface=interface,
input_dict=hmodel.h_dict
battery_controller=battery_controller, interface=interface, input_dict=hmodel.h_dict
)

hmodel.assign_controller(controller)

# Run the simulation
Expand All @@ -61,16 +59,19 @@ def simulate(soc_0, clipping_thresholds, gain):

return time, power_sequence, soc_sequence, reference_sequence


def plot_results_soc(ax, color, time, power_sequence, soc_sequence):
ax[0].plot(time, power_sequence, color=color,
label="SOC initial: {:.3f}".format(soc_sequence[0]))
ax[0].plot(
time, power_sequence, color=color, label="SOC initial: {:.3f}".format(soc_sequence[0])
)
ax[1].plot(time, soc_sequence, color=color, label="SOC")


def plot_results_gain(ax, color, time, power_sequence, soc_sequence, gain):
ax[0].plot(time, power_sequence, color=color,
label="Gain: {:.3f}".format(gain))
ax[0].plot(time, power_sequence, color=color, label="Gain: {:.3f}".format(gain))
ax[1].plot(time, soc_sequence, color=color, label="SOC")


### SOC clipping

# Establish simulation options for demonstrating SOC clipping
Expand All @@ -79,66 +80,76 @@ def plot_results_gain(ax, color, time, power_sequence, soc_sequence, gain):
clipping_thresholds = [0.1, 0.2, 0.8, 0.9]

# Run simulations and create plots for SOC clipping
fig, ax = plt.subplots(2,1,sharex=True)
fig.set_size_inches(10,5)
fig, ax = plt.subplots(2, 1, sharex=True)
fig.set_size_inches(10, 5)
for soc_0, col in zip(starting_socs, colors):
time, pow, soc, ref = simulate(soc_0, clipping_thresholds, 0.01)
plot_results_soc(ax, col, time/60, pow, soc)
plot_results_soc(ax, col, time / 60, pow, soc)

# Add references and plot aesthetics
ax[0].plot(time/60, ref, color="black", linestyle="dashed", label="Reference")
ax[0].plot(time / 60, ref, color="black", linestyle="dashed", label="Reference")
ax[0].set_ylabel("Power [kW]")
ax[0].legend()

ax[1].set_ylabel("SOC [-]")
ax[1].set_xlabel("Time [min]")
ax[1].set_xlim([time[0]/60, time[-1]/60])
ax[1].set_xlim([time[0] / 60, time[-1] / 60])
ax[0].grid()
ax[1].grid()
ax[0].plot([time[0]/60, time[-1]/60], [20000, 20000], color="black", linestyle="dotted")
ax[0].plot([time[0]/60, time[-1]/60], [-20000, -20000], color="black", linestyle="dotted")
ax[0].plot([time[0] / 60, time[-1] / 60], [20000, 20000], color="black", linestyle="dotted")
ax[0].plot([time[0] / 60, time[-1] / 60], [-20000, -20000], color="black", linestyle="dotted")

# Add shading for the different clipping regions
ax[1].fill_between(time/60, 0, clipping_thresholds[0], color="black", alpha=0.2, edgecolor=None)
ax[1].fill_between(time/60, clipping_thresholds[0], clipping_thresholds[1], color="black",
alpha=0.1, edgecolor=None)
ax[1].fill_between(time/60, clipping_thresholds[2], clipping_thresholds[3], color="black",
alpha=0.1, edgecolor=None)
ax[1].fill_between(time/60, clipping_thresholds[3], 1, color="black", alpha=0.2, edgecolor=None)
ax[1].set_ylim([0,1])
ax[1].fill_between(time / 60, 0, clipping_thresholds[0], color="black", alpha=0.2, edgecolor=None)
ax[1].fill_between(
time / 60,
clipping_thresholds[0],
clipping_thresholds[1],
color="black",
alpha=0.1,
edgecolor=None,
)
ax[1].fill_between(
time / 60,
clipping_thresholds[2],
clipping_thresholds[3],
color="black",
alpha=0.1,
edgecolor=None,
)
ax[1].fill_between(time / 60, clipping_thresholds[3], 1, color="black", alpha=0.2, edgecolor=None)
ax[1].set_ylim([0, 1])
if save_figs:
fig.savefig(
"../../docs/graphics/battery-soc-clipping.png",
format="png", bbox_inches="tight", dpi=300
"../../docs/graphics/battery-soc-clipping.png", format="png", bbox_inches="tight", dpi=300
)

### k_batt gain

# Demonstrate different gains
gains = [0.001, 0.01, 0.1]
fig, ax = plt.subplots(2,1,sharex=True)
fig.set_size_inches(10,5)
fig, ax = plt.subplots(2, 1, sharex=True)
fig.set_size_inches(10, 5)
for gain, col in zip(gains, colors):
time, pow, soc, ref = simulate(0.5, clipping_thresholds, gain)
plot_results_gain(ax, col, time/60, pow, soc, gain)
plot_results_gain(ax, col, time / 60, pow, soc, gain)

# Add references and plot aesthetics
ax[0].plot(time/60, ref, color="black", linestyle="dashed", label="Reference")
ax[0].plot(time / 60, ref, color="black", linestyle="dashed", label="Reference")
ax[0].set_ylabel("Power [kW]")
ax[0].legend()

ax[1].set_ylabel("SOC [-]")
ax[1].set_xlabel("Time [min]")
ax[1].set_xlim([0, 15]) # Show only the first 15 to highlight differences
ax[1].set_xlim([0, 15]) # Show only the first 15 to highlight differences
ax[1].set_ylim([0.45, 0.51])
ax[0].grid()
ax[1].grid()
ax[0].plot([time[0]/60, time[-1]/60], [20000, 20000], color="black", linestyle="dotted")
ax[0].plot([time[0]/60, time[-1]/60], [-20000, -20000], color="black", linestyle="dotted")
ax[0].plot([time[0] / 60, time[-1] / 60], [20000, 20000], color="black", linestyle="dotted")
ax[0].plot([time[0] / 60, time[-1] / 60], [-20000, -20000], color="black", linestyle="dotted")
if save_figs:
fig.savefig(
"../../docs/graphics/battery-varying-gains.png",
format="png", bbox_inches="tight", dpi=300
"../../docs/graphics/battery-varying-gains.png", format="png", bbox_inches="tight", dpi=300
)

plt.show()
17 changes: 5 additions & 12 deletions examples/battery_market_revenue_control/plot_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ def plot_outputs():
ax = axarr[1]
ax.plot(
df["time"],
df["battery.power"]/1e3, # Base unit: kW
df["battery.power"] / 1e3, # Base unit: kW
label="Battery output",
color="k",
linewidth=1.0,
)
ax.plot(
df["time"],
df["battery.power_setpoint"]/1e3, # Base unit: kW
df["battery.power_setpoint"] / 1e3, # Base unit: kW
label="Battery setpoint",
color="k",
linestyle=":",
Expand All @@ -129,27 +129,20 @@ def plot_outputs():

color = "C0"
ax2.set_ylabel("State of charge [-]", color=color)
ax2.plot(
df["time"],
df["battery.soc"],
color=color
)
ax2.plot(df["time"], df["battery.soc"], color=color)
ax2.tick_params(axis="y", labelcolor=color)

for ax in axarr:
ax.grid(True)
ax.legend(loc="upper right")

# Compute total revenue on real-time market
df["revenue_rt"] = (
df["battery.power"]/1e3
* df["external_signals.lmp_rt"]
/ 3600
)
df["revenue_rt"] = df["battery.power"] / 1e3 * df["external_signals.lmp_rt"] / 3600
print("Real-time revenue over simulation: ${:.1f}".format(df["revenue_rt"].sum()))

return fig


if __name__ == "__main__":
fig = plot_outputs()
# fig.savefig("../../docs/graphics/battery-market.png", dpi=300, format="png")
Expand Down
9 changes: 3 additions & 6 deletions examples/battery_market_revenue_control/runscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,17 @@

# Generate the LMP data needed for the simulation
df_lmp = generate_locational_marginal_price_dataframe_from_gridstatus(
pd.read_csv("../example_inputs/lmp_da.csv"),
pd.read_csv("../example_inputs/lmp_rt.csv")
pd.read_csv("../example_inputs/lmp_da.csv"), pd.read_csv("../example_inputs/lmp_rt.csv")
)
df_lmp.to_csv("lmp_data.csv", index=False)

# Load the input file and establish the Hercules model
hmodel = HerculesModel("hercules_input.yaml")

# Establish the interface and controller, assign to the Hercules model
interface=HerculesInterface(hmodel.h_dict)
interface = HerculesInterface(hmodel.h_dict)
controller = HybridSupervisoryControllerMultiRef(
battery_controller=BatteryPriceSOCController(
interface=interface, input_dict=hmodel.h_dict
),
battery_controller=BatteryPriceSOCController(interface=interface, input_dict=hmodel.h_dict),
interface=HerculesInterface(hmodel.h_dict),
input_dict=hmodel.h_dict,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
a range of offset lookup tables and comparing them to one-another.
"""


import matplotlib.pyplot as plt
from floris import FlorisModel
from hycon.design_tools import wake_steering_design as wsd, wake_steering_visualization as wsv
Expand Down Expand Up @@ -77,8 +76,8 @@
wd_std = 3.0
ws_main = 8.0
wd_rate_limit = 3.0
ws_rate_limit = 100.0 # No rate limit on wind speed
ti_rate_limit = 1e3 # No rate limit on turbulence intensity
ws_rate_limit = 100.0 # No rate limit on wind speed
ti_rate_limit = 1e3 # No rate limit on turbulence intensity
plot_turbine = 0
plot_wd_lims = (240, 300)

Expand All @@ -87,7 +86,7 @@
col_unc = "C0"
col_rate_limited = "C1"
col_ws_ramps = "C2"

fmodel = FlorisModel(floris_dict)

print("Building simple lookup table.")
Expand Down Expand Up @@ -149,7 +148,7 @@
ws_wake_steering_cut_in=3.0,
ws_wake_steering_fully_engaged_low=4.0,
ws_wake_steering_fully_engaged_high=11.0,
ws_wake_steering_cut_out=13.0
ws_wake_steering_cut_out=13.0,
)

# Plot various designs
Expand All @@ -162,7 +161,7 @@
ti_plot=ti_min,
color=col_simple,
label="Simple",
ax=ax
ax=ax,
)

wsv.plot_offsets_wd(
Expand All @@ -172,7 +171,7 @@
ti_plot=ti_min,
color=col_unc,
label="Uncertain",
ax=ax
ax=ax,
)

wsv.plot_offsets_wd(
Expand All @@ -182,7 +181,7 @@
ti_plot=ti_min,
color=col_rate_limited,
label="Rate limited",
ax=ax
ax=ax,
)

wsv.plot_offsets_wd(
Expand All @@ -193,7 +192,7 @@
color=col_ws_ramps,
label="Single wind speed",
linestyle="dotted",
ax=ax
ax=ax,
)

ax.set_title("Yaw offsets at {} m/s".format(ws_main))
Expand All @@ -204,52 +203,52 @@
ax.legend()

# Also, plot heatmap of offsets for Simple design
fig, ax = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10,10))
fig, ax = plt.subplots(2, 2, sharex=True, sharey=True, figsize=(10, 10))
_, cbar = wsv.plot_offsets_wdws_heatmap(
df_opt_simple,
plot_turbine,
ti_plot=ti_min,
vmax=maximum_yaw_angle,
vmin=minimum_yaw_angle,
ax=ax[0,0]
ax=ax[0, 0],
)
cbar.set_label("Yaw offset [deg]")
ax[0,0].set_title("Simple")
ax[0, 0].set_title("Simple")
_, cbar = wsv.plot_offsets_wdws_heatmap(
df_opt_unc,
plot_turbine,
ti_plot=ti_min,
vmax=maximum_yaw_angle,
vmin=minimum_yaw_angle,
ax=ax[0,1]
ax=ax[0, 1],
)
cbar.set_label("Yaw offset [deg]")
ax[0,1].set_title("Uncertain")
ax[0, 1].set_title("Uncertain")
_, cbar = wsv.plot_offsets_wdws_heatmap(
df_opt_rate_limited,
plot_turbine,
ti_plot=ti_min,
vmax=maximum_yaw_angle,
vmin=minimum_yaw_angle,
ax=ax[1,0]
ax=ax[1, 0],
)
cbar.set_label("Yaw offset [deg]")
ax[1,0].set_title("Rate limited")
ax[1, 0].set_title("Rate limited")
_, cbar = wsv.plot_offsets_wdws_heatmap(
df_opt_ws_ramps,
plot_turbine,
ti_plot=ti_min,
vmax=maximum_yaw_angle,
vmin=minimum_yaw_angle,
ax=ax[1,1]
ax=ax[1, 1],
)
cbar.set_label("Yaw offset [deg]")
ax[1,1].set_title("Single wind speed heuristic")
ax[1, 1].set_title("Single wind speed heuristic")

for ax_ in ax[:,0]:
for ax_ in ax[:, 0]:
ax_.set_ylabel("Wind speed [m/s]")
for ax_ in ax[-1,:]:
for ax_ in ax[-1, :]:
ax_.set_xlabel("Wind direction [deg]")
ax[0,0].set_xlim(plot_wd_lims)
ax[0, 0].set_xlim(plot_wd_lims)

plt.show()
Loading