dwind 0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dwind/__init__.py +3 -0
- dwind/btm_sizing.py +129 -0
- dwind/config.py +118 -0
- dwind/helper.py +172 -0
- dwind/loader.py +59 -0
- dwind/model.py +371 -0
- dwind/mp.py +225 -0
- dwind/resource.py +166 -0
- dwind/run.py +288 -0
- dwind/scenarios.py +139 -0
- dwind/valuation.py +1562 -0
- dwind-0.3.dist-info/METADATA +168 -0
- dwind-0.3.dist-info/RECORD +17 -0
- dwind-0.3.dist-info/WHEEL +5 -0
- dwind-0.3.dist-info/entry_points.txt +2 -0
- dwind-0.3.dist-info/licenses/LICENSE.txt +29 -0
- dwind-0.3.dist-info/top_level.txt +1 -0
dwind/resource.py
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
import h5py as h5
|
2
|
+
import pandas as pd
|
3
|
+
|
4
|
+
from dwind import Configuration
|
5
|
+
|
6
|
+
|
7
|
+
class ResourcePotential:
|
8
|
+
def __init__(
|
9
|
+
self, parcels, model_config: Configuration, tech="wind", application="fom", year="2018"
|
10
|
+
):
|
11
|
+
self.df = parcels
|
12
|
+
self.tech = tech
|
13
|
+
self.application = application
|
14
|
+
self.year = year
|
15
|
+
self.config = model_config
|
16
|
+
|
17
|
+
if self.tech not in ("wind", "solar"):
|
18
|
+
raise ValueError("`tech` must be one of 'solar' or 'wind'.")
|
19
|
+
|
20
|
+
def create_rev_gid_to_summary_lkup(self, configs, save_csv=True):
|
21
|
+
config_dfs = []
|
22
|
+
for c in configs:
|
23
|
+
file_str = self.config.rev.DIR / f"rev_{c}_generation_{self.year}.h5"
|
24
|
+
|
25
|
+
with h5.File(file_str, "r") as hf:
|
26
|
+
rev_index = pd.DataFrame(hf["meta"][...]).index.to_series()
|
27
|
+
gids = pd.DataFrame(hf["meta"][...])[["gid"]]
|
28
|
+
annual_energy = pd.DataFrame(hf["annual_energy"][...])
|
29
|
+
cf_mean = pd.DataFrame(hf["cf_mean"][...])
|
30
|
+
|
31
|
+
config_df = pd.concat([rev_index, gids, annual_energy, cf_mean], axis=1)
|
32
|
+
config_df.columns = [
|
33
|
+
f"rev_index_{self.tech}",
|
34
|
+
f"rev_gid_{self.tech}",
|
35
|
+
f"{self.tech}_naep",
|
36
|
+
f"{self.tech}_cf",
|
37
|
+
]
|
38
|
+
|
39
|
+
config_df["config"] = c
|
40
|
+
config_dfs.append(config_df)
|
41
|
+
|
42
|
+
summary_df = pd.concat(config_dfs)
|
43
|
+
|
44
|
+
if save_csv:
|
45
|
+
save_name = (
|
46
|
+
self.config.rev.generation[f"{self.tech}_DIR"]
|
47
|
+
/ f"lkup_rev_gid_to_summary_{self.tech}_{self.year}.csv"
|
48
|
+
)
|
49
|
+
summary_df.to_csv(save_name, index=False)
|
50
|
+
|
51
|
+
return summary_df
|
52
|
+
|
53
|
+
def find_rev_summary_table(self):
|
54
|
+
if self.tech == "solar":
|
55
|
+
configs = self.config.rev.settings.solar
|
56
|
+
config_col = "solar_az_tilt"
|
57
|
+
col_list = ["gid", f"rev_gid_{self.tech}", config_col]
|
58
|
+
self.df[config_col] = self.df[f"azimuth_{self.application}"].map(
|
59
|
+
self.config.rev.settings.azimuth_direction_to_degree
|
60
|
+
)
|
61
|
+
self.df[config_col] = (
|
62
|
+
self.df[config_col].astype(str) + "_" + self.df[f"tilt_{self.tech}"].astype(str)
|
63
|
+
)
|
64
|
+
elif self.tech == "wind":
|
65
|
+
configs = self.rev.settings.wind
|
66
|
+
config_col = "turbine_class"
|
67
|
+
col_list = [
|
68
|
+
"gid",
|
69
|
+
f"rev_gid_{self.tech}",
|
70
|
+
config_col,
|
71
|
+
"turbine_height_m",
|
72
|
+
"wind_turbine_kw",
|
73
|
+
]
|
74
|
+
self.df[config_col] = self.df["wind_turbine_kw"].map(self.config.rev.turbine_class_dict)
|
75
|
+
|
76
|
+
out_cols = [*col_list, f"rev_index_{self.tech}", f"{self.tech}_naep", f"{self.tech}_cf"]
|
77
|
+
|
78
|
+
f_gen = (
|
79
|
+
self.config.rev.generation[f"{self.tech}_DIR"]
|
80
|
+
/ f"lkup_rev_gid_to_summary_{self.tech}_{self.year}.csv"
|
81
|
+
)
|
82
|
+
|
83
|
+
if f_gen.exists():
|
84
|
+
generation_summary = pd.read_csv(f_gen)
|
85
|
+
else:
|
86
|
+
generation_summary = self.create_rev_gid_to_summary_lkup(configs)
|
87
|
+
|
88
|
+
generation_summary = (
|
89
|
+
generation_summary.reset_index(drop=True)
|
90
|
+
.drop_duplicates(subset=[f"rev_index_{self.tech}", "config"])
|
91
|
+
.rename(columns={"config": config_col})
|
92
|
+
)
|
93
|
+
agents = self.df.merge(
|
94
|
+
generation_summary, how="left", on=[f"rev_index_{self.tech}", config_col]
|
95
|
+
)
|
96
|
+
return agents[out_cols]
|
97
|
+
|
98
|
+
def prepare_agents_for_gen(self):
|
99
|
+
# create lookup column based on each tech
|
100
|
+
if self.tech == "wind":
|
101
|
+
# drop wind turbine size duplicates
|
102
|
+
# SINCE WE ASSUME ANY TURBINE IN A GIVEN CLASS HAS THE SAME POWER CURVE
|
103
|
+
self.df.drop_duplicates(subset=["gid", "wind_size_kw"], keep="last", inplace=True)
|
104
|
+
# if running FOM application, only consider a single (largest) turbine size
|
105
|
+
if self.application == "fom":
|
106
|
+
self.df = self.df.loc[self.df["wind_size_kw"] == self.df["wind_size_kw_fom"]]
|
107
|
+
|
108
|
+
self.df["turbine_class"] = self.df["wind_turbine_kw"].map(
|
109
|
+
self.config.rev.turbine_class_dict
|
110
|
+
)
|
111
|
+
|
112
|
+
if self.tech == "solar":
|
113
|
+
# NOTE: tilt and azimuth are application-specific
|
114
|
+
self.df["solar_az_tilt"] = self.df[f"azimuth_{self.application}"].map(
|
115
|
+
self.config.rev.settings.azimuth_direction_to_degree
|
116
|
+
)
|
117
|
+
self.df["solar_az_tilt"] = self.df["solar_az_tilt"].astype(str)
|
118
|
+
self.df["solar_az_tilt"] = (
|
119
|
+
self.df["solar_az_tilt"] + "_" + self.df[f"tilt_{self.application}"].astype(str)
|
120
|
+
)
|
121
|
+
|
122
|
+
def merge_gen_to_agents(self, tech_agents):
|
123
|
+
if self.tech == "wind":
|
124
|
+
cols = ["turbine_height_m", "wind_turbine_kw", "turbine_class"]
|
125
|
+
else:
|
126
|
+
# NOTE: need to drop duplicates in solar agents
|
127
|
+
# since multiple rows exist due to multiple turbine configs for a given parcel
|
128
|
+
tech_agents = tech_agents.drop_duplicates(
|
129
|
+
subset=["gid", "rev_gid_solar", "solar_az_tilt"]
|
130
|
+
)
|
131
|
+
cols = ["solar_az_tilt"]
|
132
|
+
|
133
|
+
cols.extend(["gid", f"rev_index_{self.tech}"])
|
134
|
+
|
135
|
+
self.df = self.df.merge(tech_agents, how="left", on=cols)
|
136
|
+
|
137
|
+
def match_rev_summary_to_agents(self):
|
138
|
+
self.prepare_agents_for_gen()
|
139
|
+
tech_agents = self.find_rev_summary_table()
|
140
|
+
self.merge_gen_to_agents(tech_agents)
|
141
|
+
|
142
|
+
if self.tech == "wind":
|
143
|
+
# fill nan generation values
|
144
|
+
self.df = self.df.loc[
|
145
|
+
~((self.df["wind_naep"].isnull()) & (self.df["turbine_class"] != "none"))
|
146
|
+
]
|
147
|
+
self.df["wind_naep"] = self.df["wind_naep"].fillna(0.0)
|
148
|
+
self.df["wind_cf"] = self.df["wind_cf"].fillna(0.0)
|
149
|
+
# self.df['wind_cf_hourly'] = self.df['wind_cf_hourly'].fillna(0.)
|
150
|
+
# calculate annual energy production (aep)
|
151
|
+
self.df["wind_aep"] = self.df["wind_naep"] * self.df["wind_turbine_kw"]
|
152
|
+
# self.df = self.df.drop(columns="turbine_class")
|
153
|
+
else:
|
154
|
+
# fill nan generation values
|
155
|
+
self.df = self.df.loc[~(self.df["solar_naep"].isnull())]
|
156
|
+
# size groundmount system to equal wind aep
|
157
|
+
# self.df['solar_size_kw_fom'] = np.where(
|
158
|
+
# self.df['solar_groundmount'],
|
159
|
+
# self.df['wind_aep'] / (self.df['solar_cf'] * 8760),
|
160
|
+
# self.df['solar_size_kw_fom']
|
161
|
+
# )
|
162
|
+
|
163
|
+
# calculate annual energy production (aep)
|
164
|
+
self.df["solar_aep"] = self.df["solar_naep"] * self.df["solar_size_kw_fom"]
|
165
|
+
|
166
|
+
return self.df
|
dwind/run.py
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
"""Enables running dwind as a CLI tool, the primary interface for working with dwind."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import sys
|
6
|
+
import tomllib
|
7
|
+
from enum import Enum
|
8
|
+
from typing import Annotated
|
9
|
+
from pathlib import Path
|
10
|
+
|
11
|
+
import typer
|
12
|
+
import pandas as pd
|
13
|
+
|
14
|
+
|
15
|
+
# from memory_profiler import profile
|
16
|
+
|
17
|
+
|
18
|
+
app = typer.Typer()
|
19
|
+
|
20
|
+
DWIND = Path("/projects/dwind/agents")
|
21
|
+
|
22
|
+
|
23
|
+
class Sector(str, Enum):
|
24
|
+
"""Typer helper for validating the sector input.
|
25
|
+
|
26
|
+
- "fom": Front of meter
|
27
|
+
- "btm": Behind the meter
|
28
|
+
"""
|
29
|
+
|
30
|
+
fom = "fom"
|
31
|
+
btm = "btm"
|
32
|
+
|
33
|
+
|
34
|
+
class Scenario(str, Enum):
|
35
|
+
"""Scenario to run.
|
36
|
+
|
37
|
+
The only current option is "baseline".
|
38
|
+
"""
|
39
|
+
|
40
|
+
baseline = "baseline"
|
41
|
+
|
42
|
+
|
43
|
+
def year_callback(ctx: typer.Context, param: typer.CallbackParam, value: int):
|
44
|
+
"""Typer helper to validate the year input.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
ctx : typer.Context
|
49
|
+
The Typer context.
|
50
|
+
param : typer.CallbackParam
|
51
|
+
The Typer parameter.
|
52
|
+
value : int
|
53
|
+
User input for the analysis year basis, must be one of 2022, 2024, or 2025.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
-------
|
57
|
+
int
|
58
|
+
The input :py:param:`value`, if it is a valid input.
|
59
|
+
|
60
|
+
Raises:
|
61
|
+
------
|
62
|
+
typer.BadParameter
|
63
|
+
Raised if the input is not one of 2022, 2024, or 2025.
|
64
|
+
"""
|
65
|
+
valid_years = (2022, 2024, 2025, 2035, 2040)
|
66
|
+
if ctx.resilient_parsing:
|
67
|
+
return
|
68
|
+
if value not in valid_years:
|
69
|
+
raise typer.BadParameter(f"Only {valid_years} are valid options for `year`, not {value}.")
|
70
|
+
return value
|
71
|
+
|
72
|
+
|
73
|
+
def load_agents(
|
74
|
+
file_name: str | Path | None = None,
|
75
|
+
location: str | None = None,
|
76
|
+
sector: str | None = None,
|
77
|
+
) -> pd.DataFrame:
|
78
|
+
"""Load the agent file based on a filename or the location and sector to a Pandas DataFrame,
|
79
|
+
and return the data frame.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
file_name (str | Path | None, optional): Name of the agent file, if not auto-generating from
|
83
|
+
the :py:attr:`location` and :py:attr:`sector` inputs. Defaults to None.
|
84
|
+
location (str | None, optional): The name of the location or grouping, such as
|
85
|
+
"colorado_larimer" or "priority1". Defaults to None.
|
86
|
+
sector (str | None, optional): The name of the section. Must be one of "btm" or "fom".
|
87
|
+
Defaults to None.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
pd.DataFrame: The agent DataFrame.
|
91
|
+
"""
|
92
|
+
from dwind.model import Agents
|
93
|
+
|
94
|
+
if file_name is None and (location is None or sector is None):
|
95
|
+
raise ValueError("One of `file_name` or `location` and `sector` must be provided.")
|
96
|
+
|
97
|
+
f_agents = (
|
98
|
+
file_name if file_name is not None else DWIND / f"{location}/agents_dwind_{sector}.parquet"
|
99
|
+
)
|
100
|
+
if not f_agents.exists():
|
101
|
+
f_agents = f_agents.with_suffix(".pickle")
|
102
|
+
agents = Agents(agent_file=f_agents).agents
|
103
|
+
return agents
|
104
|
+
|
105
|
+
|
106
|
+
@app.command()
|
107
|
+
def run_hpc(
|
108
|
+
location: Annotated[
|
109
|
+
str, typer.Option(help="The state, state_county, or priority region to run.")
|
110
|
+
],
|
111
|
+
sector: Annotated[
|
112
|
+
Sector, typer.Option(help="One of fom (front of meter) or btm (back-of-the-meter).")
|
113
|
+
],
|
114
|
+
scenario: Annotated[
|
115
|
+
Scenario,
|
116
|
+
typer.Option(help="The scenario to run (baseline is the current only option)."),
|
117
|
+
],
|
118
|
+
year: Annotated[
|
119
|
+
int,
|
120
|
+
typer.Option(
|
121
|
+
callback=year_callback,
|
122
|
+
help="The assumption year for the analysis. Options are 2022, 2024, and 2025.",
|
123
|
+
),
|
124
|
+
],
|
125
|
+
repository: Annotated[
|
126
|
+
str, typer.Option(help="Path to the dwind repository to use when running the model.")
|
127
|
+
],
|
128
|
+
nodes: Annotated[
|
129
|
+
int,
|
130
|
+
typer.Option(
|
131
|
+
help="Number of HPC nodes or CPU nodes to run on. -1 indicates 75% of CPU limit."
|
132
|
+
),
|
133
|
+
],
|
134
|
+
allocation: Annotated[str, typer.Option(help="HPC allocation name.")],
|
135
|
+
memory: Annotated[int, typer.Option(help="Node memory, in GB (HPC only).")],
|
136
|
+
walltime: Annotated[int, typer.Option(help="Node walltime request, in hours.")],
|
137
|
+
feature: Annotated[
|
138
|
+
str,
|
139
|
+
typer.Option(
|
140
|
+
help=(
|
141
|
+
"Additional flags for the SLURM job, using formatting such as"
|
142
|
+
" --qos=high or --depend=[state:job_id]."
|
143
|
+
)
|
144
|
+
),
|
145
|
+
],
|
146
|
+
env: Annotated[
|
147
|
+
str,
|
148
|
+
typer.Option(
|
149
|
+
help="The path to the dwind Python environment that should be used to run the model."
|
150
|
+
),
|
151
|
+
],
|
152
|
+
model_config: Annotated[
|
153
|
+
str, typer.Option(help="Complete file name and path of the model configuration file")
|
154
|
+
],
|
155
|
+
dir_out: Annotated[
|
156
|
+
str, typer.Option(help="Path to where the chunked outputs should be saved.")
|
157
|
+
],
|
158
|
+
stdout_path: Annotated[str | None, typer.Option(help="The path to write stdout logs.")] = None,
|
159
|
+
):
|
160
|
+
"""Run dwind via the HPC multiprocessing interface."""
|
161
|
+
sys.path.append(repository)
|
162
|
+
from dwind.mp import MultiProcess
|
163
|
+
|
164
|
+
# NOTE: collect_by_priority has been removed but may need to be reinstated
|
165
|
+
|
166
|
+
mp = MultiProcess(
|
167
|
+
location=location,
|
168
|
+
sector=sector,
|
169
|
+
scenario=scenario,
|
170
|
+
year=year,
|
171
|
+
env=env,
|
172
|
+
n_nodes=nodes,
|
173
|
+
memory=memory,
|
174
|
+
walltime=walltime,
|
175
|
+
allocation=allocation,
|
176
|
+
feature=feature,
|
177
|
+
repository=repository,
|
178
|
+
model_config=model_config,
|
179
|
+
dir_out=dir_out,
|
180
|
+
stdout_path=stdout_path,
|
181
|
+
)
|
182
|
+
|
183
|
+
agent_df = load_agents(location=location, sector=sector)
|
184
|
+
mp.run_jobs(agent_df)
|
185
|
+
|
186
|
+
|
187
|
+
@app.command("run-config")
|
188
|
+
def run_hpc_from_config(
|
189
|
+
config_path: Annotated[
|
190
|
+
str, typer.Argument(help="Path to configuration TOML with run and model parameters.")
|
191
|
+
],
|
192
|
+
):
|
193
|
+
"""Run dwind via the HPC multiprocessing interface from a configuration file."""
|
194
|
+
config_path = Path(config_path).resolve()
|
195
|
+
with config_path.open("rb") as f:
|
196
|
+
config = tomllib.load(f)
|
197
|
+
print(config)
|
198
|
+
|
199
|
+
run_hpc(**config)
|
200
|
+
|
201
|
+
|
202
|
+
@app.command()
|
203
|
+
def run_chunk(
|
204
|
+
# start: Annotated[int, typer.Option(help="chunk start index")],
|
205
|
+
# end: Annotated[int, typer.Option(help="chunk end index")],
|
206
|
+
location: Annotated[
|
207
|
+
str, typer.Option(help="The state, state_county, or priority region to run.")
|
208
|
+
],
|
209
|
+
sector: Annotated[
|
210
|
+
Sector, typer.Option(help="One of fom (front of meter) or btm (back-of-the-meter).")
|
211
|
+
],
|
212
|
+
scenario: Annotated[str, typer.Option(help="The scenario to run, such as baseline.")],
|
213
|
+
year: Annotated[
|
214
|
+
int, typer.Option(callback=year_callback, help="The year basis of the scenario.")
|
215
|
+
],
|
216
|
+
chunk_ix: Annotated[int, typer.Option(help="Chunk number/index. Used for logging.")],
|
217
|
+
out_path: Annotated[str, typer.Option(help="save path")],
|
218
|
+
repository: Annotated[
|
219
|
+
str, typer.Option(help="Path to the dwind repository to use when running the model.")
|
220
|
+
],
|
221
|
+
model_config: Annotated[
|
222
|
+
str, typer.Option(help="Complete file name and path of the model configuration file")
|
223
|
+
],
|
224
|
+
):
|
225
|
+
"""Run a chunk of a dwind model. Internal only, do not run outside the context of a
|
226
|
+
chunked analysis.
|
227
|
+
"""
|
228
|
+
# Import the correct version of the library
|
229
|
+
sys.path.append(repository)
|
230
|
+
from dwind.model import Model
|
231
|
+
|
232
|
+
agent_file = Path(out_path).resolve() / f"agents_{chunk_ix}.pqt"
|
233
|
+
agents = load_agents(file_name=agent_file)
|
234
|
+
agent_file.unlink()
|
235
|
+
|
236
|
+
model = Model(
|
237
|
+
agents=agents,
|
238
|
+
location=location,
|
239
|
+
sector=sector,
|
240
|
+
scenario=scenario,
|
241
|
+
year=year,
|
242
|
+
chunk_ix=chunk_ix,
|
243
|
+
out_path=out_path,
|
244
|
+
model_config=model_config,
|
245
|
+
)
|
246
|
+
model.run()
|
247
|
+
|
248
|
+
|
249
|
+
@app.command()
|
250
|
+
def run(
|
251
|
+
location: Annotated[
|
252
|
+
str, typer.Option(help="The state, state_county, or priority region to run.")
|
253
|
+
],
|
254
|
+
sector: Annotated[
|
255
|
+
Sector, typer.Option(help="One of fom (front of meter) or btm (back-of-the-meter).")
|
256
|
+
],
|
257
|
+
scenario: Annotated[str, typer.Option(help="The scenario to run, such as 'baseline'.")],
|
258
|
+
year: Annotated[int, typer.Option(help="The year basis of the scenario.")],
|
259
|
+
out_path: Annotated[str, typer.Option(help="save path")],
|
260
|
+
repository: Annotated[
|
261
|
+
str, typer.Option(help="Path to the dwind repository to use when running the model.")
|
262
|
+
],
|
263
|
+
model_config: Annotated[
|
264
|
+
str, typer.Option(help="Complete file name and path of the model configuration file")
|
265
|
+
],
|
266
|
+
):
|
267
|
+
"""Run the dwind model. Does not yet work, do not run unless dwind has been configured
|
268
|
+
to not rely on Kestrel usage.
|
269
|
+
"""
|
270
|
+
# Import the correct version of the library
|
271
|
+
sys.path.append(repository)
|
272
|
+
from dwind.model import Model
|
273
|
+
|
274
|
+
agents = load_agents(location=location, sector=sector)
|
275
|
+
model = Model(
|
276
|
+
agents=agents,
|
277
|
+
location=location,
|
278
|
+
sector=sector,
|
279
|
+
scenario=scenario,
|
280
|
+
year=year,
|
281
|
+
out_path=out_path,
|
282
|
+
model_config=model_config,
|
283
|
+
)
|
284
|
+
model.run()
|
285
|
+
|
286
|
+
|
287
|
+
if __name__ == "__main__":
|
288
|
+
app()
|
dwind/scenarios.py
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
import pandas as pd
|
5
|
+
|
6
|
+
|
7
|
+
def config_nem(scenario, year):
|
8
|
+
# NEM_SCENARIO_CSV
|
9
|
+
nem_opt_scens = ["highrecost", "lowrecost", "re100"]
|
10
|
+
# nem_opt_scens = ['der_value_HighREcost', 'der_value_LowREcost', 're_100']
|
11
|
+
if scenario in nem_opt_scens:
|
12
|
+
nem_scenario_csv = "nem_optimistic_der_value_2035.csv"
|
13
|
+
elif scenario == "baseline" and year in (2022, 2025, 2035):
|
14
|
+
nem_scenario_csv = f"nem_baseline_{year}.csv"
|
15
|
+
else:
|
16
|
+
nem_scenario_csv = "nem_baseline_2035.csv"
|
17
|
+
|
18
|
+
return nem_scenario_csv
|
19
|
+
|
20
|
+
|
21
|
+
def config_cambium(scenario):
|
22
|
+
# CAMBIUM_SCENARIO
|
23
|
+
if scenario == "highrecost" or scenario == "re100":
|
24
|
+
cambium_scenario = "StdScen20_HighRECost"
|
25
|
+
elif scenario == "lowrecost":
|
26
|
+
cambium_scenario = "StdScen20_LowRECost"
|
27
|
+
else:
|
28
|
+
# cambium_scenario = "StdScen20_MidCase"
|
29
|
+
cambium_scenario = "Cambium23_MidCase"
|
30
|
+
|
31
|
+
return cambium_scenario
|
32
|
+
|
33
|
+
|
34
|
+
def config_costs(scenario, year):
|
35
|
+
# COST_INPUTS
|
36
|
+
f = Path(f"/projects/dwind/configs/costs/atb24/ATB24_costs_{scenario}_{year}.json").resolve()
|
37
|
+
with f.open("r") as f_in:
|
38
|
+
cost_inputs = json.load(f_in)
|
39
|
+
|
40
|
+
return cost_inputs
|
41
|
+
|
42
|
+
|
43
|
+
def config_performance(scenario, year):
|
44
|
+
# PERFORMANCE_INPUTS
|
45
|
+
if scenario == "baseline" and year == 2022:
|
46
|
+
performance_inputs = {
|
47
|
+
"solar": pd.DataFrame(
|
48
|
+
[
|
49
|
+
["res", 0.017709659, 0.005],
|
50
|
+
["com", 0.017709659, 0.005],
|
51
|
+
["ind", 0.017709659, 0.00],
|
52
|
+
],
|
53
|
+
columns=["sector_abbr", "pv_kw_per_sqft", "pv_degradation_factor"],
|
54
|
+
),
|
55
|
+
"wind": pd.DataFrame(
|
56
|
+
[
|
57
|
+
[2.5, 0.083787756, 0.85],
|
58
|
+
[5.0, 0.083787756, 0.85],
|
59
|
+
[10.0, 0.083787756, 0.85],
|
60
|
+
[20.0, 0.083787756, 0.85],
|
61
|
+
[50.0, 0.116657183, 0.85],
|
62
|
+
[100.0, 0.116657183, 0.85],
|
63
|
+
[250.0, 0.106708234, 0.85],
|
64
|
+
[500.0, 0.106708234, 0.85],
|
65
|
+
[750.0, 0.106708234, 0.85],
|
66
|
+
[1000.0, 0.106708234, 0.85],
|
67
|
+
[1500.0, 0.106708234, 0.85],
|
68
|
+
],
|
69
|
+
columns=["wind_turbine_kw_btm", "perf_improvement_factor", "wind_derate_factor"],
|
70
|
+
),
|
71
|
+
}
|
72
|
+
else:
|
73
|
+
performance_inputs = {
|
74
|
+
"solar": {
|
75
|
+
"pv_kw_per_sqft": {"res": 0.021677397, "com": 0.021677397, "ind": 0.021677397},
|
76
|
+
"pv_degradation_factor": {"res": 0.005, "com": 0.005, "ind": 0.005},
|
77
|
+
},
|
78
|
+
"wind": {
|
79
|
+
"perf_improvement_factor": {
|
80
|
+
2.5: 0.23136759,
|
81
|
+
5.0: 0.23136759,
|
82
|
+
10.0: 0.23136759,
|
83
|
+
20.0: 0.23136759,
|
84
|
+
50.0: 0.23713196,
|
85
|
+
100.0: 0.23713196,
|
86
|
+
250.0: 0.23617185,
|
87
|
+
500.0: 0.23617185,
|
88
|
+
750.0: 0.23617185,
|
89
|
+
1000.0: 0.23617185,
|
90
|
+
1500.0: 0.23617185,
|
91
|
+
},
|
92
|
+
"wind_derate_factor": {
|
93
|
+
2.5: 0.85,
|
94
|
+
5.0: 0.85,
|
95
|
+
10.0: 0.85,
|
96
|
+
20.0: 0.85,
|
97
|
+
50.0: 0.85,
|
98
|
+
100.0: 0.85,
|
99
|
+
250.0: 0.85,
|
100
|
+
500.0: 0.85,
|
101
|
+
750.0: 0.85,
|
102
|
+
1000.0: 0.85,
|
103
|
+
1500.0: 0.85,
|
104
|
+
},
|
105
|
+
},
|
106
|
+
}
|
107
|
+
|
108
|
+
return performance_inputs
|
109
|
+
|
110
|
+
|
111
|
+
def config_financial(scenario, year):
|
112
|
+
# FINANCIAL_INPUTS
|
113
|
+
scenarios = ("baseline", "metering", "billing")
|
114
|
+
if scenario in scenarios and year == 2025:
|
115
|
+
f = f"/projects/dwind/configs/costs/atb24/ATB24_financing_baseline_{year}.json"
|
116
|
+
i = Path("/projects/dwind/data/incentives/2025_incentives.json").resolve()
|
117
|
+
with i.open("r") as i_in:
|
118
|
+
incentives = json.load(i_in)
|
119
|
+
elif scenario in scenarios and year in (2035, 2040):
|
120
|
+
f = "/projects/dwind/configs/costs/atb24/ATB24_financing_baseline_2035.json"
|
121
|
+
else:
|
122
|
+
# use old assumptions
|
123
|
+
f = "/projects/dwind/configs/costs/atb20/ATB20_financing_baseline_2035.json"
|
124
|
+
f = Path(f).resolve()
|
125
|
+
|
126
|
+
with f.open("r") as f_in:
|
127
|
+
financials = json.load(f_in)
|
128
|
+
if year == 2025:
|
129
|
+
financials["BTM"]["itc_fraction_of_capex"] = incentives
|
130
|
+
financials["FOM"]["itc_fraction_of_capex"] = incentives
|
131
|
+
financials["FOM"]["ptc_fed_dlrs_per_kwh"]["solar"] = 0.0
|
132
|
+
financials["FOM"]["ptc_fed_dlrs_per_kwh"]["wind"] = 0.0
|
133
|
+
else:
|
134
|
+
financials["BTM"]["itc_fraction_of_capex"] = 0.3
|
135
|
+
financials["FOM"]["itc_fraction_of_capex"] = 0.3
|
136
|
+
financials["FOM"]["ptc_fed_dlrs_per_kwh"]["solar"] = 0.0
|
137
|
+
financials["FOM"]["ptc_fed_dlrs_per_kwh"]["wind"] = 0.0
|
138
|
+
|
139
|
+
return financials
|