naohiro701 commited on
Commit
867436b
·
verified ·
1 Parent(s): 6f219ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -17
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
- "tech": ["lng","coal","oil","nuclear","solar","onshore_wind","offshore_wind","river"],
689
- # 既定は小さめの値(0~1000JPY/MWh程度): 実勢VOMは別途CSVで上書き可能
690
- "VOM": [400.0, 600.0, 1000.0, 800.0, 50.0, 80.0, 120.0, 30.0]
691
- }), use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # 再エネ余裕(未使用キャパ)とLMPの関係
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())