dwind 0.3__py3-none-any.whl → 0.3.2__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/mp.py CHANGED
@@ -1,12 +1,19 @@
1
+ """Provides the :py:class:`MultiProcess` class for running a model on `NREL's Kestrel HPC system`_.
2
+
3
+ .. NREL's Kestrel HPC system: https://nrel.github.io/HPC/Documentation/Systems/Kestrel/
4
+ """
5
+
1
6
  from __future__ import annotations
2
7
 
3
8
  import time
4
9
  from pathlib import Path
5
10
 
6
11
  import pandas as pd
12
+ from rich.live import Live
7
13
  from rex.utilities.hpc import SLURM
8
14
 
9
- from dwind.helper import split_by_index
15
+ from dwind.utils import hpc
16
+ from dwind.utils.array import split_by_index
10
17
 
11
18
 
12
19
  class MultiProcess:
@@ -116,7 +123,7 @@ class MultiProcess:
116
123
 
117
124
  # Create the output directory if it doesn't already exist
118
125
  self.dir_out = Path.cwd() if dir_out is None else Path(self.dir_out).resolve()
119
- self.out_path = self.dir_out / f"chunk_files_{self.run_name}"
126
+ self.out_path = self.dir_out / "chunk_files"
120
127
  if not self.out_path.exists():
121
128
  self.out_path.mkdir()
122
129
 
@@ -127,78 +134,70 @@ class MultiProcess:
127
134
  log_dir.mkdir()
128
135
  self.stdout_path = log_dir
129
136
 
130
- def check_status(self, job_ids: list[int]):
137
+ def check_status(self, job_ids: list[int], start_time: float):
131
138
  """Prints the status of all :py:attr:`jobs` submitted.
132
139
 
133
140
  Parameters
134
141
  ----------
135
142
  job_ids : list[int]
136
143
  The list of HPC ``job_id``s to check on.
144
+ start_time : float
145
+ The results of initial ``time.perf_counter()``.
137
146
  """
138
- hpc = SLURM()
139
- print(f"{len(job_ids)} job(s) started")
140
-
141
- jobs_status = {j: hpc.check_status(job_id=j) for j in job_ids}
142
- n_remaining = len([s for s in jobs_status.values() if s in ("PD", "R")])
143
- print(f"{n_remaining} job(s) remaining: {jobs_status}")
144
- time.sleep(30)
145
-
146
- while n_remaining > 0:
147
- hpc = SLURM()
148
- for job, status in jobs_status.items():
149
- if status in ("GC", "None"):
150
- continue
151
- jobs_status.update({job: hpc.check_status(job_id=job)})
152
-
153
- n_remaining = len([s for s in jobs_status.values() if s in ("PD", "R")])
154
- print(f"{n_remaining} job(s) remaining: {jobs_status}")
155
- if n_remaining > 0:
156
- time.sleep(30)
157
-
158
- def aggregate_outputs(self):
159
- """Collect the chunked results files, combine them into a single output parquet file, and
160
- delete the chunked results files.
161
- """
162
- result_files = [f for f in self.out_path.iterdir() if f.suffix in (".pickle", ".pkl")]
163
-
164
- if len(result_files) > 0:
165
- result_agents = pd.concat([pd.read_pickle(f) for f in result_files])
166
- f_out = self.dir_out / f"run_{self.run_name}.pqt"
167
- result_agents.to_parquet(f_out)
168
-
169
- for f in result_files:
170
- f.unlink()
171
-
172
- def run_jobs(self, agent_df: pd.DataFrame) -> None:
147
+ slurm = SLURM()
148
+ job_status = {
149
+ j: {
150
+ "status": slurm.check_status(job_id=j),
151
+ "start": start_time,
152
+ "wait": time.perf_counter() - start_time,
153
+ "run": 0,
154
+ }
155
+ for j in job_ids
156
+ }
157
+ table, complete = hpc.generate_run_status_table(job_status)
158
+ with Live(table, refresh_per_second=1) as live:
159
+ while not complete:
160
+ time.sleep(5)
161
+ job_status |= hpc.update_status(job_status)
162
+ table, complete = hpc.generate_run_status_table(job_status)
163
+ live.update(table)
164
+
165
+ def run_jobs(self, agent_df: pd.DataFrame) -> dict[str, int]:
173
166
  """Run :py:attr:`n_jobs` number of jobs for the :py:attr:`agent_df`.
174
167
 
175
- Parameters
176
- ----------
177
- agent_df : pandas.DataFrame
178
- The agent DataFrame to be chunked and analyzed.
168
+ Args:
169
+ agent_df (pandas.DataFrame): The agent DataFrame to be chunked and analyzed.
170
+
171
+ Returns:
172
+ dict[str, int]: Dictionary mapping of each SLURM job id to the chunk run in that job.
179
173
  """
