Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -522,7 +522,7 @@ def clear_market_network(df_units: pd.DataFrame,
|
|
| 522 |
vom_u = float(vom_adders_map.get(tech_u, 0.0))
|
| 523 |
for t in range(T):
|
| 524 |
if row_u["is_renew"]:
|
| 525 |
-
#
|
| 526 |
vc_ut = vom_u
|
| 527 |
elif fuel_u == "nuclear":
|
| 528 |
vc_ut = float(nuc_vc[t]) + vom_u
|
|
@@ -673,22 +673,83 @@ with st.sidebar:
|
|
| 673 |
uploaded = st.file_uploader("Time-varying overrides CSV(任意)", type=["csv"])
|
| 674 |
|
| 675 |
st.header("スカラー既定(CSV欠損の補完に使用)")
|
| 676 |
-
usd_jpy0 = st.number_input("USD/JPY (scalar)", value=148.21, step=0.5)
|
| 677 |
-
lng_px0 = st.number_input("LNG (USD/MMBtu, scalar)", value=11.27, step=0.1)
|
| 678 |
-
coal_px0 = st.number_input("Coal (USD/ton, scalar)", value=130.0, step=1.0)
|
| 679 |
-
oil_px0 = st.number_input("Oil (USD/bbl, scalar)", value=80.0, step=1.0)
|
| 680 |
-
nuc_vc0 = st.number_input("Nuclear var cost (JPY/MWh, scalar)", value=2300.0, step=100.0)
|
| 681 |
-
rr0 = st.number_input("Reserve ratio (scalar)", value=0.03, step=0.005, min_value=0.0, max_value=0.2, format="%.3f")
|
| 682 |
-
voll0 = st.number_input("VOLL (JPY/MWh, scalar)", value=300000.0, step=10000.0)
|
| 683 |
-
|
| 684 |
-
spill_penalty_ui = st.number_input("Spill penalty (JPY/MWh)", value=1.0, step=1.0, min_value=0.0)
|
| 685 |
|
| 686 |
st.header("VOM adder [JPY/MWh]")
|
| 687 |
-
vom_df = st.data_editor(pd.DataFrame({
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
lng_s = ov.get("lng_usd_per_mmbtu", pd.Series(lng_px0, index=ts_slice.index))
|
| 693 |
coal_s = ov.get("coal_usd_per_ton", pd.Series(coal_px0, index=ts_slice.index))
|
| 694 |
oil_s = ov.get("oil_usd_per_bbl", pd.Series(oil_px0, index=ts_slice.index))
|
|
@@ -781,7 +842,7 @@ if st.button("シミュレーション実行(ネットワーク&実務スケ
|
|
| 781 |
st.plotly_chart(px.scatter(diag_df, x="Spill_MW", y="LMP", title=f"Spill vs LMP — {area}"), use_container_width=True)
|
| 782 |
st.plotly_chart(px.scatter(diag_df, x="Shed_MW", y="LMP", title=f"Shed vs LMP — {area}"), use_container_width=True)
|
| 783 |
|
| 784 |
-
#
|
| 785 |
disp = res["dispatch_df"]
|
| 786 |
units_ren = fleet_df[fleet_df["is_renew"]]
|
| 787 |
disp_merge = disp.merge(units_ren[["unit_id","region","tech","cap_MW","cf_key"]], on=["unit_id","region"], how="inner")
|
|
@@ -796,7 +857,7 @@ if st.button("シミュレーション実行(ネットワーク&実務スケ
|
|
| 796 |
slack = disp_merge.groupby(["Time","region"], as_index=False)["slack"].sum().pivot(index="Time", columns="region", values="slack").reindex(columns=regions)
|
| 797 |
st.plotly_chart(px.scatter(x=slack[area], y=lmp_df[area], labels={"x":"Renewable Slack (MW)", "y":"LMP"}, title=f"Renewable Slack vs LMP — {area}"), use_container_width=True)
|
| 798 |
|
| 799 |
-
#
|
| 800 |
eps = 1e-3
|
| 801 |
spill_hours = int((spill_df[area] > 1.0).sum())
|
| 802 |
near_spill_price_hours = int((((lmp_df[area] - spill_penalty_ui).abs() < eps) & (spill_df[area] > 1.0)).sum())
|
|
|
|
| 522 |
vom_u = float(vom_adders_map.get(tech_u, 0.0))
|
| 523 |
for t in range(T):
|
| 524 |
if row_u["is_renew"]:
|
| 525 |
+
# Give small variable O&M to renewables to avoid price pinning at 0
|
| 526 |
vc_ut = vom_u
|
| 527 |
elif fuel_u == "nuclear":
|
| 528 |
vc_ut = float(nuc_vc[t]) + vom_u
|
|
|
|
| 673 |
uploaded = st.file_uploader("Time-varying overrides CSV(任意)", type=["csv"])
|
| 674 |
|
| 675 |
st.header("スカラー既定(CSV欠損の補完に使用)")
|
| 676 |
+
usd_jpy0 = st.number_input("USD/JPY (scalar)", value=148.21, step=0.5)
|
| 677 |
+
lng_px0 = st.number_input("LNG (USD/MMBtu, scalar)", value=11.27, step=0.1)
|
| 678 |
+
coal_px0 = st.number_input("Coal (USD/ton, scalar)", value=130.0, step=1.0)
|
| 679 |
+
oil_px0 = st.number_input("Oil (USD/bbl, scalar)", value=80.0, step=1.0)
|
| 680 |
+
nuc_vc0 = st.number_input("Nuclear var cost (JPY/MWh, scalar)", value=2300.0, step=100.0)
|
| 681 |
+
rr0 = st.number_input("Reserve ratio (scalar)", value=0.03, step=0.005, min_value=0.0, max_value=0.2, format="%.3f")
|
| 682 |
+
voll0 = st.number_input("VOLL (JPY/MWh, scalar)", value=300000.0, step=10000.0)
|
| 683 |
+
spill_penalty_ui = st.number_input("Spill penalty (JPY/MWh)", value=1.0, step=1.0, min_value=0.0)
|
|
|
|
| 684 |
|
| 685 |
st.header("VOM adder [JPY/MWh]")
|
| 686 |
+
vom_df = st.data_editor(pd.DataFrame({
|
| 687 |
+
"tech": ["lng","coal","oil","nuclear","solar","onshore_wind","offshore_wind","river"],
|
| 688 |
+
"VOM": [400.0, 600.0, 1000.0, 800.0, 50.0, 80.0, 120.0, 30.0]
|
| 689 |
+
}), use_container_width=True)
|
| 690 |
+
|
| 691 |
+
st.header("ユニット数(各エリア)")
|
| 692 |
+
units_df = st.data_editor(pd.DataFrame({
|
| 693 |
+
"region": regions,
|
| 694 |
+
"lng_units":[6,10,25,10,4,20,8,4,10,0],
|
| 695 |
+
"coal_units":[3,6,10,6,3,10,5,2,7,0],
|
| 696 |
+
"oil_units":[2,3,6,3,2,5,3,2,3,0],
|
| 697 |
+
"nuc_units":[0,2,4,2,0,3,1,0,2,0],
|
| 698 |
+
"solar_units":[20,30,60,30,12,40,20,12,30,0],
|
| 699 |
+
"on_units":[10,15,25,15,6,20,10,6,15,0],
|
| 700 |
+
"off_units":[2,3,6,3,1,4,2,1,3,0],
|
| 701 |
+
"river_units":[5,8,12,8,4,12,6,3,8,0]
|
| 702 |
+
}), use_container_width=True)
|
| 703 |
+
|
| 704 |
+
st.header("容量レンジ [MW/ユニット]")
|
| 705 |
+
cap_bounds = {
|
| 706 |
+
"lng": (200.0, 900.0), "coal": (300.0, 1000.0), "oil": (100.0, 700.0), "nuclear": (500.0, 1400.0)
|
| 707 |
+
}
|
| 708 |
+
ren_bounds = {
|
| 709 |
+
"solar": (10.0, 200.0), "onshore_wind": (20.0, 300.0),
|
| 710 |
+
"offshore_wind": (100.0, 600.0), "river": (10.0, 200.0)
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
st.header("熱率レンジ(GJ/MWh, 乱数生成)")
|
| 714 |
+
hr_bounds = {
|
| 715 |
+
"lng": (6.2, 6.9), "coal": (7.8, 9.0), "oil": (8.8, 10.0), "nuclear": (np.nan, np.nan)
|
| 716 |
+
}
|
| 717 |
+
|
| 718 |
+
st.header("最低出力(比率レンジ, 乱数生成)")
|
| 719 |
+
minout_cfg = {"lng": (0.0, 0.2), "coal": (0.3, 0.6), "oil": (0.0, 0.4), "nuclear": (0.6, 0.9)}
|
| 720 |
+
|
| 721 |
+
st.header("可用性(FOR, 平均停止時間[h])")
|
| 722 |
+
for_map = st.data_editor(pd.DataFrame({
|
| 723 |
+
"fuel": ["lng","coal","oil","nuclear"], "FOR":[0.06,0.08,0.10,0.04], "mean_down_h":[24,48,24,120]
|
| 724 |
+
}), use_container_width=True)
|
| 725 |
+
|
| 726 |
+
st.header("ランプ比率(capの何倍/時)")
|
| 727 |
+
ramp_df = st.data_editor(pd.DataFrame({
|
| 728 |
+
"fuel": ["lng","coal","oil","nuclear"], "RU_frac":[0.50,0.20,0.30,0.05], "RD_frac":[0.50,0.20,0.30,0.05]
|
| 729 |
+
}), use_container_width=True)
|
| 730 |
+
|
| 731 |
+
# Build config dicts
|
| 732 |
+
counts_cfg = {}
|
| 733 |
+
for _, row in units_df.iterrows():
|
| 734 |
+
rname = row["region"]
|
| 735 |
+
counts_cfg[(rname, "lng")] = int(row["lng_units"])
|
| 736 |
+
counts_cfg[(rname, "coal")] = int(row["coal_units"])
|
| 737 |
+
counts_cfg[(rname, "oil")] = int(row["oil_units"])
|
| 738 |
+
counts_cfg[(rname, "nuclear")] = int(row["nuc_units"])
|
| 739 |
+
counts_cfg[(rname, "solar")] = int(row["solar_units"])
|
| 740 |
+
counts_cfg[(rname, "onshore_wind")] = int(row["on_units"])
|
| 741 |
+
counts_cfg[(rname, "offshore_wind")] = int(row["off_units"])
|
| 742 |
+
counts_cfg[(rname, "river")] = int(row["river_units"])
|
| 743 |
+
|
| 744 |
+
ramp_up_frac_map = {row["fuel"]: float(row["RU_frac"]) for _, row in ramp_df.iterrows()}
|
| 745 |
+
ramp_down_frac_map = {row["fuel"]: float(row["RD_frac"]) for _, row in ramp_df.iterrows()}
|
| 746 |
+
for_map_dict = {row["fuel"]: float(row["FOR"]) for _, row in for_map.iterrows()}
|
| 747 |
+
mean_down_map = {row["fuel"]: float(row["mean_down_h"]) for _, row in for_map.iterrows()}
|
| 748 |
+
|
| 749 |
+
# Time-varying series (override CSV or synthesized)
|
| 750 |
+
if uploaded is not None:
|
| 751 |
+
ov = load_overrides_csv(uploaded, ts_slice.index)
|
| 752 |
+
usd_jpy_s = ov.get("usd_jpy", pd.Series(usd_jpy0, index=ts_slice.index))
|
| 753 |
lng_s = ov.get("lng_usd_per_mmbtu", pd.Series(lng_px0, index=ts_slice.index))
|
| 754 |
coal_s = ov.get("coal_usd_per_ton", pd.Series(coal_px0, index=ts_slice.index))
|
| 755 |
oil_s = ov.get("oil_usd_per_bbl", pd.Series(oil_px0, index=ts_slice.index))
|
|
|
|
| 842 |
st.plotly_chart(px.scatter(diag_df, x="Spill_MW", y="LMP", title=f"Spill vs LMP — {area}"), use_container_width=True)
|
| 843 |
st.plotly_chart(px.scatter(diag_df, x="Shed_MW", y="LMP", title=f"Shed vs LMP — {area}"), use_container_width=True)
|
| 844 |
|
| 845 |
+
# Renewables slack vs LMP
|
| 846 |
disp = res["dispatch_df"]
|
| 847 |
units_ren = fleet_df[fleet_df["is_renew"]]
|
| 848 |
disp_merge = disp.merge(units_ren[["unit_id","region","tech","cap_MW","cf_key"]], on=["unit_id","region"], how="inner")
|
|
|
|
| 857 |
slack = disp_merge.groupby(["Time","region"], as_index=False)["slack"].sum().pivot(index="Time", columns="region", values="slack").reindex(columns=regions)
|
| 858 |
st.plotly_chart(px.scatter(x=slack[area], y=lmp_df[area], labels={"x":"Renewable Slack (MW)", "y":"LMP"}, title=f"Renewable Slack vs LMP — {area}"), use_container_width=True)
|
| 859 |
|
| 860 |
+
# Metrics
|
| 861 |
eps = 1e-3
|
| 862 |
spill_hours = int((spill_df[area] > 1.0).sum())
|
| 863 |
near_spill_price_hours = int((((lmp_df[area] - spill_penalty_ui).abs() < eps) & (spill_df[area] > 1.0)).sum())
|