optixde 0.2.2__tar.gz → 0.2.4__tar.gz

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.
Files changed (101) hide show
  1. {optixde-0.2.2 → optixde-0.2.4}/CHANGELOG.md +32 -1
  2. {optixde-0.2.2 → optixde-0.2.4}/CITATION.cff +3 -3
  3. {optixde-0.2.2 → optixde-0.2.4}/PKG-INFO +47 -19
  4. {optixde-0.2.2 → optixde-0.2.4}/README.md +46 -18
  5. optixde-0.2.4/optixde/custom_pde/__init__.py +62 -0
  6. optixde-0.2.4/optixde/custom_pde/compiler.py +309 -0
  7. optixde-0.2.4/optixde/custom_pde/errors.py +23 -0
  8. optixde-0.2.4/optixde/custom_pde/ir.py +45 -0
  9. optixde-0.2.4/optixde/custom_pde/solver.py +270 -0
  10. optixde-0.2.4/optixde/custom_pde/symbolic.py +169 -0
  11. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/__init__.py +55 -1
  12. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/__init__.py +36 -0
  13. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/burgers.py +146 -13
  14. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/cylinder_flow.py +111 -35
  15. optixde-0.2.4/optixde/solvers/base/cylinder_re200.py +422 -0
  16. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/navier_stokes.py +293 -76
  17. optixde-0.2.4/optixde/solvers/cylinder_re200.py +7 -0
  18. optixde-0.2.4/optixde/solvers/dfg_cylinder.py +935 -0
  19. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/navier_stokes.py +8 -0
  20. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/solid/__init__.py +2 -0
  21. optixde-0.2.4/optixde/solvers/solid/elastic_halfspace.py +326 -0
  22. {optixde-0.2.2 → optixde-0.2.4}/optixde/version.py +1 -1
  23. {optixde-0.2.2 → optixde-0.2.4}/optixde.egg-info/PKG-INFO +47 -19
  24. {optixde-0.2.2 → optixde-0.2.4}/optixde.egg-info/SOURCES.txt +17 -0
  25. {optixde-0.2.2 → optixde-0.2.4}/pyproject.toml +1 -1
  26. {optixde-0.2.2 → optixde-0.2.4}/tests/test_burgers.py +72 -0
  27. optixde-0.2.4/tests/test_custom_pde.py +246 -0
  28. optixde-0.2.4/tests/test_cylinder_flow_paper.py +225 -0
  29. optixde-0.2.4/tests/test_cylinder_re200.py +174 -0
  30. optixde-0.2.4/tests/test_dfg_cylinder.py +245 -0
  31. optixde-0.2.4/tests/test_elastic_halfspace.py +83 -0
  32. optixde-0.2.4/tests/test_lshape_poisson_paper.py +84 -0
  33. {optixde-0.2.2 → optixde-0.2.4}/tests/test_navier_stokes.py +48 -0
  34. optixde-0.2.4/tests/test_nonlinear_schrodinger_paper.py +43 -0
  35. {optixde-0.2.2 → optixde-0.2.4}/tests/test_public_api.py +7 -0
  36. {optixde-0.2.2 → optixde-0.2.4}/tests/test_validation.py +12 -0
  37. {optixde-0.2.2 → optixde-0.2.4}/LICENSE +0 -0
  38. {optixde-0.2.2 → optixde-0.2.4}/MANIFEST.in +0 -0
  39. {optixde-0.2.2 → optixde-0.2.4}/optixde/__init__.py +0 -0
  40. {optixde-0.2.2 → optixde-0.2.4}/optixde/bc/__init__.py +0 -0
  41. {optixde-0.2.2 → optixde-0.2.4}/optixde/bc/rasterize.py +0 -0
  42. {optixde-0.2.2 → optixde-0.2.4}/optixde/bc/robin.py +0 -0
  43. {optixde-0.2.2 → optixde-0.2.4}/optixde/bc/segmented.py +0 -0
  44. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/__init__.py +0 -0
  45. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/base.py +0 -0
  46. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/cupy_backend.py +0 -0
  47. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/numpy_backend.py +0 -0
  48. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/propagator_cache.py +0 -0
  49. {optixde-0.2.2 → optixde-0.2.4}/optixde/fft_backend/torch_backend.py +0 -0
  50. {optixde-0.2.2 → optixde-0.2.4}/optixde/geometry/__init__.py +0 -0
  51. {optixde-0.2.2 → optixde-0.2.4}/optixde/geometry/boolean.py +0 -0
  52. {optixde-0.2.2 → optixde-0.2.4}/optixde/geometry/primitives.py +0 -0
  53. {optixde-0.2.2 → optixde-0.2.4}/optixde/operators/__init__.py +0 -0
  54. {optixde-0.2.2 → optixde-0.2.4}/optixde/operators/spectral.py +0 -0
  55. {optixde-0.2.2 → optixde-0.2.4}/optixde/operators/transforms.py +0 -0
  56. {optixde-0.2.2 → optixde-0.2.4}/optixde/post/__init__.py +0 -0
  57. {optixde-0.2.2 → optixde-0.2.4}/optixde/post/plotting.py +0 -0
  58. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/_diagnostics.py +0 -0
  59. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/allen_cahn.py +0 -0
  60. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/allen_cahn.py +0 -0
  61. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/diffusion.py +0 -0
  62. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/etd.py +0 -0
  63. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/helmholtz.py +0 -0
  64. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/ode.py +0 -0
  65. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/poisson.py +0 -0
  66. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/poisson_inhom.py +0 -0
  67. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/schrodinger.py +0 -0
  68. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/base/wave.py +0 -0
  69. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/burgers.py +0 -0
  70. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/cylinder_flow.py +0 -0
  71. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/diffusion.py +0 -0
  72. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/helmholtz.py +0 -0
  73. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/poisson.py +0 -0
  74. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/schrodinger.py +0 -0
  75. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/segmented/__init__.py +0 -0
  76. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/segmented/poisson.py +0 -0
  77. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/segmented/polygonal.py +0 -0
  78. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/segmented/transient.py +0 -0
  79. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/solid/elasticity_dynamic.py +0 -0
  80. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/solid/elasticity_static.py +0 -0
  81. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/solid/postprocess.py +0 -0
  82. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/splitting.py +0 -0
  83. {optixde-0.2.2 → optixde-0.2.4}/optixde/solvers/wave.py +0 -0
  84. {optixde-0.2.2 → optixde-0.2.4}/optixde.egg-info/dependency_links.txt +0 -0
  85. {optixde-0.2.2 → optixde-0.2.4}/optixde.egg-info/requires.txt +0 -0
  86. {optixde-0.2.2 → optixde-0.2.4}/optixde.egg-info/top_level.txt +0 -0
  87. {optixde-0.2.2 → optixde-0.2.4}/setup.cfg +0 -0
  88. {optixde-0.2.2 → optixde-0.2.4}/tests/test_allen_cahn.py +0 -0
  89. {optixde-0.2.2 → optixde-0.2.4}/tests/test_backend_cache.py +0 -0
  90. {optixde-0.2.2 → optixde-0.2.4}/tests/test_core_solvers.py +0 -0
  91. {optixde-0.2.2 → optixde-0.2.4}/tests/test_cylinder_flow.py +0 -0
  92. {optixde-0.2.2 → optixde-0.2.4}/tests/test_cylinder_flow_example.py +0 -0
  93. {optixde-0.2.2 → optixde-0.2.4}/tests/test_import.py +0 -0
  94. {optixde-0.2.2 → optixde-0.2.4}/tests/test_robin.py +0 -0
  95. {optixde-0.2.2 → optixde-0.2.4}/tests/test_schrodinger.py +0 -0
  96. {optixde-0.2.2 → optixde-0.2.4}/tests/test_solver_convergence.py +0 -0
  97. {optixde-0.2.2 → optixde-0.2.4}/tests/test_solver_diagnostics.py +0 -0
  98. {optixde-0.2.2 → optixde-0.2.4}/tests/test_splitting.py +0 -0
  99. {optixde-0.2.2 → optixde-0.2.4}/tests/test_torch_backend.py +0 -0
  100. {optixde-0.2.2 → optixde-0.2.4}/tests/test_transforms.py +0 -0
  101. {optixde-0.2.2 → optixde-0.2.4}/tests/test_wave.py +0 -0