180
174
  agent_df = agent_df.reset_index(drop=True)
181
175
  # chunks = np.array_split(agent_df, self.n_nodes)
182
176
  starts, ends = split_by_index(agent_df, self.n_nodes)
183
- jobs = []
184
-
185
- base_cmd_str = f"module load conda; conda activate {self.env}; "
186
- base_cmd_str += "dwind run-chunk "
187
-
188
- base_args = f"--location {self.location} "
189
- base_args += f"--sector {self.sector} "
190
- base_args += f"--scenario {self.scenario} "
191
- base_args += f"--year {self.year} "
192
- base_args += f"--repository {self.repository} "
193
- base_args += f"--model-config {self.model_config} "
194
- base_args += f"--out-path {self.out_path}"
195
-
196
- for i, (start, end) in enumerate(zip(starts, ends, strict=True)):
197
- fn = self.out_path / f"agents_{i}.pqt"
177
+ job_chunk_map = {}
178
+
179
+ base_cmd_str = f"module load conda; conda activate {self.env};"
180
+ base_cmd_str += " dwind run chunk"
181
+
182
+ base_args = f" {self.location}"
183
+ base_args += f" {self.sector}"
184
+ base_args += f" {self.scenario}"
185
+ base_args += f" {self.year}"
186
+ base_args += f" {self.out_path}"
187
+ base_args += f" {self.repository}"
188
+ base_args += f" {self.model_config}"
189
+
190
+ if not (agent_path := self.out_path / "agent_chunks").is_dir():
191
+ agent_path.mkdir()
192
+
193
+ start_time = time.perf_counter()
194
+ # for i, (start, end) in enumerate(zip(starts, ends, strict=True)):
195
+ for i, (start, end) in enumerate(zip(starts, ends)): # noqa: B905
196
+ fn = self.out_path / "agent_chunks" / f"agents_{i}.pqt"
198
197
  agent_df.iloc[start:end].to_parquet(fn)
199
198
 
200
199
  job_name = f"{self.run_name}_{i}"
201
- cmd_str = f"{base_cmd_str} --chunk-ix {i} {base_args}"
200
+ cmd_str = f"{base_cmd_str} {i} {base_args}"
202
201
  print("cmd:", cmd_str)
203
202
 
204
203
  slurm_manager = SLURM()
@@ -213,7 +212,7 @@ class MultiProcess:
213
212
  )
214
213
 
215
214
  if job_id:
216
- jobs.append(job_id)
215
+ job_chunk_map[job_id] = i
217
216
  print(f"Kicked off job: {job_name}, with SLURM {job_id=} on Eagle.")
218
217
  else:
219
218
  print(
@@ -221,5 +220,6 @@ class MultiProcess:
221
220
  )
222
221
 
223
222
  # Check on the job statuses until they're complete, then aggregate the results
