Skip to content

Commit 04c05e4

Browse files
committed
feat(design): add runtime priority control
1 parent 04e4851 commit 04c05e4

File tree

4 files changed

+86
-16
lines changed

4 files changed

+86
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333
- Added a Gaussian inverse design filter option with autograd gradients and complete padding mode coverage.
3434
- Added support for argument passing to DRC file when running checks with `DRCRunner.run(..., drc_args={key: value})` in klayout plugin.
3535
- Added support for `nonlinear_spec` in `CustomMedium` and `CustomDispersiveMedium`.
36+
- `tidy3d.plugins.design.DesignSpace.run(..., fn_post=...)` now accepts a `priority` keyword to propagate vGPU queue priority to all automatically batched simulations.
3637

3738
### Breaking Changes
3839
- Edge singularity correction at PEC and lossy metal edges defaults to `True`.

tests/test_plugins/test_design.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,22 @@ def test_sweep(sweep_method, monkeypatch):
463463
assert float_label in sweep_results_df, "didn't assign column header properly for float"
464464

465465

466+
def test_priority_forwarded_to_batch(monkeypatch):
467+
captured_priority = {}
468+
469+
def run_with_priority(batch, path_dir: Optional[str] = None, priority: Optional[int] = None):
470+
captured_priority["value"] = priority
471+
return emulated_batch_run(batch, path_dir=path_dir)
472+
473+
monkeypatch.setattr(web.Batch, "run", run_with_priority)
474+
475+
design_space = init_design_space(sweep_method=tdd.MethodGrid())
476+
477+
design_space.run(scs_pre, scs_post, verbose=False, priority=7)
478+
479+
assert captured_priority["value"] == 7
480+
481+
466482
def emulated_estimate_cost_return(self, verbose=True):
467483
return 0.5
468484

