Skip to content

Conversation

@marcorudolphflex
Copy link
Contributor

@marcorudolphflex marcorudolphflex commented Nov 12, 2025

As our test suite does have a quite long runtime, this PR addresses the identification of the most time-intensive modules/functions/runs and speeds up the most critical ones.

  • added a profile_pytest.py in scripts to analyze the most time-consuming modules/functions/runs (see report below)
  • added a pytest.mark.slow for the most time-consuming tests. May be used later for skipping for faster test modes.
  • made some optimizations in test_autograd.py as the most critical module (10 min raw runtime!) -> cut it down by roughly the half. Made sure these tests run in the beginning such that we avoid non-fully-parallelized works are on them at the end.
    • do we want to skip some structures for async? Shouldn't be the async run functionality independently from the structures?
  • made some "obvious" minor performance optimizations for other test functions
  • reduction of total test time by roughly 30%

I'm open for more inputs how to speedup the tests.

Analysis before PR:

============================== slowest durations ===============================
58.40s call     tests/test_plugins/test_design.py::test_sweep[sweep_method0]
27.68s call     tests/test_components/test_simulation.py::test_num_lumped_elements
21.97s call     tests/test_components/test_scene.py::test_num_mediums
19.13s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[True-<ALL>-diff]
18.59s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[True-<ALL>-field_vol]
18.47s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[True-<ALL>-field_point]
18.32s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[True-<ALL>-mode]
17.95s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[False-<ALL>-diff]
17.77s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[False-<ALL>-field_point]
17.69s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[False-<ALL>-mode]
16.93s call     tests/test_components/test_IO.py::test_validation_speed
16.49s call     tests/test_components/autograd/test_autograd.py::test_autograd_async[False-<ALL>-field_vol]
14.61s call     tests/test_components/test_custom.py::test_custom_lorentz[True]
13.80s call     tests/test_components/test_eme.py::test_eme_sim_data
13.53s call     tests/test_plugins/test_design.py::test_sweep[sweep_method2]
13.50s call     tests/test_plugins/test_invdes.py::test_continue_run_from_file
12.29s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_server[<ALL>-diff]
12.17s call     tests/test_plugins/test_dispersion_fitter.py::test_dispersion_set_wvg_range
11.90s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_server[<ALL>-field_vol]
11.88s call     tests/test_plugins/test_design.py::test_sweep[sweep_method3]
11.65s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_server[<ALL>-mode]
11.62s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_server[<ALL>-field_point]
11.11s call     tests/test_components/test_custom.py::test_custom_sellmeier[True]
10.31s call     tests/test_data/test_sim_data.py::test_plot[1.0]
10.28s call     tests/test_components/test_custom.py::test_custom_pole_residue[True]
10.20s call     tests/test_plugins/test_invdes.py::test_result_store_full_results_is_false
9.68s call     tests/test_components/test_custom.py::test_custom_anisotropic_medium[True]
9.32s call     tests/test_data/test_sim_data.py::test_plot[0]
9.28s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_some_zero_grad[<ALL>-diff]
8.74s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_some_zero_grad[<ALL>-mode]

(28084 durations < 0.005s hidden.  Use -vv to show these durations.)
========== 10559 passed, 67 skipped, 6 warnings in 195.88s (0:03:15) ===========
Aggregated durations (by file):
  635.72s  tests/test_components/autograd/test_autograd.py
  132.66s  tests/test_components/test_simulation.py
  109.70s  tests/test_plugins/test_design.py
   77.06s  tests/test_components/test_custom.py
   58.46s  tests/test_plugins/test_mode_solver.py
   50.78s  tests/test_components/test_IO.py
   49.24s  tests/test_plugins/test_dispersion_fitter.py
   45.02s  tests/test_plugins/test_invdes.py
   40.38s  tests/test_plugins/smatrix/test_terminal_component_modeler.py
   35.89s  tests/test_data/test_sim_data.py
   32.64s  tests/test_plugins/autograd/invdes/test_filters.py
   32.34s  tests/test_data/test_monitor_data.py
   29.58s  tests/test_components/test_scene.py
   18.20s  tests/test_components/test_eme.py
   18.17s  tests/test_web/test_webapi.py
   16.96s  tests/test_package/test_parametric_variants.py
   16.66s  tests/test_components/test_sidewall.py
   11.37s  tests/test_components/test_microwave.py
   10.19s  tests/test_plugins/autograd/invdes/test_penalties.py
    9.67s  tests/test_plugins/smatrix/test_component_modeler.py
    9.65s  tests/test_data/test_datasets.py
    8.41s  tests/test_plugins/autograd/invdes/test_parametrizations.py
    8.16s  tests/test_components/test_medium.py
    7.56s  tests/test_components/autograd/test_autograd_polyslab.py
    6.30s  tests/test_components/test_heat_charge.py
    6.22s  tests/test_plugins/smatrix/test_component_modeler_autograd.py
    5.00s  tests/test_components/test_mode.py
    4.59s  tests/test_components/test_field_projection.py
    4.34s  tests/test_components/test_parameter_perturbation.py
    4.26s  tests/test_components/test_heat.py