@@ -7,6 +7,35 @@ and OptiXDE uses semantic versioning for public releases.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.4] - 2026-08-16
11
+
12
+ ### Added
13
+
14
+ - An additive `optixde.custom_pde` subsystem for compiling scalar,
15
+ constant-coefficient, two-dimensional periodic linear PDEs into steady or
16
+ transient generic spectral execution plans.
17
+ - A hybrid-spectral two-dimensional elastic half-space solver in
18
+ `optixde.solvers.solid`, supporting a traction-free surface, absorbing
19
+ exterior layers, configurable source histories, and sampled wavefields.
20
+ - Backend-independent periodic immersed-boundary Navier--Stokes utilities for
21
+ NumPy and CuPy, including nonzero mean flow and split constraints.
22
+ - Exact three-halves padding dealiasing for the periodic Burgers solver.
23
+
24
+ ### Changed
25
+
26
+ - Updated the Burgers and immersed-cylinder paper examples to call the public
27
+ `optixde.solvers` implementations while preserving their numerical kernels
28
+ and reference outputs.
29
+ - Aligned the canonical Re=200 paper driver with the final frozen production
30
+ configuration (ND=24/32/40, eta=0.0025, compact mask width 1h, T=100).
31
+
32
+ ## [0.2.3] - 2026-08-04
33
+
34
+ ### Changed
35
+
36
+ - Fixed the PyPI project-page logo by using an absolute HTTPS image URL.
37
+ - Removed the benchmark-reference subsection from the main README.
38
+
10
39
  ## [0.2.2] - 2026-07-27