tidy3d/plugins/design/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ With the design parameters and our method defined, we can combine everything int
9595
design_space = tdd.DesignSpace(parameters=[param_n, param_r], method=method)
9696
```
9797

98+
Execution-time options such as vGPU queue priority are supplied when calling `run`. For example,
99+
`design_space.run(fn_pre, fn_post, priority=7)` forwards that priority to every automatically batched
100+
simulation (only available when both `fn` and `fn_post` are provided).
101+
98102
## Results
99103

100104
The `DesignSpace.run()` function returns a `Result` object, which is basically a dataset containing the function inputs, outputs, source code, and any task ID information corresponding to each data point.

tidy3d/plugins/design/design.py

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,13 @@ def get_fn_source(function: Callable) -> str:
144144
except (TypeError, OSError):
145145
return None
146146

147-
def run(self, fn: Callable, fn_post: Optional[Callable] = None, verbose: bool = True) -> Result:
147+
def run(
148+
self,
149+
fn: Callable,
150+
fn_post: Optional[Callable] = None,
151+
verbose: bool = True,
152+
priority: Optional[int] = None,
153+
) -> Result:
148154
"""Explore a parameter space with a supplied method using the user supplied function.
149155
Supplied functions are used to evaluate the design space and are called within the method.
150156
For optimization methods these functions act as the fitness function. A single function can be
@@ -209,26 +215,43 @@ def run(self, fn: Callable, fn_post: Optional[Callable] = None, verbose: bool =
209215
verbose : bool = True
210216
Toggle the output of statements stored in the logging console.
211217
218+
priority : int = None
219+
Optional vGPU queue priority (1 = lowest, 10 = highest) applied to simulations run via
220+
``fn`` / ``fn_post``. Ignored when ``fn_post`` is not provided because no automatic
221+
batching occurs in that mode.
222+
212223
Returns
213224
-------
214225
:class:`Result`
215226
Object containing the results of the design space exploration.
216227
Can be converted to ``pandas.DataFrame`` with ``.to_dataframe()``.
217228
"""
218229

230+
if priority is not None and (priority < 1 or priority > 10):
231+
raise ValueError("'priority' must be between 1 and 10 (inclusive).")
232+
219233
# Get the console
220234
# Method.run checks for console is None instead of being passed console and verbose
221235
console = get_logging_console() if verbose else None
222236

223237
# Run based on how many functions the user provides
224238
if fn_post is None:
239+
if priority is not None:
240+
log.warning(
241+
"'priority' has no effect unless both fn and fn_post are provided;"
242+
" please split your function to allow automatic batching.",
243+
log_once=True,
244+
)
225245
fn_args, fn_values, aux_values, opt_output = self.run_single(fn, console)
226246
sim_names = None
227247
sim_paths = None
228248

229249
else:
230250
fn_args, fn_values, aux_values, opt_output, sim_names, sim_paths = self.run_pre_post(
231-
fn_pre=fn, fn_post=fn_post, console=console
251+
fn_pre=fn,
252+
fn_post=fn_post,
253+
console=console,
254+
priority=priority,
232255
)
233256

234257
if len(sim_names) == 0:
@@ -252,12 +275,20 @@ def run_single(self, fn: Callable, console: Console) -> tuple(list[dict], list,
252275
evaluate_fn = self._get_evaluate_fn_single(fn=fn)
253276
return self.method._run(run_fn=evaluate_fn, parameters=self.parameters, console=console)
254277

255-
def run_pre_post(self, fn_pre: Callable, fn_post: Callable, console: Console) -> tuple(
256-
list[dict], list[dict], list[Any]
257-
):
278+
def run_pre_post(
279+
self,
280+
fn_pre: Callable,
281+
fn_post: Callable,
282+
console: Console,
283+
priority: Optional[int] = None,
284+
) -> tuple(list[dict], list[dict], list[Any]):
258285
"""Run a function with Tidy3D implicitly called in between."""
259286
handler = self._get_evaluate_fn_pre_post(
260-
fn_pre=fn_pre, fn_post=fn_post, fn_mid=self._fn_mid, console=console
287+
fn_pre=fn_pre,
288+
fn_post=fn_post,
289+
fn_mid=self._fn_mid,
290+
console=console,
291+
priority=priority,
261292
)
262293
fn_args, fn_values, aux_values, opt_output = self.method._run(
263294
run_fn=handler.fn_combined, parameters=self.parameters, console=console
@@ -276,16 +307,22 @@ def evaluate(args_list: list) -> list[Any]:
276307
return evaluate
277308

278309
def _get_evaluate_fn_pre_post(
279-
self, fn_pre: Callable, fn_post: Callable, fn_mid: Callable, console: Console
310+
self,
311+
fn_pre: Callable,
312+
fn_post: Callable,
313+
fn_mid: Callable,
314+
console: Console,
315+
priority: Optional[int],
280316
):
281317
"""Get function that tries to use batch processing on a set of arguments."""
282318

283319
class Pre_Post_Handler:
284-
def __init__(self, console) -> None:
320+
def __init__(self, console, priority) -> None:
285321
self.sim_counter = 0
286322
self.sim_names = []
287323
self.sim_paths = []
288324
self.console = console
325+
self.priority = priority
289326

290327
def fn_combined(self, args_list: list[dict[str, Any]]) -> list[Any]:
291328
"""Compute fn_pre and fn_post functions and capture other outputs."""
@@ -300,26 +337,37 @@ def fn_combined(self, args_list: list[dict[str, Any]]) -> list[Any]:
300337
)
301338

302339
data, task_names, task_paths, sim_counter = fn_mid(
303-
sim_dict, self.sim_counter, self.console
340+
sim_dict,
341+
self.sim_counter,
342+
self.console,
343+
self.priority,
304344
)
305345
self.sim_names.extend(task_names)
306346
self.sim_paths.extend(task_paths)
307347
self.sim_counter = sim_counter
308348
post_out = [fn_post(val) for val in data.values()]
309349
return post_out
310350

311-
handler = Pre_Post_Handler(console)
351+
handler = Pre_Post_Handler(console, priority)
312352

313353
return handler
314354

315355
@staticmethod
316-
def _run_batch(batch: Batch, path_dir: str) -> BatchData:
356+
def _run_batch(
357+
batch: Batch,
358+
path_dir: str,
359+
priority: Optional[int] = None,
360+
) -> BatchData:
317361
"""Run a batch and return the BatchData."""
318-
batch_out = batch.run(path_dir=path_dir)
362+
batch_out = batch.run(path_dir=path_dir, priority=priority)
319363
return batch_out
320364

321365
def _fn_mid(
322-
self, pre_out: dict[int, Any], sim_counter: int, console: Console
366+
self,
367+
pre_out: dict[int, Any],
368+
sim_counter: int,
369+
console: Console,
370+
priority: Optional[int],
323371
) -> Union[dict[int, Any], BatchData]:
324372
"""A function of the output of ``fn_pre`` that gives the input to ``fn_post``."""
325373

@@ -399,11 +447,11 @@ def _find_and_map(
399447
simulation_type="tidy3d_design",
400448
verbose=False, # Using a custom output instead of Batch.monitor updates
401449
)
402-
sims_out = self._run_batch(batch, path_dir=self.path_dir)
450+
sims_out = self._run_batch(batch, path_dir=self.path_dir, priority=priority)
403451

404452
batch_results = {}
405453
for batch_key, batch in batches.items():
406-
batch_out = self._run_batch(batch, path_dir=self.path_dir)
454+
batch_out = self._run_batch(batch, path_dir=self.path_dir, priority=priority)
407455
batch_results[batch_key] = batch_out
408456

409457
def _return_to_dict(return_dict: dict, key: str, return_obj: Any) -> None:
@@ -468,6 +516,7 @@ def run_batch(
468516
Union[SimulationData, list[SimulationData], dict[str, SimulationData]], Any
469517
],
470518
path_dir: str = ".",
519+
priority: Optional[int] = None,
471520
**batch_kwargs: Any,
472521
) -> Result:
473522
"""
@@ -485,7 +534,7 @@ def run_batch(
485534
)
486535

487536
new_self = self.updated_copy(path_dir=path_dir)
488-
result = new_self.run(fn=fn_pre, fn_post=fn_post)
537+
result = new_self.run(fn=fn_pre, fn_post=fn_post, priority=priority)
489538

490539
return result
491540

0 commit comments

Comments
 (0)