Aggregated durations (by test (parametrizations combined)):
  220.42s  tests/test_components/autograd/test_autograd.py::test_autograd_async
   98.23s  tests/test_plugins/test_design.py::test_sweep
   72.02s  tests/test_components/autograd/test_autograd.py::test_autograd_async_server
   56.05s  tests/test_components/autograd/test_autograd.py::test_autograd_async_some_zero_grad
   46.61s  tests/test_components/autograd/test_autograd.py::test_multi_frequency_equivalence
   39.50s  tests/test_components/autograd/test_autograd.py::test_autograd_objective
   38.17s  tests/test_components/autograd/test_autograd.py::test_autograd_server
   37.65s  tests/test_components/autograd/test_autograd.py::TestFieldProjection::test_field_projection_grad_prop
   31.49s  tests/test_components/test_simulation.py::test_sim_subsection
   27.68s  tests/test_components/test_simulation.py::test_num_lumped_elements
   25.01s  tests/test_components/autograd/test_autograd.py::test_interp_objectives
   21.97s  tests/test_components/test_scene.py::test_num_mediums
   20.08s  tests/test_components/test_simulation.py::TestAnisotropicPlotting::test_plot_fully_anisotropic_medium_diff
   20.00s  tests/test_components/autograd/test_autograd.py::TestFieldProjection::test_error_if_server_side_projection
   19.63s  tests/test_data/test_sim_data.py::test_plot
   18.48s  tests/test_components/autograd/test_autograd.py::test_vjp_nan
   17.49s  tests/test_components/test_custom.py::test_custom_lorentz
   16.93s  tests/test_components/test_IO.py::test_validation_speed
   16.19s  tests/test_plugins/autograd/invdes/test_filters.py::TestMakeFilter::test_make_filter
   16.12s  tests/test_package/test_parametric_variants.py::test_graphene
   15.68s  tests/test_data/test_monitor_data.py::TestZBF::test_fielddata_tozbf_readzbf
   13.80s  tests/test_components/test_eme.py::test_eme_sim_data
   13.50s  tests/test_plugins/test_invdes.py::test_continue_run_from_file
   12.86s  tests/test_components/test_custom.py::test_custom_sellmeier
   12.73s  tests/test_web/test_webapi.py::test_batch_run_accepts_pathlike_dir
   12.47s  tests/test_components/test_custom.py::test_custom_pole_residue
   12.17s  tests/test_plugins/test_dispersion_fitter.py::test_dispersion_set_wvg_range
   10.92s  tests/test_plugins/test_mode_solver.py::test_mode_solver_unstructured_custom_medium
   10.48s  tests/test_components/test_custom.py::test_custom_debye
   10.46s  tests/test_components/autograd/test_autograd.py::test_multi_freq_edge_cases

Analysis after PR

