Skip to content

Commit 2ce5542

Browse files
committed
test: isolate numerical autograd artifacts
1 parent 1fac5c3 commit 2ce5542

8 files changed

+170
-114
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import re
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
ARTIFACT_ENV_VAR = "TIDY3D_NUMERICAL_ARTIFACT_DIR"
10+
DEFAULT_RELATIVE_DIR = Path("tests/tmp/autograd_numerical")
11+
12+
13+
def _sanitize_segment(value: str) -> str:
14+
sanitized = re.sub(r"[^\w.-]+", "_", value)
15+
sanitized = sanitized.strip("_")
16+
return sanitized or "case"
17+
18+
19+
def _resolve_artifact_root() -> Path:
20+
env_value = os.environ.get(ARTIFACT_ENV_VAR)
21+
if env_value:
22+
root = Path(env_value).expanduser()
23+
else:
24+
repo_root = Path(__file__).resolve().parents[4]
25+
root = repo_root / DEFAULT_RELATIVE_DIR
26+
root.mkdir(parents=True, exist_ok=True)
27+
return root
28+
29+
30+
@pytest.fixture(scope="session")
31+
def numerical_artifact_root() -> Path:
32+
return _resolve_artifact_root()
33+
34+
35+
@pytest.fixture
36+
def numerical_case_dir(request, numerical_artifact_root: Path) -> Path:
37+
safe_nodeid = _sanitize_segment(request.node.nodeid.replace(os.sep, "_"))
38+
case_dir = numerical_artifact_root / safe_nodeid
39+
case_dir.mkdir(parents=True, exist_ok=True)
40+
return case_dir

tests/test_components/autograd/numerical/test_autograd_box_polyslab_numerical.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import sys
6+
from pathlib import Path
67

78
import autograd.numpy as anp
89
import numpy as np
@@ -40,6 +41,12 @@
4041
sys.stdout = sys.stderr
4142

4243

44+
def case_identifier(is_3d: bool, infinite_dim_2d: int | None, shift_box_center: bool) -> str:
45+
geometry_tag = "3d" if is_3d else f"2d_infinite_dim_{infinite_dim_2d}"
46+
shift_tag = "shifted" if shift_box_center else "centered"
47+
return f"box_polyslab_{geometry_tag}_{shift_tag}"
48+
49+
4350
def dimension_permutation(infinite_dim: int) -> tuple[int, int]:
4451
offset_dim = (1 + infinite_dim) % 3
4552
final_dim = (1 + offset_dim) % 3
@@ -191,11 +198,13 @@ def run_parameter_simulations(
191198
tag: str,
192199
base_sim: td.Simulation,
193200
fom,
194-
tmp_path,
201+
artifact_dir: Path,
195202
*,
196203
local_gradient: bool,
197204
):
198205
simulation_dict = {}
206+
output_dir = artifact_dir
207+
output_dir.mkdir(parents=True, exist_ok=True)
199208