11
40
 
12
41
  ### Added
@@ -77,7 +106,9 @@ and OptiXDE uses semantic versioning for public releases.
77
106
  modules are still experimental and should be validated for each research use
78
107
  case.
79
108
 
80
- [Unreleased]: https://github.com/yangyLab/OptiXDE/compare/v0.2.2...main
109
+ [Unreleased]: https://github.com/yangyLab/OptiXDE/compare/v0.2.4...main
110
+ [0.2.4]: https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.4
111
+ [0.2.3]: https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.3
81
112
  [0.2.2]: https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.2
82
113
  [0.2.1]: https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.1
83
114
  [0.2.0]: https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.0
@@ -1,8 +1,8 @@
1
1
  cff-version: 1.2.0
2
2
  message: "If you use OptiXDE in academic work, please cite this software and the related paper or preprint when available."
3
3
  title: "OptiXDE: optical-inspired PDE solvers"
4
- version: "0.2.2"
5
- date-released: "2026-07-27"
4
+ version: "0.2.4"
5
+ date-released: "2026-08-16"
6
6
  type: software
7
7
  license: MIT
8
8
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  given-names: "Yang"
11
11
  email: "yangyhhu@foxmail.com"
12
12
  repository-code: "https://github.com/yangyLab/OptiXDE"
13
- url: "https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.2"
13
+ url: "https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.4"
14
14
  abstract: "OptiXDE is a lightweight Python package for optical-inspired, matrix-free spectral solvers for differential equations on uniform grids, with examples for linear PDEs, nonlinear splitting, flow, wave propagation, and GPU-oriented periodic FFT workflows."
15
15
  keywords:
16
16
  - PDE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optixde
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: OptiXDE: optical-inspired PDE solver
5
5
  Author-email: Yang Yang <yangyhhu@foxmail.com>
6
6
  License-Expression: MIT
@@ -33,13 +33,13 @@ Requires-Dist: twine>=5; extra == "dev"
33
33
  Dynamic: license-file
34
34
 
35
35
  <p align="center">
36
- <img src="optixde_logo_horizontal.svg" alt="OptiXDE logo" width="620">
36
+ <img src="./optixde_logo_horizontal.svg" alt="OptiXDE logo" width="620">
37
37
  </p>
38
38
 
39
39
  # OptiXDE
40
40
 
41
41
  [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](pyproject.toml)
42
- [![Release](https://img.shields.io/badge/release-v0.2.2-blueviolet.svg)](https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.2)
42
+ [![Release](https://img.shields.io/badge/release-v0.2.4-blueviolet.svg)](https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.4)
43
43
  [![Tests](https://img.shields.io/badge/tests-unittest-green.svg)](#development)
44
44
  [![GPU](https://img.shields.io/badge/GPU-PyTorch%20periodic%20FFT-purple.svg)](docs/colab_torch_gpu.md)
45
45
 
@@ -59,8 +59,11 @@ The core convention is consistent across the package:
59
59
 
60
60
  - **Matrix-free solvers** for Poisson, Helmholtz, diffusion, and wave equations.
61
61
  - **Operator splitting** utilities for nonlinear PDEs such as Allen--Cahn.
62
+ - **Custom linear PDE compilation** from symbolic equations to periodic
63
+ spectral operators, without changing the existing specialized solvers.
62
64
  - **Nonlinear examples** for Allen--Cahn and 1D periodic viscous Burgers.
63
65
  - **Periodic incompressible flow** via 2D vorticity-streamfunction Navier--Stokes.
66
+ - **Periodic immersed-cylinder flow** with a Brinkman mask and inflow-restoring fringe.
64
67
  - **Schrödinger/paraxial propagation** for optical-style complex wave fields.
65
68
  - **FFT/DCT/DST transforms** for periodic, Dirichlet, and Neumann boundary
66
69
  conditions.
@@ -233,6 +236,10 @@ Current PyTorch scope:
233
236
  vorticity-streamfunction Navier--Stokes, and Schrödinger FFT paths.
234
237
  - Supported: CPU tensors and CUDA tensors, depending on your PyTorch install.
235
238
  - Not yet supported: Torch DCT/DST paths for Dirichlet and Neumann solvers.
239
+ - Periodic Brinkman/fringe Navier--Stokes supports NumPy and CuPy arrays; the
240
+ compatibility `cylinder_re200_*` names refer to the same general solver.
241
+ - The final Re=200 paper driver defaults to CuPy/CUDA and provides
242
+ `--backend numpy` for CPU smoke and regression runs.
236
243
 
237
244
  For Colab GPU validation, see `docs/colab_torch_gpu.md`.
238
245
 
@@ -321,6 +328,39 @@ python examples/base/navier_stokes_cylinder_brinkman.py --nx 96 --ny 48 --steps
321
328
  python examples/base/schrodinger_gaussian_packet.py --n 128 --T 1.0
322
329
  ```
323
330
 
331
+ Compile a scalar constant-coefficient periodic PDE:
332
+
333
+ ```python
334
+ from optixde.custom_pde import Equation, Field, compile_pde, dt, laplacian, solve_pde
335
+
336
+ u = Field("u")
337
+ problem = compile_pde(
338
+ Equation(dt(u), 0.05 * laplacian(u)),
339
+ shape=u0.shape,
340
+ domain=(Lx, Ly),
341
+ )
342
+ times, states = solve_pde(problem, u0, dt=0.01, t_end=1.0)
343
+ ```
344
+
345
+ The first compiler version supports one scalar field, constant coefficients,
346
+ two-dimensional periodic domains, steady equations, and first-order transient
347
+ equations with derivatives up to fourth order. Existing PDE-specific solver
348
+ APIs remain unchanged.
349
+
350
+ The paper Burgers driver uses the public solver with fourth-order nonlinear
351
+ stages and exact three-halves padding:
352
+
353
+ ```python
354
+ u_next = burgers1d_step(
355
+ u,
356
+ nu,
357
+ L,
358
+ dt,
359
+ nonlinear_order=4,
360
+ dealias="three_halves",
361
+ )
362
+ ```
363
+
324
364
  Run the unified reference-validation table:
325
365
 
326
366
  ```bash
@@ -343,6 +383,8 @@ Primary public imports:
343
383
 
344
384
  - `from optixde.solvers import poisson2d_solve, diffusion2d_solve, wave2d_solve`
345
385
  - `from optixde.solvers import burgers1d_solve, navier_stokes2d_vorticity_solve`
386
+ - `from optixde.solvers import navier_stokes2d_brinkman_fringe_step`
387
+ - `from optixde.solvers import cylinder_re200_step` (compatibility alias)
346
388
  - `from optixde.solvers import schrodinger2d_solve, allen_cahn2d_solve`
347
389
 
348
390
  PDE-specific modules are also kept as stable compatibility namespaces:
@@ -355,6 +397,7 @@ PDE-specific modules are also kept as stable compatibility namespaces:
355
397
  - `optixde.solvers.allen_cahn`
356
398
  - `optixde.solvers.burgers`
357
399
  - `optixde.solvers.navier_stokes`
400
+ - `optixde.solvers.cylinder_re200`
358
401
  - `optixde.solvers.schrodinger`
359
402
  - `optixde.fft_backend`
360
403
  - `optixde.geometry`
@@ -399,27 +442,12 @@ from `CITATION.cff`.
399
442
  author = {Yang, Yang},
400
443
  year = {2026},
401
444
  url = {https://github.com/yangyLab/OptiXDE},
402
- version = {0.2.2},
445
+ version = {0.2.4},
403
446
  }
404
447
  ```
405
448
 
406
449
  Release notes are tracked in `CHANGELOG.md`.
407
450
 
408
- ### Benchmark References
409
-
410
- Several examples reuse published benchmark problem definitions for independent
411
- OptiXDE implementations and comparisons:
412
-
413
- - M. Raissi, P. Perdikaris, and G. E. Karniadakis, “Physics-informed neural
414
- networks: A deep learning framework for solving forward and inverse problems
415
- involving nonlinear partial differential equations,” *Journal of
416
- Computational Physics*, 378, 686–707 (2019),
417
- [doi:10.1016/j.jcp.2018.10.045](https://doi.org/10.1016/j.jcp.2018.10.045).
418
- - L. Lu, X. Meng, Z. Mao, and G. E. Karniadakis, “DeepXDE: A deep learning
419
- library for solving differential equations,” *SIAM Review*, 63(1), 208–228
420
- (2021),
421
- [doi:10.1137/19M1274067](https://doi.org/10.1137/19M1274067).
422
-
423
451
  ## Copyright, License, and Disclaimer
424
452
 
425
453
  Copyright (c) 2025-2026 Yang Yang
@@ -1,11 +1,11 @@
1
1
  <p align="center">
2
- <img src="optixde_logo_horizontal.svg" alt="OptiXDE logo" width="620">
2
+ <img src="./optixde_logo_horizontal.svg" alt="OptiXDE logo" width="620">
3
3
  </p>
4
4
 
5
5
  # OptiXDE
6
6
 
7
7
  [![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](pyproject.toml)
8
- [![Release](https://img.shields.io/badge/release-v0.2.2-blueviolet.svg)](https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.2)
8
+ [![Release](https://img.shields.io/badge/release-v0.2.4-blueviolet.svg)](https://github.com/yangyLab/OptiXDE/releases/tag/v0.2.4)
9
9
  [![Tests](https://img.shields.io/badge/tests-unittest-green.svg)](#development)
10
10
  [![GPU](https://img.shields.io/badge/GPU-PyTorch%20periodic%20FFT-purple.svg)](docs/colab_torch_gpu.md)
11
11
 
@@ -25,8 +25,11 @@ The core convention is consistent across the package:
25
25
 
26
26
  - **Matrix-free solvers** for Poisson, Helmholtz, diffusion, and wave equations.
27
27
  - **Operator splitting** utilities for nonlinear PDEs such as Allen--Cahn.
28
+ - **Custom linear PDE compilation** from symbolic equations to periodic
29
+ spectral operators, without changing the existing specialized solvers.
28
30
  - **Nonlinear examples** for Allen--Cahn and 1D periodic viscous Burgers.
29
31
  - **Periodic incompressible flow** via 2D vorticity-streamfunction Navier--Stokes.
32
+ - **Periodic immersed-cylinder flow** with a Brinkman mask and inflow-restoring fringe.
30
33
  - **Schrödinger/paraxial propagation** for optical-style complex wave fields.
31
34
  - **FFT/DCT/DST transforms** for periodic, Dirichlet, and Neumann boundary
32
35
  conditions.
@@ -199,6 +202,10 @@ Current PyTorch scope:
199
202
  vorticity-streamfunction Navier--Stokes, and Schrödinger FFT paths.
200
203
  - Supported: CPU tensors and CUDA tensors, depending on your PyTorch install.
201
204
  - Not yet supported: Torch DCT/DST paths for Dirichlet and Neumann solvers.
205
+ - Periodic Brinkman/fringe Navier--Stokes supports NumPy and CuPy arrays; the
206
+ compatibility `cylinder_re200_*` names refer to the same general solver.
207
+ - The final Re=200 paper driver defaults to CuPy/CUDA and provides
208
+ `--backend numpy` for CPU smoke and regression runs.
202
209
 
203
210
  For Colab GPU validation, see `docs/colab_torch_gpu.md`.
204
211
 
@@ -287,6 +294,39 @@ python examples/base/navier_stokes_cylinder_brinkman.py --nx 96 --ny 48 --steps
287
294
  python examples/base/schrodinger_gaussian_packet.py --n 128 --T 1.0
288
295
  ```
289
296
 
297
+ Compile a scalar constant-coefficient periodic PDE:
298
+
299
+ ```python
300
+ from optixde.custom_pde import Equation, Field, compile_pde, dt, laplacian, solve_pde
301
+
302
+ u = Field("u")
303
+ problem = compile_pde(
304
+ Equation(dt(u), 0.05 * laplacian(u)),
305
+ shape=u0.shape,
306
+ domain=(Lx, Ly),
307
+ )
308
+ times, states = solve_pde(problem, u0, dt=0.01, t_end=1.0)
309
+ ```
310
+
311
+ The first compiler version supports one scalar field, constant coefficients,
312
+ two-dimensional periodic domains, steady equations, and first-order transient
313
+ equations with derivatives up to fourth order. Existing PDE-specific solver
314
+ APIs remain unchanged.
315
+
316
+ The paper Burgers driver uses the public solver with fourth-order nonlinear
317
+ stages and exact three-halves padding:
318
+
319
+ ```python
320
+ u_next = burgers1d_step(
321
+ u,
322
+ nu,
323
+ L,
324
+ dt,
325
+ nonlinear_order=4,
326
+ dealias="three_halves",
327
+ )
328
+ ```
329
+
290
330
  Run the unified reference-validation table:
291
331
 
292
332
  ```bash
@@ -309,6 +349,8 @@ Primary public imports:
309
349
 
310
350
  - `from optixde.solvers import poisson2d_solve, diffusion2d_solve, wave2d_solve`
311
351
  - `from optixde.solvers import burgers1d_solve, navier_stokes2d_vorticity_solve`
352
+ - `from optixde.solvers import navier_stokes2d_brinkman_fringe_step`
353
+ - `from optixde.solvers import cylinder_re200_step` (compatibility alias)
312
354
  - `from optixde.solvers import schrodinger2d_solve, allen_cahn2d_solve`
313
355
 
314
356
  PDE-specific modules are also kept as stable compatibility namespaces:
@@ -321,6 +363,7 @@ PDE-specific modules are also kept as stable compatibility namespaces:
321
363
  - `optixde.solvers.allen_cahn`
322
364
  - `optixde.solvers.burgers`
323
365
  - `optixde.solvers.navier_stokes`
366
+ - `optixde.solvers.cylinder_re200`
324
367
  - `optixde.solvers.schrodinger`
325
368
  - `optixde.fft_backend`
326
369
  - `optixde.geometry`
@@ -365,27 +408,12 @@ from `CITATION.cff`.
365
408
  author = {Yang, Yang},
366
409
  year = {2026},
367
410
  url = {https://github.com/yangyLab/OptiXDE},
368
- version = {0.2.2},
411
+ version = {0.2.4},
369
412
  }
370
413
  ```
371
414
 
372
415
  Release notes are tracked in `CHANGELOG.md`.
373
416
 
374
- ### Benchmark References
375
-
376
- Several examples reuse published benchmark problem definitions for independent
377
- OptiXDE implementations and comparisons:
378
-
379
- - M. Raissi, P. Perdikaris, and G. E. Karniadakis, “Physics-informed neural
380
- networks: A deep learning framework for solving forward and inverse problems
381
- involving nonlinear partial differential equations,” *Journal of
382
- Computational Physics*, 378, 686–707 (2019),
383
- [doi:10.1016/j.jcp.2018.10.045](https://doi.org/10.1016/j.jcp.2018.10.045).
384
- - L. Lu, X. Meng, Z. Mao, and G. E. Karniadakis, “DeepXDE: A deep learning
385
- library for solving differential equations,” *SIAM Review*, 63(1), 208–228
386
- (2021),
387
- [doi:10.1137/19M1274067](https://doi.org/10.1137/19M1274067).
388
-
389
417
  ## Copyright, License, and Disclaimer
390
418
 
391
419
  Copyright (c) 2025-2026 Yang Yang
@@ -0,0 +1,62 @@
1
+ # SPDX-FileCopyrightText: 2025-2026 Yang Yang <yangyhhu@foxmail.com>
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Compile scalar constant-coefficient periodic PDEs to spectral solvers."""
5
+
6
+ from .compiler import CompiledLinearPDE, analyze_equation, compile_pde
7
+ from .errors import PDECompileError, PDEError, PDESolveError, UnsupportedPDEError
8
+ from .ir import IR_VERSION, LinearPDEIR, LinearTerm, SourceTerm
9
+ from .solver import solve_pde, solve_steady, solve_transient
10
+ from .symbolic import (
11
+ Add,
12
+ Constant,
13
+ Derivative,
14
+ Equation,
15
+ Expr,
16
+ Field,
17
+ Mul,
18
+ Source,
19
+ TimeDerivative,
20
+ biharmonic,
21
+ derivative,
22
+ dt,
23
+ dx,
24
+ dxx,
25
+ dy,
26
+ dyy,
27
+ laplacian,
28
+ )
29
+
30
+ __all__ = [
31
+ "Expr",
32
+ "Constant",
33
+ "Field",
34
+ "Source",
35
+ "Add",
36
+ "Mul",
37
+ "Derivative",
38
+ "TimeDerivative",
39
+ "Equation",
40
+ "derivative",
41
+ "dx",
42
+ "dy",
43
+ "dxx",
44
+ "dyy",
45
+ "laplacian",
46
+ "biharmonic",
47
+ "dt",
48
+ "IR_VERSION",
49
+ "LinearTerm",
50
+ "SourceTerm",
51
+ "LinearPDEIR",
52
+ "CompiledLinearPDE",
53
+ "analyze_equation",
54
+ "compile_pde",
55
+ "solve_steady",
56
+ "solve_transient",
57
+ "solve_pde",
58
+ "PDEError",
59
+ "PDECompileError",
60
+ "UnsupportedPDEError",
61
+ "PDESolveError",
62
+ ]
@@ -0,0 +1,309 @@
1
+ # SPDX-FileCopyrightText: 2025-2026 Yang Yang <yangyhhu@foxmail.com>
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Compiler from the custom PDE expression tree to a periodic spectral plan."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+
10
+ from optixde.fft_backend import NumpyBackend
11
+
12
+ from .errors import PDECompileError, UnsupportedPDEError
13
+ from .ir import IR_VERSION, LinearPDEIR, LinearTerm, SourceTerm
14
+ from .symbolic import (
15
+ Add,
16
+ Constant,
17
+ Derivative,
18
+ Equation,
19
+ Field,
20
+ Mul,
21
+ Source,
22
+ TimeDerivative,
23
+ )
24
+
25
+
26
+ @dataclass
27
+ class _AnalysisState:
28
+ fields: set
29
+ time_coefficient: complex
30
+ linear_terms: list
31
+ source_terms: list
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class CompiledLinearPDE:
36
+ """Grid/backend-bound execution plan produced from :class:`LinearPDEIR`."""
37
+
38
+ ir: LinearPDEIR
39
+ shape: tuple
40
+ domain: tuple
41
+ backend: object
42
+ grids: object
43
+ spatial_symbol: object
44
+ evolution_symbol: object
45
+ zero_mode_policy: str
46
+ singular_tolerance: float
47
+ real_output: bool
48
+ specialized_hint: object
49
+
50
+ @property
51
+ def problem_type(self):
52
+ return self.ir.problem_type
53
+
54
+ @property
55
+ def report(self):
56
+ return {
57
+ "ir_version": self.ir.version,
58
+ "field": self.ir.field_name,
59
+ "problem_type": self.ir.problem_type,
60
+ "boundary": self.ir.boundary,
61
+ "shape": self.shape,
62
+ "domain": self.domain,
63
+ "max_spatial_order": self.ir.max_spatial_order,
64
+ "linear_term_count": len(self.ir.linear_terms),
65
+ "source_names": tuple(
66
+ term.name for term in self.ir.source_terms if term.name is not None
67
+ ),
68
+ "specialized_hint": self.specialized_hint,
69
+ "backend": self.backend.name,
70
+ "device": str(self.backend.device),
71
+ }
72
+
73
+
74
+ def _constant_value(expression):
75
+ if isinstance(expression, Constant):
76
+ return expression.value
77
+ if isinstance(expression, Mul):
78
+ left = _constant_value(expression.left)
79
+ right = _constant_value(expression.right)
80
+ if left is not None and right is not None:
81
+ return left * right
82
+ return None
83
+
84
+
85
+ def _collect(expression, state, *, scale=1.0, derivative_orders=(0, 0)):
86
+ if isinstance(expression, Add):
87
+ for term in expression.terms:
88
+ _collect(term, state, scale=scale, derivative_orders=derivative_orders)
89
+ return
90
+
91
+ if isinstance(expression, Mul):
92
+ left_constant = _constant_value(expression.left)
93
+ right_constant = _constant_value(expression.right)
94
+ if left_constant is not None:
95
+ _collect(
96
+ expression.right,
97
+ state,
98
+ scale=scale * left_constant,
99
+ derivative_orders=derivative_orders,
100
+ )
101
+ return
102
+ if right_constant is not None:
103
+ _collect(
104
+ expression.left,
105
+ state,
106
+ scale=scale * right_constant,
107
+ derivative_orders=derivative_orders,
108
+ )
109
+ return
110
+ raise UnsupportedPDEError(
111
+ "Products between non-constant expressions are nonlinear or "
112
+ "variable-coefficient terms and are not supported in linear PDE v1."
113
+ )
114
+
115
+ if isinstance(expression, Derivative):
116
+ combined = tuple(
117
+ int(old) + int(new)
118
+ for old, new in zip(derivative_orders, expression.orders)
119
+ )
120
+ _collect(expression.expression, state, scale=scale, derivative_orders=combined)
121
+ return
122
+
123
+ if isinstance(expression, TimeDerivative):
124
+ if derivative_orders != (0, 0) or not isinstance(expression.expression, Field):
125
+ raise UnsupportedPDEError(
126
+ "Only a first time derivative of the unknown field is supported."
127
+ )
128
+ state.fields.add(expression.expression.name)
129
+ state.time_coefficient += scale
130
+ return
131
+
132
+ if isinstance(expression, Field):
133
+ state.fields.add(expression.name)
134
+ state.linear_terms.append(LinearTerm(scale, derivative_orders))
135
+ return
136
+
137
+ if isinstance(expression, Source):
138
+ if derivative_orders != (0, 0):
139
+ raise UnsupportedPDEError(
140
+ "Spatial derivatives of prescribed sources are not supported in v1."
141
+ )
142
+ state.source_terms.append(SourceTerm(scale, expression.name))
143
+ return
144
+
145
+ if isinstance(expression, Constant):
146
+ if derivative_orders != (0, 0):
147
+ return
148
+ state.source_terms.append(SourceTerm(scale * expression.value, None))
149
+ return
150
+
151
+ raise PDECompileError(f"Unknown expression node: {type(expression).__name__}.")
152
+
153
+
154
+ def analyze_equation(equation):
155
+ """Normalize ``lhs = rhs`` into a backend-independent linear PDE IR."""
156
+ if not isinstance(equation, Equation):
157
+ raise TypeError("compile_pde expects an Equation object.")
158
+ state = _AnalysisState(set(), 0.0, [], [])
159
+ _collect(equation.lhs, state)
160
+ _collect(equation.rhs, state, scale=-1.0)
161
+
162
+ if len(state.fields) != 1:
163
+ raise UnsupportedPDEError(
164
+ "Linear PDE v1 requires exactly one scalar unknown field; "
165
+ f"found {sorted(state.fields)!r}."
166
+ )
167
+ if not state.linear_terms and state.time_coefficient == 0:
168
+ raise PDECompileError("The equation contains no spatial or reaction operator.")
169
+ max_order = max(
170
+ (sum(term.derivative_orders) for term in state.linear_terms), default=0
171
+ )
172
+ if max_order > 4:
173
+ raise UnsupportedPDEError("Linear PDE v1 supports spatial derivatives up to order four.")
174
+
175
+ transient = state.time_coefficient != 0
176
+ return LinearPDEIR(
177
+ version=IR_VERSION,
178
+ field_name=next(iter(state.fields)),
179
+ problem_type="transient" if transient else "steady",
180
+ time_coefficient=state.time_coefficient,
181
+ linear_terms=tuple(state.linear_terms),
182
+ source_terms=tuple(state.source_terms),
183
+ )
184
+
185
+
186
+ def _combine_terms(terms):
187
+ combined = {}
188
+ for term in terms:
189
+ combined[term.derivative_orders] = (
190
+ combined.get(term.derivative_orders, 0.0) + term.coefficient
191
+ )
192
+ return {orders: value for orders, value in combined.items() if value != 0}
193
+
194
+
195
+ def _specialized_hint(ir):
196
+ terms = _combine_terms(ir.linear_terms)
197
+ isotropic_second = (
198
+ (2, 0) in terms
199
+ and (0, 2) in terms
200
+ and terms[(2, 0)] == terms[(0, 2)]
201
+ )
202
+ spatial_second = set(terms).issubset({(0, 0), (2, 0), (0, 2)})
203
+ if ir.problem_type == "steady" and spatial_second and isotropic_second:
204
+ if set(terms) == {(2, 0), (0, 2)}:
205
+ return "poisson2d_solve"
206
+ if set(terms) == {(0, 0), (2, 0), (0, 2)}:
207
+ return "helmholtz2d_solve"
208
+ if (
209
+ ir.problem_type == "transient"
210
+ and set(terms) == {(2, 0), (0, 2)}
211
+ and isotropic_second
212
+ ):
213
+ return "diffusion2d_solve"
214
+ if (
215
+ ir.problem_type == "steady"
216
+ and set(terms) == {(4, 0), (2, 2), (0, 4)}
217
+ and terms[(4, 0)] == terms[(0, 4)]
218
+ and terms[(2, 2)] == 2.0 * terms[(4, 0)]
219
+ ):
220
+ return "generic_biharmonic"
221
+ return None
222
+
223
+
224
+ def _build_symbol(ir, grids):
225
+ symbol = 0j * grids.K2
226
+ for term in ir.linear_terms:
227
+ order_x, order_y = term.derivative_orders
228
+ contribution = term.coefficient
229
+ if order_x:
230
+ contribution = contribution * (1j * grids.KX) ** order_x
231
+ if order_y:
232
+ contribution = contribution * (1j * grids.KY) ** order_y
233
+ symbol = symbol + contribution
234
+ return symbol
235
+
236
+
237
+ def _make_isolated_backend(backend_name, backend_kwargs):
238
+ """Create a compiler-owned backend without touching legacy shared caches."""
239
+ name = str(backend_name or "numpy").lower()
240
+ if name in {"numpy", "np"}:
241
+ if backend_kwargs:
242
+ unexpected = ", ".join(sorted(backend_kwargs))
243
+ raise TypeError(f"NumPy backend does not accept: {unexpected}.")
244
+ return NumpyBackend()
245
+ if name in {"torch", "pytorch"}:
246
+ from optixde.fft_backend import TorchBackend
247
+
248
+ return TorchBackend(**backend_kwargs)
249
+ if name in {"cupy", "cp", "cufft"}:
250
+ from optixde.fft_backend import CuPyBackend
251
+
252
+ return CuPyBackend(**backend_kwargs)
253
+ raise ValueError(f"Unknown backend: {backend_name!r}.")
254
+
255
+
256
+ def compile_pde(
257
+ equation,
258
+ *,
259
+ shape,
260
+ domain,
261
+ boundary="periodic",
262
+ backend=None,
263
+ backend_name="numpy",
264
+ zero_mode_policy="project",
265
+ singular_tolerance=1.0e-12,
266
+ real_output=True,
267
+ **backend_kwargs,
268
+ ):
269
+ """Compile a scalar constant-coefficient periodic PDE into a spectral plan."""
270
+ if str(boundary).lower() != "periodic":
271
+ raise UnsupportedPDEError("Linear PDE v1 supports periodic boundaries only.")
272
+ shape = tuple(int(value) for value in shape)
273
+ domain = tuple(float(value) for value in domain)
274
+ if len(shape) != 2 or any(value < 2 for value in shape):
275
+ raise ValueError("shape must be a two-dimensional (Ny, Nx) tuple.")
276
+ if len(domain) != 2 or any(value <= 0.0 for value in domain):
277
+ raise ValueError("domain must contain positive lengths (Lx, Ly).")
278
+ if zero_mode_policy not in {"project", "check", "raise"}:
279
+ raise ValueError("zero_mode_policy must be 'project', 'check', or 'raise'.")
280
+ if singular_tolerance <= 0.0:
281
+ raise ValueError("singular_tolerance must be positive.")
282
+
283
+ ir = analyze_equation(equation)
284
+ selected_backend = backend or _make_isolated_backend(backend_name, backend_kwargs)
285
+ ny, nx = shape
286
+ lx, ly = domain
287
+ grids = selected_backend.make_freq_grids(nx, ny, lx, ly)
288
+ spatial_symbol = _build_symbol(ir, grids)
289
+ evolution_symbol = (
290
+ -spatial_symbol / ir.time_coefficient
291
+ if ir.problem_type == "transient"
292
+ else None
293
+ )
294
+ return CompiledLinearPDE(
295
+ ir=ir,
296
+ shape=shape,
297
+ domain=domain,
298
+ backend=selected_backend,
299
+ grids=grids,
300
+ spatial_symbol=spatial_symbol,
301
+ evolution_symbol=evolution_symbol,
302
+ zero_mode_policy=zero_mode_policy,
303
+ singular_tolerance=float(singular_tolerance),
304
+ real_output=bool(real_output),
305
+ specialized_hint=_specialized_hint(ir),
306
+ )
307
+
308
+
309
+ __all__ = ["CompiledLinearPDE", "analyze_equation", "compile_pde"]