============================== slowest durations ===============================
40.14s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_all
39.39s call     tests/test_components/autograd/test_autograd.py::test_autograd_async_server
21.09s call     tests/test_plugins/test_mode_solver.py::test_sort_spec_track_freq
16.44s call     tests/test_components/test_eme.py::test_eme_sim_data
14.87s call     tests/test_components/test_custom.py::test_custom_lorentz[True]
14.56s call     tests/test_plugins/test_invdes.py::test_continue_run_from_file
14.02s call     tests/test_plugins/test_design.py::test_sweep[sweep_method0]
12.81s call     tests/test_plugins/test_design.py::test_sweep[sweep_method3]
12.45s call     tests/test_plugins/test_dispersion_fitter.py::test_dispersion_set_wvg_range
12.12s call     tests/test_components/test_custom.py::test_custom_sellmeier[True]
10.32s call     tests/test_plugins/test_invdes.py::test_result_store_full_results_is_false
10.29s call     tests/test_plugins/test_design.py::test_sweep[sweep_method2]
10.28s call     tests/test_components/test_custom.py::test_custom_pole_residue[True]
9.83s call     tests/test_components/test_custom.py::test_custom_anisotropic_medium[True]
9.75s call     tests/test_plugins/test_dispersion_fitter.py::test_dispersion_loss_samples
9.73s call     tests/test_data/test_sim_data.py::test_plot[1.0]
9.46s call     tests/test_components/test_sidewall.py::test_intersection_with_inside_poly
9.30s call     tests/test_components/test_simulation.py::test_sim_subsection[True-13]
9.23s call     tests/test_data/test_sim_data.py::test_plot[0]
8.70s call     tests/test_plugins/test_dispersion_fitter.py::test_lossy_dispersion
8.57s call     tests/test_components/test_custom.py::test_custom_debye[True]
8.54s call     tests/test_components/test_mode.py::test_mode_sim
8.37s call     tests/test_components/test_microwave.py::test_mode_solver_with_microwave_mode_spec
8.17s call     tests/test_plugins/test_invdes.py::test_continue_run_fns
8.15s call     tests/test_plugins/test_dispersion_fitter.py::test_lossless_dispersion
8.04s call     tests/test_plugins/test_design.py::test_sweep[sweep_method4]
7.58s call     tests/test_components/autograd/test_autograd.py::test_autograd_server[<ALL>-field_point]
7.58s call     tests/test_components/autograd/test_autograd.py::test_web_run_duplicate_simulations
7.42s call     tests/test_components/test_simulation.py::test_sim_subsection[True-1]
7.40s call     tests/test_plugins/test_mode_solver.py::test_mode_solver_straight_vs_angled

(27789 durations < 0.005s hidden.  Use -vv to show these durations.)
========= 10469 passed, 66 skipped, 193 warnings in 137.23s (0:02:17) ==========
Aggregated durations (by file):
  330.78s  tests/test_components/autograd/test_autograd.py
  113.18s  tests/test_plugins/test_mode_solver.py
  101.27s  tests/test_components/test_simulation.py
   79.02s  tests/test_components/test_custom.py
   62.36s  tests/test_plugins/test_design.py
   52.02s  tests/test_plugins/test_dispersion_fitter.py
   47.17s  tests/test_plugins/test_invdes.py
   39.07s  tests/test_plugins/smatrix/test_terminal_component_modeler.py
   35.71s  tests/test_data/test_sim_data.py
   34.52s  tests/test_components/test_IO.py
   33.87s  tests/test_plugins/autograd/invdes/test_filters.py
   31.98s  tests/test_data/test_monitor_data.py
   21.22s  tests/test_components/test_eme.py
   19.61s  tests/test_components/test_microwave.py
   17.33s  tests/test_components/test_sidewall.py
   11.67s  tests/test_web/test_webapi.py
   10.97s  tests/test_plugins/autograd/invdes/test_penalties.py
   10.11s  tests/test_data/test_datasets.py
    9.85s  tests/test_plugins/smatrix/test_component_modeler.py
    9.49s  tests/test_components/test_mode.py
    9.28s  tests/test_package/test_parametric_variants.py
    8.79s  tests/test_plugins/autograd/invdes/test_parametrizations.py
    7.86s  tests/test_components/test_medium.py
    7.71s  tests/test_components/test_scene.py
    7.64s  tests/test_components/autograd/test_autograd_polyslab.py
    7.12s  tests/test_components/test_heat_charge.py
    6.17s  tests/test_plugins/smatrix/test_component_modeler_autograd.py
    5.24s  tests/test_components/test_parameter_perturbation.py
    5.13s  tests/test_plugins/autograd/test_functions.py
    4.86s  tests/test_plugins/test_array_factor.py