200209
for idx, param_values in enumerate(parameter_sets):
201210
geometry = make_geometry(param_values, box_center)
@@ -209,16 +218,19 @@ def run_parameter_simulations(
209218

210219
if len(simulation_dict) == 1:
211220
key, sim = next(iter(simulation_dict.items()))
221+
result_path = output_dir / f"{key}.hdf5"
212222
sim_data = web.run(
213223
sim,
214224
task_name=key,
225+
path=str(result_path),
215226
local_gradient=local_gradient,
216227
verbose=VERBOSE,
217228
)
218229
return fom(sim_data)
219230

220231
sim_data_map = web.run_async(
221232
simulation_dict,
233+
path_dir=str(output_dir),
222234
local_gradient=local_gradient,
223235
verbose=VERBOSE,
224236
)
@@ -232,10 +244,13 @@ def make_objective(
232244
tag: str,
233245
base_sim: td.Simulation,
234246
fom,
235-
tmp_path,
247+
case_dir: Path,
236248
*,
237249
local_gradient: bool,
238250
):
251+
artifact_dir = case_dir / tag
252+
artifact_dir.mkdir(parents=True, exist_ok=True)
253+
239254
def objective(parameters):
240255
results = run_parameter_simulations(
241256
parameters,
@@ -244,7 +259,7 @@ def objective(parameters):
244259
tag,
245260
base_sim,
246261
fom,
247-
tmp_path,
262+
artifact_dir,
248263
local_gradient=local_gradient,
249264
)
250265

@@ -304,7 +319,9 @@ def squeeze_dimension(array: np.ndarray, is_3d: bool, infinite_dim: int | None)
304319
],
305320
)
306321
@pytest.mark.parametrize("shift_box_center", (True, False))
307-
def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_center, tmp_path):
322+
def test_box_and_polyslab_gradients_match(
323+
is_3d, infinite_dim_2d, shift_box_center, numerical_case_dir
324+
):
308325
"""Test that the box and polyslab gradients match for rectangular slab geometries. Allow
309326
comparison as well to finite difference values."""
310327

@@ -330,13 +347,17 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent
330347
box_center[infinite_dim_2d] = 0.5 * INFINITE_DIM_SIZE_UM
331348
box_center[final_dim_2d] = 0.5 * PERIODS_UM[0]
332349

350+
case_id = case_identifier(is_3d, None if is_3d else infinite_dim_2d, shift_box_center)
351+
case_dir = numerical_case_dir / case_id
352+
case_dir.mkdir(parents=True, exist_ok=True)
353+
333354
box_objective = make_objective(
334355
make_box_geometry,
335356
box_center,
336357
"box",
337358
base_sim,
338359
fom,
339-
tmp_path,
360+
case_dir,
340361
local_gradient=LOCAL_GRADIENT,
341362
)
342363
polyslab_objective = make_objective(
@@ -345,7 +366,7 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent
345366
"polyslab",
346367
base_sim,
347368
fom,
348-
tmp_path,
369+
case_dir,
349370
local_gradient=LOCAL_GRADIENT,
350371
)
351372

@@ -355,7 +376,7 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent
355376
"box_fd",
356377
base_sim,
357378
fom,
358-
tmp_path,
379+
case_dir,
359380
local_gradient=False,
360381
)
361382
polyslab_objective_fd = make_objective(
@@ -364,7 +385,7 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent
364385
"polyslab_fd",
365386
base_sim,
366387
fom,
367-
tmp_path,
388+
case_dir,
368389
local_gradient=False,
369390
)
370391

@@ -396,10 +417,10 @@ def test_box_and_polyslab_gradients_match(is_3d, infinite_dim_2d, shift_box_cent
396417
}
397418