224
- self.check_status(jobs)
225
- self.aggregate_outputs()
223
+ jobs = [*job_chunk_map]
224
+ self.check_status(jobs, start_time)
225
+ return job_chunk_map
dwind/resource.py CHANGED
@@ -1,23 +1,75 @@
1
+ """Provides the :py:class:`ResourcePotential` class for gathering pre-calculated reV generation
2
+ data.
3
+ """
4
+
1
5
  import h5py as h5
2
6
  import pandas as pd
3
7
 
4
- from dwind import Configuration
8
+ from dwind.config import Sector, Technology, Configuration
5
9
 
6
10
 
7
11
  class ResourcePotential:
12
+ """Helper class designed to retrieve pre-calculated energy generation data from reV."""
13
+
8
14
  def __init__(
9
- self, parcels, model_config: Configuration, tech="wind", application="fom", year="2018"
15
+ self,
16
+ parcels: pd.DataFrame,
17
+ model_config: Configuration,
18
+ sector: Sector,
19
+ tech: str = "wind",
20
+ year: int = 2018,
10
21
  ):
22
+ """Initializes the :py:class:`ResourcePotential` instance.
23
+
24
+ Args:
25
+ parcels (pd.DataFrame): The agent DataFrame containing at least the following columns:
26
+ "gid", "rev_gid_{tech}", "solar_az_tilt" (solar only), "azimuth_{sector}"
27
+ (solar only), "tilt_{tech}" (solar only), "turbine_class" (wind only),
28
+ "wind_turbine_kw" (wind only), and "turbine_height_m" (wind only).
29
+ model_config (Configuration): The pre-loaded model configuration data object containing
30
+ the requisite SQL, file, and configuration data.
31
+ sector (dwind.config.Sector): A valid sector instance.
32
+ tech (str, optional): One of "solar" or "wind". Defaults to "wind".
33
+ year (int, optional): Resource year for the reV lookup. Defaults to 2018.
34
+
35
+ Raises:
36
+ ValueError: Raised if :py:attr:`parcels:` is missing any of the required columns.
37
+ """
11
38
  self.df = parcels
12
- self.tech = tech
13
- self.application = application
39
+ self.tech = Technology(tech)
40
+ self.sector = sector
14
41
  self.year = year
15
42
  self.config = model_config
16
43
 
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):
44
+ solar_cols = ("solar_az_tilt", f"azimuth_{self.sector.value}", f"tilt_{self.tech.value}")
45
+ # wind_cols = ("turbine_class", "wind_turbine_kw", "turbine_height_m")
46
+ wind_cols = ("wind_turbine_kw", "turbine_height_m")
47
+
48
+ if self.tech is Technology.WIND:
49
+ cols = wind_cols
50
+ elif self.tech is Technology.SOLAR:
51
+ cols = solar_cols
52
+
53
+ missing = set(cols).difference(self.df.columns.tolist())
54
+ if missing:
55
+ raise ValueError(f"`parcels` is missing the following columns: {', '.join(missing)}")
56
+
57
+ def create_rev_gid_to_summary_lkup(
58
+ self, configs: list[str], *, save_csv: bool = True
59
+ ) -> pd.DataFrame:
60
+ """Creates the reV summary tables based on the "gid" mappings in :py:attr:`parcels`.
61
+
62
+ Args:
63
+ configs (list[str]): The list of technology-specific configurations where the generation
64
+ data should be retrieved.
65
+ save_csv (bool, optional): If True, save the resulting lookup calculated from reV to the
66
+ reV folder definied in ``Configuration.rev.generation.{tech}_DIR``. Defaults to
67
+ True.
68
+
69
+ Returns:
70
+ pd.DataFrame: reV generation lookup table for the technology-specific configurations in
71
+ :py:attr:`configs`.
72
+ """
21
73
  config_dfs = []
22
74
  for c in configs:
23
75
  file_str = self.config.rev.DIR / f"rev_{c}_generation_{self.year}.h5"
@@ -30,10 +82,10 @@ class ResourcePotential:
30
82
 