Aggregated durations (by test (parametrizations combined)):
   51.15s  tests/test_plugins/test_design.py::test_sweep
   40.99s  tests/test_components/autograd/test_autograd.py::test_autograd_server
   40.67s  tests/test_components/autograd/test_autograd.py::test_autograd_objective
   40.14s  tests/test_components/autograd/test_autograd.py::test_autograd_async_all
   39.44s  tests/test_components/autograd/test_autograd.py::TestFieldProjection::test_field_projection_grad_prop
   39.39s  tests/test_components/autograd/test_autograd.py::test_autograd_async_server
   30.82s  tests/test_components/test_simulation.py::test_sim_subsection
   26.26s  tests/test_components/autograd/test_autograd.py::test_interp_objectives
   24.16s  tests/test_plugins/test_mode_solver.py::test_mode_solver_unstructured_custom_medium
   22.55s  tests/test_components/autograd/test_autograd.py::TestFieldProjection::test_error_if_server_side_projection
   21.09s  tests/test_plugins/test_mode_solver.py::test_sort_spec_track_freq
   19.51s  tests/test_components/test_simulation.py::TestAnisotropicPlotting::test_plot_fully_anisotropic_medium_diff
   18.96s  tests/test_data/test_sim_data.py::test_plot
   17.98s  tests/test_components/test_custom.py::test_custom_lorentz
   16.51s  tests/test_plugins/autograd/invdes/test_filters.py::TestMakeFilter::test_make_filter
   16.44s  tests/test_components/test_eme.py::test_eme_sim_data
   15.57s  tests/test_data/test_monitor_data.py::TestZBF::test_fielddata_tozbf_readzbf
   14.56s  tests/test_plugins/test_invdes.py::test_continue_run_from_file
   13.92s  tests/test_components/test_custom.py::test_custom_sellmeier
   12.89s  tests/test_components/autograd/test_autograd.py::test_multi_freq_edge_cases
   12.70s  tests/test_components/test_custom.py::test_custom_pole_residue
   12.45s  tests/test_plugins/test_dispersion_fitter.py::test_dispersion_set_wvg_range
   10.97s  tests/test_plugins/autograd/invdes/test_penalties.py::test_make_erosion_dilation_penalty
   10.57s  tests/test_components/test_custom.py::test_custom_debye
   10.32s  tests/test_plugins/test_invdes.py::test_result_store_full_results_is_false
    9.92s  tests/test_plugins/test_mode_solver.py::test_mode_solver_simple
    9.83s  tests/test_components/test_custom.py::test_custom_anisotropic_medium
    9.75s  tests/test_plugins/test_dispersion_fitter.py::test_dispersion_loss_samples
    9.46s  tests/test_components/test_sidewall.py::test_intersection_with_inside_poly
    8.79s  tests/test_plugins/autograd/invdes/test_parametrizations.py::test_make_filter_and_project

Greptile Overview

Greptile Summary

This PR achieves a ~30% reduction in test suite runtime (195s → 137s) through strategic optimizations targeting the most time-intensive tests, particularly in the autograd module which was reduced from 636s to 331s.

Key Changes:

  • Consolidated parameterized autograd tests into batch runs, reducing test_autograd_async from 32 independent test runs to 2 combined batch tests
  • Added pytest.mark.slow markers to 15+ expensive tests for potential future filtering
  • Reduced test parameter spaces (e.g., validation test structures from 100 to ~3, graphene seeds from 15 to 8)
  • Added pytest.mark.order(0) to run autograd tests first, ensuring they execute in parallel rather than serially at the end
  • Introduced scripts/profile_pytest.py utility for ongoing performance analysis
  • Added pytest-order dependency for test execution control

Trade-offs:
The performance gains come with reduced test coverage isolation. Tests that previously ran 16+ structure/monitor combinations independently now run all combinations in a single batch, making individual failures harder to isolate and debug. The test_multi_frequency_equivalence test now only validates the first structure type instead of all 12.