398419
if SAVE_OUTPUT_DATA:
399-
np.savez(
400-
f"test_diff_init_{'3' if is_3d else '2'}d_infinite_dim_{infinite_dim_2d}.npz",
401-
**test_data,
420+
npz_path = case_dir / (
421+
f"test_diff_init_{'3' if is_3d else '2'}d_infinite_dim_{infinite_dim_2d}.npz"
402422
)
423+
np.savez(npz_path, **test_data)
403424

404425
def angled_overlap_deg(v1, v2):
405426
norm_v1 = np.linalg.norm(v1)

tests/test_components/autograd/numerical/test_autograd_conductivity_numerical.py

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
SAVE_ADJ_LOC = 1
3838
LOCAL_GRADIENT = True
3939
VERBOSE = False
40-
NUMERICAL_RESULTS_DATA_DIR = "./numerical_conductivity_test/"
40+
NUMERICAL_RESULTS_SUBDIR = "numerical_conductivity_test"
4141
SHOW_PRINT_STATEMENTS = False
4242

4343
RMS_THRESHOLD = 0.6
@@ -325,17 +325,9 @@ def flux(sim_data):
325325

326326

327327
@pytest.mark.numerical
328-
@pytest.mark.parametrize(
329-
"conductivity_data_test_parameters, dir_name",
330-
zip(
331-
conductivity_data_test_parameters,
332-
([NUMERICAL_RESULTS_DATA_DIR] if SAVE_FD_ADJ_DATA else [None])
333-
* len(conductivity_data_test_parameters),
334-
),
335-
indirect=["dir_name"],
336-
)
328+
@pytest.mark.parametrize("conductivity_data_test_parameters", conductivity_data_test_parameters)
337329
def test_finite_difference_conductivity_data(
338-
conductivity_data_test_parameters, rng, tmp_path, create_directory
330+
conductivity_data_test_parameters, rng, numerical_case_dir
339331
):
340332
"""Test autograd conductivity gradients by comparing to numerical finite difference.
341333
@@ -356,17 +348,14 @@ def test_finite_difference_conductivity_data(
356348
Test parameters including wavelengths, monitor configuration, etc.
357349
rng : numpy.random.Generator
358350
Random number generator for creating perturbation patterns
359-
tmp_path : pathlib.Path
360-
Temporary directory for simulation files
361-
create_directory : fixture
362-
Pytest fixture for creating directories
351+
numerical_case_dir : pathlib.Path
352+
Case-specific artifact directory for simulation and result files
363353
"""
364354

365355
# Create directory for plots if plotting is enabled
356+
results_dir = numerical_case_dir / NUMERICAL_RESULTS_SUBDIR
366357
if PLOT_FD_ADJ_COMPARISON or SAVE_FD_ADJ_DATA:
367-
import os
368-
369-
os.makedirs(NUMERICAL_RESULTS_DATA_DIR, exist_ok=True)
358+
results_dir.mkdir(parents=True, exist_ok=True)
370359

371360
num_tests = 0
372361
for monitor_size_wvl in monitor_sizes_3d_wvl:
@@ -414,8 +403,8 @@ def test_finite_difference_conductivity_data(
414403

415404
eval_fns, eval_fn_names = make_eval_fns(monitor_size_wvl)
416405

417-
sim_path_dir = tmp_path / f"test{test_number}"
418-
sim_path_dir.mkdir()
406+
sim_path_dir = numerical_case_dir / "simulations" / f"test{test_number}"
407+
sim_path_dir.mkdir(parents=True, exist_ok=True)
419408

420409
objective = create_objective_function(
421410
block,
@@ -494,11 +483,21 @@ def test_finite_difference_conductivity_data(
494483
print("-" * 20)
495484
print("\n" * 3)
496485

497-
assert rms_error < RMS_THRESHOLD * fd_mag, "RMS error magnitude too large"
498-
499486
test_results[SAVE_FD_LOC, :] = fd_grad
500487
test_results[SAVE_ADJ_LOC, :] = pattern_dot_adj_gradient
501488

489+
save_idx = test_number + 1
490+
save_path = None
491+
if SAVE_FD_ADJ_DATA:
492+
results_dir.mkdir(parents=True, exist_ok=True)
493+
save_path = results_dir / f"results_{save_idx}.npy"
494+
495+
try:
496+
assert rms_error < RMS_THRESHOLD * fd_mag, "RMS error magnitude too large"
497+
finally:
498+
if save_path is not None:
499+
np.save(save_path, test_results)
500+
502501
test_number += 1
503502

504503
if PLOT_FD_ADJ_COMPARISON:
@@ -512,12 +511,9 @@ def test_finite_difference_conductivity_data(
512511
plt.grid(True, alpha=0.3)
513512

514513
# Save the plot
515-
plot_filename = f"{NUMERICAL_RESULTS_DATA_DIR}/gradient_comparison_test_{test_number}_{eval_fn_name}.png"
514+
plot_filename = results_dir / (f"gradient_comparison_test_{test_number}_{eval_fn_name}.png")
516515
plt.savefig(plot_filename, dpi=150, bbox_inches="tight")
517516
print(f"Plot saved to: {plot_filename}")
518517

519518
plt.show()
520519
plt.close()
521-
522-
if SAVE_FD_ADJ_DATA:
523-
np.save(f"{NUMERICAL_RESULTS_DATA_DIR}/results_{test_number}.npy", test_results)

tests/test_components/autograd/numerical/test_autograd_mode_polyslab_numerical.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
SAVE_ADJ_LOC = 1
2121
LOCAL_GRADIENT = True
2222
VERBOSE = False
23-
NUMERICAL_RESULTS_DATA_DIR = "./numerical_mode_polyslab_test/"
23+
NUMERICAL_RESULTS_SUBDIR = "numerical_mode_polyslab_test"
2424
SHOW_PRINT_STATEMENTS = False
2525

2626
NUM_MODE_MONITOR_FREQUENCIES = 4
@@ -301,18 +301,8 @@ def objective(vertices):
301301

302302

303303
@pytest.mark.numerical
304-
@pytest.mark.parametrize(
305-
"mode_data_test_parameters, dir_name",
306-
zip(
307-
mode_data_test_parameters,
308-
([NUMERICAL_RESULTS_DATA_DIR] if SAVE_FD_ADJ_DATA else [None])
309-
* len(mode_data_test_parameters),
310-
),
311-
indirect=["dir_name"],
312-
)
313-
def test_finite_difference_mode_data_polyslab(
314-
mode_data_test_parameters, rng, tmp_path, create_directory
315-
):
304+
@pytest.mark.parametrize("mode_data_test_parameters", mode_data_test_parameters)
305+
def test_finite_difference_mode_data_polyslab(mode_data_test_parameters, rng, numerical_case_dir):
316306
"""Test a variety of autograd permittivity gradients for ModeData in combination with polyslab by"""
317307
"""comparing them to numerical finite difference."""
318308

@@ -351,8 +341,8 @@ def test_finite_difference_mode_data_polyslab(
351341
size=(np.inf, np.inf, MODE_LAYER_HEIGHT_WVL * mesh_wvl_um + mesh_wvl_um),
352342
)
353343

354-
sim_path_dir = tmp_path / f"test{test_number}"
355-
sim_path_dir.mkdir()
344+
sim_path_dir = numerical_case_dir / "simulations" / f"test{test_number}"
345+
sim_path_dir.mkdir(parents=True, exist_ok=True)
356346

357347
# Weights for creating a random objective function over multiple frequencies by
358348
# summing their contributions by random weights. This helps verify gradient errors
@@ -471,11 +461,22 @@ def eval_fn(sim_data):
471461
print("-" * 20)
472462
print("\n" * 3)
473463

474-
assert rms_error < RMS_THRESHOLD * fd_mag, "RMS error magnitude too large"
475-
476464
test_results[SAVE_FD_LOC, :] = fd_grad
477465
test_results[SAVE_ADJ_LOC, :] = pattern_dot_adj_gradient
478466

467+
save_idx = test_number + 1
468+
save_path = None
469+
if SAVE_FD_ADJ_DATA:
470+
results_dir = numerical_case_dir / NUMERICAL_RESULTS_SUBDIR
471+
results_dir.mkdir(parents=True, exist_ok=True)
472+
save_path = results_dir / f"results_{save_idx}.npy"
473+
474+
try:
475+
assert rms_error < RMS_THRESHOLD * fd_mag, "RMS error magnitude too large"
476+
finally:
477+
if save_path is not None:
478+
np.save(save_path, test_results)
479+
479480
test_number += 1
480481

481482
if PLOT_FD_ADJ_COMPARISON:
@@ -487,6 +488,3 @@ def eval_fn(sim_data):
487488
plt.ylabel("Gradient value")
488489
plt.legend()
489490
plt.show()
490-
491-
if SAVE_FD_ADJ_DATA:
492-
np.save(f"{NUMERICAL_RESULTS_DATA_DIR}/results_{test_number}.npy", test_results)

0 commit comments

Comments
 (0)