31
83
  config_df = pd.concat([rev_index, gids, annual_energy, cf_mean], axis=1)
32
84
  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",
85
+ f"rev_index_{self.tech.value}",
86
+ f"rev_gid_{self.tech.value}",
87
+ f"{self.tech.value}_naep",
88
+ f"{self.tech.value}_cf",
37
89
  ]
38
90
 
39
91
  config_df["config"] = c
@@ -43,84 +95,107 @@ class ResourcePotential:
43
95
 
44
96
  if save_csv:
45
97
  save_name = (
46
- self.config.rev.generation[f"{self.tech}_DIR"]
47
- / f"lkup_rev_gid_to_summary_{self.tech}_{self.year}.csv"
98
+ self.config.rev.generation[f"{self.tech.value}_DIR"]
99
+ / f"lkup_rev_gid_to_summary_{self.tech.value}_{self.year}.csv"
48
100
  )
49
101
  summary_df.to_csv(save_name, index=False)
50
102
 
51
103
  return summary_df
52
104
 
53
105
  def find_rev_summary_table(self):
54
- if self.tech == "solar":
106
+ """Creates the generation summary data for each of the :py:attr:`tech`-specific
107
+ configurations specified in :py:attr:`config.rev.settings.{tech}`, then maps it to the
108
+ agent data (:py:attr:`parcels`), overwriting any previously computed data.
109
+ """
110
+ if self.tech is Technology.SOLAR:
55
111
  configs = self.config.rev.settings.solar
56
112
  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(
113
+ col_list = ["gid", f"rev_gid_{self.tech.value}", config_col]
114
+ self.df[config_col] = self.df[f"azimuth_{self.sector.value}"].map(
59
115
  self.config.rev.settings.azimuth_direction_to_degree
60
116
  )
61
117
  self.df[config_col] = (
62
- self.df[config_col].astype(str) + "_" + self.df[f"tilt_{self.tech}"].astype(str)
118
+ self.df[config_col].astype(str)
119
+ + "_"
120
+ + self.df[f"tilt_{self.tech.value}"].astype(str)
63
121
  )
64
- elif self.tech == "wind":
65
- configs = self.rev.settings.wind
122
+ elif self.tech is Technology.WIND:
123
+ configs = self.config.rev.settings.wind
66
124
  config_col = "turbine_class"
67
125
  col_list = [
68
126
  "gid",
69
- f"rev_gid_{self.tech}",
127
+ f"rev_gid_{self.tech.value}",
70
128
  config_col,
71
129
  "turbine_height_m",
72
130
  "wind_turbine_kw",
73
131
  ]
74
132
  self.df[config_col] = self.df["wind_turbine_kw"].map(self.config.rev.turbine_class_dict)
75
133
 
76
- out_cols = [*col_list, f"rev_index_{self.tech}", f"{self.tech}_naep", f"{self.tech}_cf"]
134
+ out_cols = [
135
+ *col_list,
136
+ f"rev_index_{self.tech.value}",
137
+ f"{self.tech.value}_naep",
138
+ f"{self.tech.value}_cf",
139
+ ]
140
+
141
+ drop_cols = [
142
+ f"rev_gid_{self.tech.value}",
143
+ f"{self.tech.value}_naep",
144
+ f"{self.tech.value}_cf",
145
+ ]
146
+ self.df = self.df.drop(columns=[c for c in drop_cols if c in self.df])
77
147
 
78
148
  f_gen = (
79
- self.config.rev.generation[f"{self.tech}_DIR"]
80
- / f"lkup_rev_gid_to_summary_{self.tech}_{self.year}.csv"
149
+ self.config.rev.generation[f"{self.tech.value}_DIR"]
150
+ / f"lkup_rev_gid_to_summary_{self.tech.value}_{self.year}.csv"
81
151
  )
82
152
 
83
153
  if f_gen.exists():
84
- generation_summary = pd.read_csv(f_gen)
154
+ generation_summary = pd.read_csv(f_gen, dtype_backend="pyarrow")
85
155
  else:
86
156
  generation_summary = self.create_rev_gid_to_summary_lkup(configs)
87
157
 
88
158
  generation_summary = (
89
159
  generation_summary.reset_index(drop=True)
90
- .drop_duplicates(subset=[f"rev_index_{self.tech}", "config"])
160
+ .drop_duplicates(subset=[f"rev_index_{self.tech.value}", "config"])
91
161
  .rename(columns={"config": config_col})
92
162
  )
93
163
  agents = self.df.merge(
94
- generation_summary, how="left", on=[f"rev_index_{self.tech}", config_col]
164
+ generation_summary, how="left", on=[f"rev_index_{self.tech.value}", config_col]
95
165
  )
96
166
  return agents[out_cols]
97
167
 
98
168
  def prepare_agents_for_gen(self):
99
- # create lookup column based on each tech
100
- if self.tech == "wind":
169
+ """Create lookup column based on each technology."""
170
+ if self.tech is Technology.WIND:
101
171
  # drop wind turbine size duplicates
102
172
  # SINCE WE ASSUME ANY TURBINE IN A GIVEN CLASS HAS THE SAME POWER CURVE
103
173
  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":
174
+ # if running FOM sector, only consider a single (largest) turbine size
175
+ if self.sector is Sector.FOM:
106
176
  self.df = self.df.loc[self.df["wind_size_kw"] == self.df["wind_size_kw_fom"]]
107
177
 
108
178
  self.df["turbine_class"] = self.df["wind_turbine_kw"].map(
109
179
  self.config.rev.turbine_class_dict
110
180
  )
111
181
 
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(
182
+ if self.tech is Technology.SOLAR:
183
+ # NOTE: tilt and azimuth are sector-specific
184
+ self.df["solar_az_tilt"] = self.df[f"azimuth_{self.sector.value}"].map(
115
185
  self.config.rev.settings.azimuth_direction_to_degree
116
186
  )
117
187
  self.df["solar_az_tilt"] = self.df["solar_az_tilt"].astype(str)
118
188
  self.df["solar_az_tilt"] = (
119
- self.df["solar_az_tilt"] + "_" + self.df[f"tilt_{self.application}"].astype(str)
189
+ self.df["solar_az_tilt"] + "_" + self.df[f"tilt_{self.sector.value}"].astype(str)
120
190
  )
121
191
 
122
- def merge_gen_to_agents(self, tech_agents):
123
- if self.tech == "wind":
192
+ def merge_gen_to_agents(self, tech_agents: pd.DataFrame):
193
+ """Merges :py:attr:`tech_agents` to the parcel data :py:attr:`df`.
194
+
195
+ Args:
196
+ tech_agents (pd.DataFrame): The technology-specific energy generation data.
197
+ """
198
+ if self.tech is Technology.WIND:
124
199
  cols = ["turbine_height_m", "wind_turbine_kw", "turbine_class"]
125
200
  else:
126
201
  # NOTE: need to drop duplicates in solar agents
@@ -130,16 +205,23 @@ class ResourcePotential:
130
205
  )
131
206
  cols = ["solar_az_tilt"]
132
207
 
133
- cols.extend(["gid", f"rev_index_{self.tech}"])
208
+ cols.extend(["gid", f"rev_index_{self.tech.value}"])
134
209
 
135
210
  self.df = self.df.merge(tech_agents, how="left", on=cols)
136
211
 
137
212
  def match_rev_summary_to_agents(self):
213
+ """Runs the energy generation gathering and merging steps, and retursns back the updated
214
+ :py:attr:`df` agent/parcel data.
215
+
216
+ Returns:
217
+ pd.DataFrame: Updated agent/parcel data with rec/alculated "wind_aep" or "solar_aep"
218
+ information for each agent.
219
+ """
138
220
  self.prepare_agents_for_gen()
139
221
  tech_agents = self.find_rev_summary_table()
140
222
  self.merge_gen_to_agents(tech_agents)
141
223
 
142
- if self.tech == "wind":
224
+ if self.tech is Technology.WIND:
143
225
  # fill nan generation values
144
226
  self.df = self.df.loc[
145
227
  ~((self.df["wind_naep"].isnull()) & (self.df["turbine_class"] != "none"))
@@ -150,7 +232,7 @@ class ResourcePotential:
150
232
  # calculate annual energy production (aep)
151
233
  self.df["wind_aep"] = self.df["wind_naep"] * self.df["wind_turbine_kw"]
152
234
  # self.df = self.df.drop(columns="turbine_class")
153
- else:
235
+ elif self.tech is Technology.SOLAR:
154
236
  # fill nan generation values
155
237
  self.df = self.df.loc[~(self.df["solar_naep"].isnull())]
156
238
  # size groundmount system to equal wind aep
dwind/run.py CHANGED
@@ -3,19 +3,26 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import sys
6
- import tomllib
7
6
  from enum import Enum
8
- from typing import Annotated
7
+ from typing import Optional, Annotated
9
8
  from pathlib import Path
10
9
 
11
10
  import typer
12
11
  import pandas as pd
12
+ from rich.style import Style
13
+ from rich.pretty import pprint
14
+ from rich.console import Console
13
15
 
14
16
 
15
- # from memory_profiler import profile
16
-
17
+ # fmt: off
18
+ if sys.version_info >= (3, 11): # noqa
19
+ import tomllib
20
+ else:
21
+ import tomli as tomllib
22
+ # fmt: on
17
23
 
18
24
  app = typer.Typer()
25
+ console = Console()
19
26
 
20
27
  DWIND = Path("/projects/dwind/agents")
21
28
 
@@ -71,20 +78,25 @@ def year_callback(ctx: typer.Context, param: typer.CallbackParam, value: int):
71
78
 
72
79
 
73
80
  def load_agents(
74
- file_name: str | Path | None = None,
81
+ file_name: Path | None = None,
75
82
  location: str | None = None,
76
83
  sector: str | None = None,
84
+ model_config: str | Path | None = None,
85
+ *,
86
+ prepare: bool = False,
77
87
  ) -> pd.DataFrame:
78
88
  """Load the agent file based on a filename or the location and sector to a Pandas DataFrame,
79
89
  and return the data frame.
80
90
 
81
91
  Args:
82
- file_name (str | Path | None, optional): Name of the agent file, if not auto-generating from
92
+ file_name (Path | None, optional): Name of the agent file, if not auto-generating from
83
93
  the :py:attr:`location` and :py:attr:`sector` inputs. Defaults to None.
84
94
  location (str | None, optional): The name of the location or grouping, such as
85
95
  "colorado_larimer" or "priority1". Defaults to None.
86
96
  sector (str | None, optional): The name of the section. Must be one of "btm" or "fom".
87
97
  Defaults to None.
98
+ prepare (bool, optional): True if loading pre-chunked and prepared agent data, which should
99
+ bypass the standard column checking for additional joins, by default False.
88
100
 
89
101
  Returns:
90
102
  pd.DataFrame: The agent DataFrame.
@@ -97,10 +109,28 @@ def load_agents(
97
109
  f_agents = (
98
110
  file_name if file_name is not None else DWIND / f"{location}/agents_dwind_{sector}.parquet"
99
111
  )
112
+ if not isinstance(f_agents, Path):
113
+ f_agents = Path(f_agents).resolve()
114
+
115
+ alternative_suffix = (".pqt", ".parquet", ".pkl", ".pickle", ".csv")
116
+ base_style = Style.parse("cyan")
100
117
  if not f_agents.exists():
101
- f_agents = f_agents.with_suffix(".pickle")
102
- agents = Agents(agent_file=f_agents).agents
103
- return agents
118
+ for suffix in alternative_suffix:
119
+ if (new_fn := f_agents.with_suffix(suffix)).exists():
120
+ if new_fn != f_agents:
121
+ msg = (
122
+ f"Using alternative agent file: {new_fn}\n\t"
123
+ f"Requested agent file: {f_agents}"
124
+ )
125
+ console.print(msg, style=base_style)
126
+ f_agents = new_fn
127
+ break
128
+
129
+ if prepare:
130
+ return Agents.load_and_prepare_agents(
131
+ agent_file=f_agents, sector=sector, model_config=model_config
132
+ )
133
+ return Agents.load_agents(agent_file=f_agents)
104
134
 
105
135
 
106
136
  @app.command()
@@ -155,7 +185,9 @@ def run_hpc(
155
185
  dir_out: Annotated[
156
186
  str, typer.Option(help="Path to where the chunked outputs should be saved.")
157
187
  ],
158
- stdout_path: Annotated[str | None, typer.Option(help="The path to write stdout logs.")] = None,
188
+ stdout_path: Annotated[
189
+ Optional[str], typer.Option(help="The path to write stdout logs.") # noqa
190
+ ] = None,
159
191
  ):
160
192
  """Run dwind via the HPC multiprocessing interface."""
161
193
  sys.path.append(repository)
@@ -180,7 +212,9 @@ def run_hpc(
180
212
  stdout_path=stdout_path,
181
213
  )
182
214
 
183
- agent_df = load_agents(location=location, sector=sector)
215
+ agent_df = load_agents(
216
+ location=location, sector=sector, model_config=model_config, prepare=True
217
+ )
184
218
  mp.run_jobs(agent_df)
185
219
 
186
220
 
@@ -194,15 +228,15 @@ def run_hpc_from_config(
194
228
  config_path = Path(config_path).resolve()
195
229
  with config_path.open("rb") as f:
196
230
  config = tomllib.load(f)
197
- print(config)
231
+ print("Running the following configuration:")
232
+ pprint(config)
198
233
 
199
234
  run_hpc(**config)
200
235
 
201
236
 
202
237
  @app.command()
203
238
  def run_chunk(
204
- # start: Annotated[int, typer.Option(help="chunk start index")],
205
- # end: Annotated[int, typer.Option(help="chunk end index")],
239
+ chunk_ix: Annotated[int, typer.Option(help="Chunk number/index. Used for logging.")],
206
240
  location: Annotated[
207
241
  str, typer.Option(help="The state, state_county, or priority region to run.")
208
242
  ],
@@ -213,7 +247,6 @@ def run_chunk(
213
247
  year: Annotated[
214
248
  int, typer.Option(callback=year_callback, help="The year basis of the scenario.")
215
249
  ],
216
- chunk_ix: Annotated[int, typer.Option(help="Chunk number/index. Used for logging.")],
217
250
  out_path: Annotated[str, typer.Option(help="save path")],
218
251
  repository: Annotated[
219
252
  str, typer.Option(help="Path to the dwind repository to use when running the model.")
@@ -230,7 +263,7 @@ def run_chunk(
230
263
  from dwind.model import Model
231
264
 
232
265
  agent_file = Path(out_path).resolve() / f"agents_{chunk_ix}.pqt"
233
- agents = load_agents(file_name=agent_file)
266
+ agents = load_agents(file_name=agent_file, prepare=False)
234
267
  agent_file.unlink()
235
268
 
236
269
  model = Model(
@@ -271,7 +304,7 @@ def run(
271
304
  sys.path.append(repository)
272
305
  from dwind.model import Model
273
306
 
274
- agents = load_agents(location=location, sector=sector)
307
+ agents = load_agents(location=location, sector=sector, model_config=model_config, prepare=True)
275
308
  model = Model(
276
309
  agents=agents,
277
310
  location=location,