Confidence Score: 3/5

  • Safe to merge with moderate risk due to reduced test coverage isolation
  • Score reflects successful performance improvements (~30% speedup) but concerns about test coverage trade-offs. The consolidation of parameterized tests into batches reduces independent validation of structure/monitor combinations, potentially masking edge case failures. The changes are well-intentioned for CI performance but sacrifice some test granularity.
  • Pay close attention to tests/test_components/autograd/test_autograd.py - the test consolidation significantly changes coverage patterns and may make debugging autograd issues harder in production

Important Files Changed

File Analysis

Filename Score Overview
scripts/profile_pytest.py 5/5 New profiling utility script for analyzing test suite performance
pyproject.toml 5/5 Added pytest-order dependency for controlling test execution order
tests/test_components/autograd/test_autograd.py 2/5 Major refactoring to consolidate parameterized tests into batch runs; significant test coverage reduction from ~32 to ~2 test runs for async tests
tests/test_components/test_simulation.py 5/5 Reduced TEST_MAX_NUM_MEDIUMS constant from production value to 3 for faster tests
tests/test_components/test_IO.py 5/5 Reduced validation speed test iterations from 10 to 2 and structure range from 100 to ~3
tests/test_plugins/test_design.py 5/5 Reduced sweep test parameters (grid points, sphere counts, allowed values) and added slow markers

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant Script as profile_pytest.py
    participant Pytest as pytest
    participant Tests as Test Suite
    participant CI as CI Pipeline
    
    Dev->>Script: Run profiling analysis
    Script->>Pytest: Execute with --durations
    Pytest->>Tests: Run full suite
    Tests-->>Pytest: Execution times
    Pytest-->>Script: Duration data
    Script->>Script: Aggregate by file/test
    Script-->>Dev: Performance report
    
    Note over Dev: Identifies slow tests<br/>(autograd: 636s)
    
    Dev->>Tests: Add pytest.mark.slow
    Dev->>Tests: Consolidate parameterized tests
    Dev->>Tests: Reduce test parameters
    Dev->>Tests: Add pytest.mark.order(0)
    
    CI->>Pytest: Run test suite
    Pytest->>Tests: Execute in order
    Note over Tests: Autograd tests run first<br/>(parallel execution)
    Tests->>Tests: Batch async tests together
    Tests-->>Pytest: Combined results
    Pytest-->>CI: Suite complete (137s vs 195s)
    
    Note over CI,Tests: 30% faster execution<br/>but reduced test isolation
Loading

@marcorudolphflex
Copy link
Contributor Author

@greptile

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-3721-speed-up-test-suite branch from dd55b29 to 0d5292d Compare November 12, 2025 12:11
@marcorudolphflex marcorudolphflex marked this pull request as ready for review November 12, 2025 12:11
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@marcorudolphflex marcorudolphflex force-pushed the FXC-3721-speed-up-test-suite branch 2 times, most recently from 700ecb3 to 693676d Compare November 12, 2025 12:19
@github-actions
Copy link
Contributor

Diff Coverage

Diff: origin/develop...HEAD, staged and unstaged changes

No lines with coverage information in this diff.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering a bit what benefit this provides over just running pytest --durations > times.txt?

Comment on lines +185 to +190
N_tests = 2 # may be increased temporarily, makes it slow for routine tests
max_structures = np.log10(2) # may be increased temporarily, makes it slow for routine tests

# adjust as needed, keeping small to speed tests up
num_structures = np.logspace(0, 2, N_tests).astype(int)
num_structures = np.logspace(0, max_structures, N_tests).astype(int)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this turns the test in this file into a smoke-test essentially. i believe the purpose there was to catch regressions related to validation time where the serialization scales poorly as structure count grows. now we basically just assert that both cases work. but to be fair the old test didnt actually assert any regressions so i'm not really sure what makes sense here, maybe @momchil-flex has an opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made for now a "perf" marker and deselected for regular tests

@marcorudolphflex marcorudolphflex force-pushed the FXC-3721-speed-up-test-suite branch from 5bbca11 to 3f326b4 Compare November 13, 2025 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants