algo2code 0.2.1__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.
- algo2code/__init__.py +72 -0
- algo2code/algo_parser.py +458 -0
- algo2code/ast_nodes.py +171 -0
- algo2code/backends/__init__.py +1 -0
- algo2code/backends/c_petsc_codegen.py +1 -0
- algo2code/backends/numpy_codegen.py +1 -0
- algo2code/backends/taichi_codegen.py +859 -0
- algo2code/errors.py +27 -0
- algo2code/expr_parser.py +598 -0
- algo2code/library/.gitkeep +0 -0
- algo2code/library/__init__.py +19 -0
- algo2code/library/pcg.py +127 -0
- algo2code/library/radial_return_j2.py +90 -0
- algo2code/library/radial_return_j2_kinematic.py +89 -0
- algo2code/library/radial_return_j2_mixed.py +91 -0
- algo2code/type_inference.py +219 -0
- algo2code-0.2.1.dist-info/METADATA +30 -0
- algo2code-0.2.1.dist-info/RECORD +20 -0
- algo2code-0.2.1.dist-info/WHEEL +4 -0
- algo2code-0.2.1.dist-info/licenses/LICENSE +21 -0
algo2code/library/pcg.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
r"""Canonical Preconditioned Conjugate Gradient (PCG) algorithm in algpseudocode.
|
|
2
|
+
|
|
3
|
+
This module is the **algo2code interface hook** consumed by
|
|
4
|
+
``mechdsl-core``'s ``Algo2CodePCGSolver`` adapter (Plan recovery task P6-1).
|
|
5
|
+
It exposes:
|
|
6
|
+
|
|
7
|
+
* :data:`PCG_ALGORITHM_LATEX` — the verbatim LaTeX source of the canonical
|
|
8
|
+
PCG algorithm, mirrored from
|
|
9
|
+
``dev/tasks/recovery_plan_latex_contract/json/P6-1.json`` field
|
|
10
|
+
``pcg_algorithm_latex.latex``.
|
|
11
|
+
* :func:`get_pcg_algorithm_latex` — accessor returning that text.
|
|
12
|
+
|
|
13
|
+
Runtime invariant
|
|
14
|
+
-----------------
|
|
15
|
+
|
|
16
|
+
This module imports **only stdlib**. ``algo2code`` is the runtime-free
|
|
17
|
+
sibling package of ``mechdsl-core`` (see ``.claude/CLAUDE.md`` /
|
|
18
|
+
``dev/design_docs/11-ALGO2CODE.md``); nothing here imports — directly or
|
|
19
|
+
transitively — ``mechdsl``. Consumers that want a runnable implementation
|
|
20
|
+
own the translation step.
|
|
21
|
+
|
|
22
|
+
Transpilation status (issue #307, updated)
|
|
23
|
+
------------------------------------------
|
|
24
|
+
|
|
25
|
+
This LaTeX **now transpiles** via :func:`algo2code.transpile`. The original
|
|
26
|
+
deferral — multi-letter scratch identifiers such as ``pq`` tokenising as
|
|
27
|
+
``p * q`` — was resolved by the tokenizer's multi-character identifier rule
|
|
28
|
+
(``expr_parser.TOKEN_PATTERNS``), and the compound updates lower correctly via
|
|
29
|
+
the SSA vector-lowering pass (issue #307 F1/F2). The generated ``pcg`` driver is
|
|
30
|
+
runnable Taichi.
|
|
31
|
+
|
|
32
|
+
``mechdsl-core``'s ``Algo2CodePCGSolver`` is the hand-written, line-by-line
|
|
33
|
+
translation. A numeric comparison (hand-written vs ``transpile`` output, run under
|
|
34
|
+
Taichi) now shows them **bit-identical on every path** — both the converged path
|
|
35
|
+
and the max-iteration-exhausted path — after the for-loop range lowering was
|
|
36
|
+
fixed to emit the inclusive ``range(start, end + 1)`` (issue #307; the generated
|
|
37
|
+
``\For{$k = 1, ..., \text{maxiter}$}`` previously did one fewer iteration). The
|
|
38
|
+
parity is guarded by
|
|
39
|
+
``...recovery_plan_latex_contract/test_pcg_transpiler_parity.py``.
|
|
40
|
+
|
|
41
|
+
The hand-translation is **retained** despite the parity, because the generated
|
|
42
|
+
code and the consumer have different runtime models: ``transpile`` emits **Taichi**
|
|
43
|
+
operating on a **dense matrix field** ``A``, whereas the Newton seam
|
|
44
|
+
(``mechdsl.solver.newton``) is **matrix-free numpy** — it passes the tangent as a
|
|
45
|
+
matvec callback and never forms ``A``. Swapping the generated Taichi code in would
|
|
46
|
+
break that matrix-free contract. A literal code replacement would require a numpy
|
|
47
|
+
backend for algo2code that emits a matrix-free PCG matching ``LinearSolverInterface``
|
|
48
|
+
(the numpy backend is currently a stub). Until then the adapter stays, but it is
|
|
49
|
+
no longer a maintenance hazard: the parity test mechanically proves it stays
|
|
50
|
+
faithful to this canonical LaTeX. Behaviour is also pinned by
|
|
51
|
+
``...recovery_plan_latex_contract/test_p6_1.py``.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
from __future__ import annotations
|
|
55
|
+
|
|
56
|
+
# ── Canonical algorithm source ───────────────────────────────────────────────
|
|
57
|
+
PCG_ALGORITHM_LATEX: str = r"""% algorithm pcg
|
|
58
|
+
% backend taichi
|
|
59
|
+
% args A:matrix, b:vector, x:vector, apply_M_inv:callable, tol:scalar, maxiter:scalar
|
|
60
|
+
|
|
61
|
+
% type r vector
|
|
62
|
+
% type z vector
|
|
63
|
+
% type p vector
|
|
64
|
+
% type q vector
|
|
65
|
+
% type rho scalar
|
|
66
|
+
% type rho_new scalar
|
|
67
|
+
% type alpha scalar
|
|
68
|
+
% type beta scalar
|
|
69
|
+
% type pq scalar
|
|
70
|
+
% type r0_norm scalar
|
|
71
|
+
% type r_norm scalar
|
|
72
|
+
|
|
73
|
+
\begin{algorithmic}
|
|
74
|
+
\State $r = b - A \cdot x$ % vector
|
|
75
|
+
\State $r_0 = \lVert r \rVert_2$ % scalar
|
|
76
|
+
\If{$r_0 = 0$}
|
|
77
|
+
\Return $x, 0, 0$
|
|
78
|
+
\EndIf
|
|
79
|
+
\State $z = \text{apply\_M\_inv}(r)$ % vector
|
|
80
|
+
\State $p = z$ % vector
|
|
81
|
+
\State $\rho = r^\top z$ % scalar
|
|
82
|
+
\For{$k = 1, 2, \ldots, \text{maxiter}$}
|
|
83
|
+
\State $q = A \cdot p$ % vector
|
|
84
|
+
\State $pq = p^\top q$ % scalar
|
|
85
|
+
\If{$|pq| < 10^{-300}$}
|
|
86
|
+
\State \textbf{break}
|
|
87
|
+
\EndIf
|
|
88
|
+
\State $\alpha = \frac{\rho}{pq}$ % scalar
|
|
89
|
+
\State $x = x + \alpha \, p$ % vector
|
|
90
|
+
\State $r = r - \alpha \, q$ % vector
|
|
91
|
+
\State $r_n = \lVert r \rVert_2$ % scalar
|
|
92
|
+
\If{$r_n < \text{tol} \cdot r_0$}
|
|
93
|
+
\Return $x, k, r_n$
|
|
94
|
+
\EndIf
|
|
95
|
+
\State $z = \text{apply\_M\_inv}(r)$ % vector
|
|
96
|
+
\State $\rho_{\text{new}} = r^\top z$ % scalar
|
|
97
|
+
\State $\beta = \frac{\rho_{\text{new}}}{\rho}$ % scalar
|
|
98
|
+
\State $p = z + \beta \, p$ % vector
|
|
99
|
+
\State $\rho = \rho_{\text{new}}$ % scalar
|
|
100
|
+
\EndFor
|
|
101
|
+
\Return $x, \text{maxiter}, \lVert r \rVert_2$
|
|
102
|
+
\end{algorithmic}"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_pcg_algorithm_latex() -> str:
|
|
106
|
+
"""Return the canonical PCG algpseudocode LaTeX source.
|
|
107
|
+
|
|
108
|
+
Consumers (e.g. ``mechdsl.solver.import_adapter.Algo2CodePCGSolver``)
|
|
109
|
+
use the returned string both as the specification artifact for their
|
|
110
|
+
runtime translation and as input to ``algo2code.transpile`` once the
|
|
111
|
+
parser supports the full surface (see *Parser deferral note* in this
|
|
112
|
+
module's docstring).
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
str
|
|
117
|
+
Verbatim LaTeX source of the canonical PCG algorithm, including
|
|
118
|
+
the leading ``% algorithm`` / ``% backend`` / ``% args`` / ``% type``
|
|
119
|
+
directive comments.
|
|
120
|
+
"""
|
|
121
|
+
return PCG_ALGORITHM_LATEX
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
__all__ = [
|
|
125
|
+
"PCG_ALGORITHM_LATEX",
|
|
126
|
+
"get_pcg_algorithm_latex",
|
|
127
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
r"""Canonical J2 radial-return algorithm (algo2code interface hook).
|
|
2
|
+
|
|
3
|
+
post_recovery_plan Phase 5 (P5-1, P5-3). Mirrors the
|
|
4
|
+
``algo2code.library.pcg`` pattern but goes one step further:
|
|
5
|
+
|
|
6
|
+
- :data:`RADIAL_RETURN_J2_LATEX` exposes the verbatim algpseudocode.
|
|
7
|
+
- :func:`get_radial_return_j2_latex` returns it (accessor symmetry
|
|
8
|
+
with PCG).
|
|
9
|
+
- :func:`transpile_radial_return_j2` runs ``algo2code.transpile`` on
|
|
10
|
+
the source and returns the emitted Python module text. Phase 5's
|
|
11
|
+
parser fixes (multi-letter scratch identifiers; binary ``/`` in
|
|
12
|
+
assignment LHS) and codegen fix (``n = b.shape[0]`` regression for
|
|
13
|
+
scalar-only algorithms) make this work end-to-end.
|
|
14
|
+
|
|
15
|
+
The mechdsl-core wrapper at ``mechdsl.lib.plasticity`` execs the
|
|
16
|
+
transpiled module text into a namespace at import time and consumes
|
|
17
|
+
the resulting ``radial_return_j2`` callable inside its dispatcher.
|
|
18
|
+
|
|
19
|
+
Runtime invariant
|
|
20
|
+
-----------------
|
|
21
|
+
|
|
22
|
+
This module imports **only stdlib** (and ``algo2code`` siblings). The
|
|
23
|
+
runtime-free contract holds.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_canonical_source() -> str:
|
|
32
|
+
"""Read the canonical algpseudocode source from
|
|
33
|
+
``dev/algorithms/radial_return_j2.tex``.
|
|
34
|
+
|
|
35
|
+
Walks up from this module to the repo root, then reads the
|
|
36
|
+
file. Single source of truth — the LaTeX file is authoritative.
|
|
37
|
+
"""
|
|
38
|
+
here = Path(__file__).resolve()
|
|
39
|
+
for parent in here.parents:
|
|
40
|
+
candidate = parent / "dev" / "algorithms" / "radial_return_j2.tex"
|
|
41
|
+
if candidate.is_file():
|
|
42
|
+
return candidate.read_text(encoding="utf-8")
|
|
43
|
+
raise FileNotFoundError(
|
|
44
|
+
"post_recovery_plan Phase 5 (P5-1): canonical algorithm source "
|
|
45
|
+
"not found. Expected at <repo>/dev/algorithms/radial_return_j2.tex."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
RADIAL_RETURN_J2_LATEX: str = _load_canonical_source()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_radial_return_j2_latex() -> str:
|
|
53
|
+
"""Return the canonical J2 radial-return algpseudocode LaTeX source."""
|
|
54
|
+
return RADIAL_RETURN_J2_LATEX
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def transpile_radial_return_j2(backend: str = "taichi") -> str:
|
|
58
|
+
"""Transpile the canonical algpseudocode to ``backend`` and return
|
|
59
|
+
the emitted Python module source.
|
|
60
|
+
|
|
61
|
+
post_recovery_plan Phase 5 lifted the deferral that previously
|
|
62
|
+
blocked this round-trip: the algo2code expr_parser now handles
|
|
63
|
+
multi-letter scratch identifiers (``sy``, ``Hp``, ``ap``) and
|
|
64
|
+
binary ``/`` in assignment LHS contexts, and the Taichi codegen
|
|
65
|
+
no longer emits ``n = b.shape[0]`` for scalar-only algorithms.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
backend:
|
|
70
|
+
Target backend name. ``"taichi"`` is the only one consumed by
|
|
71
|
+
``mechdsl-core`` today.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
str
|
|
76
|
+
Transpiled Python module source. Pass to ``exec`` with a fresh
|
|
77
|
+
namespace dict to load the ``radial_return_j2`` callable.
|
|
78
|
+
"""
|
|
79
|
+
# Local import keeps the runtime-free invariant — algo2code itself
|
|
80
|
+
# has no third-party imports beyond stdlib.
|
|
81
|
+
from algo2code import transpile
|
|
82
|
+
|
|
83
|
+
return transpile(RADIAL_RETURN_J2_LATEX, backend=backend)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = [
|
|
87
|
+
"RADIAL_RETURN_J2_LATEX",
|
|
88
|
+
"get_radial_return_j2_latex",
|
|
89
|
+
"transpile_radial_return_j2",
|
|
90
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
r"""J2 kinematic linear-hardening radial-return (algo2code interface hook).
|
|
2
|
+
|
|
3
|
+
constitutive_latex Phase 6 (P6-2). Mirrors
|
|
4
|
+
:mod:`algo2code.library.radial_return_j2` (the isotropic power-law
|
|
5
|
+
variant) for the linear-kinematic (Prager) hardening model:
|
|
6
|
+
|
|
7
|
+
- :data:`RADIAL_RETURN_J2_KINEMATIC_LATEX` exposes the verbatim
|
|
8
|
+
algpseudocode.
|
|
9
|
+
- :func:`get_radial_return_j2_kinematic_latex` returns it.
|
|
10
|
+
- :func:`transpile_radial_return_j2_kinematic` runs
|
|
11
|
+
``algo2code.transpile`` on the source and returns the emitted module
|
|
12
|
+
text.
|
|
13
|
+
|
|
14
|
+
The scalar source owns only the plastic-multiplier solve. The yield
|
|
15
|
+
surface for kinematic hardening translates rather than expands, so the
|
|
16
|
+
scalar loop operates on the trial RELATIVE-stress equivalent
|
|
17
|
+
``xi_eq`` (von Mises of ``dev(S) - beta``) with a constant yield radius
|
|
18
|
+
``sigy0``. The closed-form linear consistency condition
|
|
19
|
+
``dl = (xi_eq - sigy0) / (3*mu + H_kin)`` is authored as the same
|
|
20
|
+
fixed-iteration loop structure as the isotropic variant for
|
|
21
|
+
transpile-pattern consistency.
|
|
22
|
+
|
|
23
|
+
Runtime invariant
|
|
24
|
+
-----------------
|
|
25
|
+
|
|
26
|
+
This module imports **only stdlib** (and ``algo2code`` siblings). The
|
|
27
|
+
runtime-free contract holds.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_canonical_source() -> str:
|
|
36
|
+
"""Read the canonical algpseudocode source from
|
|
37
|
+
``dev/algorithms/radial_return_j2_kinematic.tex``.
|
|
38
|
+
|
|
39
|
+
Walks up from this module to the repo root, then reads the file.
|
|
40
|
+
Single source of truth — the LaTeX file is authoritative.
|
|
41
|
+
"""
|
|
42
|
+
here = Path(__file__).resolve()
|
|
43
|
+
for parent in here.parents:
|
|
44
|
+
candidate = parent / "dev" / "algorithms" / "radial_return_j2_kinematic.tex"
|
|
45
|
+
if candidate.is_file():
|
|
46
|
+
return candidate.read_text(encoding="utf-8")
|
|
47
|
+
raise FileNotFoundError(
|
|
48
|
+
"constitutive_latex Phase 6 (P6-2): canonical kinematic algorithm "
|
|
49
|
+
"source not found. Expected at "
|
|
50
|
+
"<repo>/dev/algorithms/radial_return_j2_kinematic.tex."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
RADIAL_RETURN_J2_KINEMATIC_LATEX: str = _load_canonical_source()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_radial_return_j2_kinematic_latex() -> str:
|
|
58
|
+
"""Return the canonical J2 kinematic radial-return algpseudocode LaTeX."""
|
|
59
|
+
return RADIAL_RETURN_J2_KINEMATIC_LATEX
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def transpile_radial_return_j2_kinematic(backend: str = "taichi") -> str:
|
|
63
|
+
"""Transpile the canonical kinematic algpseudocode to ``backend`` and
|
|
64
|
+
return the emitted Python module source.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
backend:
|
|
69
|
+
Target backend name. ``"taichi"`` is the only one consumed by
|
|
70
|
+
``mechdsl-core`` today.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
str
|
|
75
|
+
Transpiled Python module source. Pass to ``exec`` with a fresh
|
|
76
|
+
namespace dict to load the ``radial_return_j2_kinematic`` callable.
|
|
77
|
+
"""
|
|
78
|
+
# Local import keeps the runtime-free invariant — algo2code itself
|
|
79
|
+
# has no third-party imports beyond stdlib.
|
|
80
|
+
from algo2code import transpile
|
|
81
|
+
|
|
82
|
+
return transpile(RADIAL_RETURN_J2_KINEMATIC_LATEX, backend=backend)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"RADIAL_RETURN_J2_KINEMATIC_LATEX",
|
|
87
|
+
"get_radial_return_j2_kinematic_latex",
|
|
88
|
+
"transpile_radial_return_j2_kinematic",
|
|
89
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
r"""J2 mixed-hardening radial-return (algo2code interface hook).
|
|
2
|
+
|
|
3
|
+
constitutive_latex Phase 6 (P6-3). Mirrors
|
|
4
|
+
:mod:`algo2code.library.radial_return_j2` (isotropic power-law) and
|
|
5
|
+
:mod:`algo2code.library.radial_return_j2_kinematic` (linear kinematic)
|
|
6
|
+
for the MIXED-hardening model — the yield surface BOTH translates
|
|
7
|
+
(back-stress ``beta``, Prager) AND expands (power-law radius
|
|
8
|
+
``sigma_y(alpha) = sigy0 + K*alpha^n``) simultaneously:
|
|
9
|
+
|
|
10
|
+
- :data:`RADIAL_RETURN_J2_MIXED_LATEX` exposes the verbatim algpseudocode.
|
|
11
|
+
- :func:`get_radial_return_j2_mixed_latex` returns it.
|
|
12
|
+
- :func:`transpile_radial_return_j2_mixed` runs ``algo2code.transpile``
|
|
13
|
+
on the source and returns the emitted module text.
|
|
14
|
+
|
|
15
|
+
The scalar source owns only the plastic-multiplier solve. Yield is on
|
|
16
|
+
the trial RELATIVE-stress equivalent ``xi_eq`` (von Mises of
|
|
17
|
+
``dev(S) - beta``) against the EXPANDING radius ``sigma_y(alpha)``.
|
|
18
|
+
Because the isotropic part is a nonlinear power law in
|
|
19
|
+
``alpha = alpha_old + dl``, the consistency condition is nonlinear in
|
|
20
|
+
``dl`` and is solved by the same scalar NEWTON loop as the isotropic
|
|
21
|
+
variant — but with the kinematic ``(3*mu + H_kin)*dl`` linear term added
|
|
22
|
+
to the residual and the Prager modulus folded into the Newton
|
|
23
|
+
denominator alongside the isotropic slope.
|
|
24
|
+
|
|
25
|
+
Runtime invariant
|
|
26
|
+
-----------------
|
|
27
|
+
|
|
28
|
+
This module imports **only stdlib** (and ``algo2code`` siblings). The
|
|
29
|
+
runtime-free contract holds.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_canonical_source() -> str:
|
|
38
|
+
"""Read the canonical algpseudocode source from
|
|
39
|
+
``dev/algorithms/radial_return_j2_mixed.tex``.
|
|
40
|
+
|
|
41
|
+
Walks up from this module to the repo root, then reads the file.
|
|
42
|
+
Single source of truth — the LaTeX file is authoritative.
|
|
43
|
+
"""
|
|
44
|
+
here = Path(__file__).resolve()
|
|
45
|
+
for parent in here.parents:
|
|
46
|
+
candidate = parent / "dev" / "algorithms" / "radial_return_j2_mixed.tex"
|
|
47
|
+
if candidate.is_file():
|
|
48
|
+
return candidate.read_text(encoding="utf-8")
|
|
49
|
+
raise FileNotFoundError(
|
|
50
|
+
"constitutive_latex Phase 6 (P6-3): canonical mixed algorithm "
|
|
51
|
+
"source not found. Expected at "
|
|
52
|
+
"<repo>/dev/algorithms/radial_return_j2_mixed.tex."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
RADIAL_RETURN_J2_MIXED_LATEX: str = _load_canonical_source()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_radial_return_j2_mixed_latex() -> str:
|
|
60
|
+
"""Return the canonical J2 mixed radial-return algpseudocode LaTeX."""
|
|
61
|
+
return RADIAL_RETURN_J2_MIXED_LATEX
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def transpile_radial_return_j2_mixed(backend: str = "taichi") -> str:
|
|
65
|
+
"""Transpile the canonical mixed algpseudocode to ``backend`` and
|
|
66
|
+
return the emitted Python module source.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
backend:
|
|
71
|
+
Target backend name. ``"taichi"`` is the only one consumed by
|
|
72
|
+
``mechdsl-core`` today.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
Transpiled Python module source. Pass to ``exec`` with a fresh
|
|
78
|
+
namespace dict to load the ``radial_return_j2_mixed`` callable.
|
|
79
|
+
"""
|
|
80
|
+
# Local import keeps the runtime-free invariant — algo2code itself
|
|
81
|
+
# has no third-party imports beyond stdlib.
|
|
82
|
+
from algo2code import transpile
|
|
83
|
+
|
|
84
|
+
return transpile(RADIAL_RETURN_J2_MIXED_LATEX, backend=backend)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"RADIAL_RETURN_J2_MIXED_LATEX",
|
|
89
|
+
"get_radial_return_j2_mixed_latex",
|
|
90
|
+
"transpile_radial_return_j2_mixed",
|
|
91
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type inference for algorithm AST nodes.
|
|
3
|
+
|
|
4
|
+
Given type annotations on variables (from % comments or directives),
|
|
5
|
+
propagate types through expressions to resolve:
|
|
6
|
+
- Which operations are scalar (stay in Python scope)
|
|
7
|
+
- Which operations are vector/matrix (become Taichi kernels)
|
|
8
|
+
- What each binary operator actually means:
|
|
9
|
+
* vector * vector → dot product
|
|
10
|
+
* matrix * vector → matvec
|
|
11
|
+
* scalar * vector → axpy
|
|
12
|
+
* scalar / scalar → division
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from .ast_nodes import (
|
|
18
|
+
Algorithm,
|
|
19
|
+
Assign,
|
|
20
|
+
BinOp,
|
|
21
|
+
Branch,
|
|
22
|
+
Expr,
|
|
23
|
+
ForLoop,
|
|
24
|
+
FuncCall,
|
|
25
|
+
Number,
|
|
26
|
+
Return,
|
|
27
|
+
Stmt,
|
|
28
|
+
UnaryOp,
|
|
29
|
+
Var,
|
|
30
|
+
VarType,
|
|
31
|
+
WhileLoop,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TypeInferrer:
|
|
36
|
+
"""
|
|
37
|
+
Walk the AST and annotate every Expr node with its inferred VarType.
|
|
38
|
+
Also resolves BinOp.op to semantically specific operations:
|
|
39
|
+
'*' with (matrix, vector) → 'matvec'
|
|
40
|
+
'*' with (vector, vector) → 'dot' (only when one is transposed)
|
|
41
|
+
'*' with (scalar, vector) → 'scale'
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, type_annotations: dict[str, VarType]):
|
|
45
|
+
self.types = dict(type_annotations)
|
|
46
|
+
|
|
47
|
+
def infer_algorithm(self, algo: Algorithm):
|
|
48
|
+
"""Run type inference on the full algorithm."""
|
|
49
|
+
for name, vtype in algo.args:
|
|
50
|
+
if vtype != VarType.UNKNOWN:
|
|
51
|
+
self.types[name] = vtype
|
|
52
|
+
|
|
53
|
+
self._infer_block(algo.body)
|
|
54
|
+
|
|
55
|
+
def _infer_block(self, stmts: list[Stmt]):
|
|
56
|
+
for stmt in stmts:
|
|
57
|
+
self._infer_stmt(stmt)
|
|
58
|
+
|
|
59
|
+
def _infer_stmt(self, stmt: Stmt):
|
|
60
|
+
if isinstance(stmt, Assign):
|
|
61
|
+
self._infer_expr(stmt.value)
|
|
62
|
+
var_name = stmt.target.display
|
|
63
|
+
if var_name not in self.types:
|
|
64
|
+
self.types[var_name] = stmt.value.inferred_type
|
|
65
|
+
stmt.target.inferred_type = self.types.get(var_name, VarType.UNKNOWN)
|
|
66
|
+
|
|
67
|
+
elif isinstance(stmt, ForLoop):
|
|
68
|
+
self.types[stmt.var] = VarType.SCALAR
|
|
69
|
+
self._infer_block(stmt.body)
|
|
70
|
+
|
|
71
|
+
elif isinstance(stmt, WhileLoop):
|
|
72
|
+
self._infer_expr(stmt.condition)
|
|
73
|
+
self._infer_block(stmt.body)
|
|
74
|
+
|
|
75
|
+
elif isinstance(stmt, Branch):
|
|
76
|
+
self._infer_expr(stmt.condition)
|
|
77
|
+
self._infer_block(stmt.if_body)
|
|
78
|
+
for cond, body in stmt.elif_branches:
|
|
79
|
+
self._infer_expr(cond)
|
|
80
|
+
self._infer_block(body)
|
|
81
|
+
self._infer_block(stmt.else_body)
|
|
82
|
+
|
|
83
|
+
elif isinstance(stmt, Return):
|
|
84
|
+
for v in stmt.values:
|
|
85
|
+
self._infer_expr(v)
|
|
86
|
+
|
|
87
|
+
def _infer_expr(self, expr: Expr) -> VarType:
|
|
88
|
+
"""Infer and set the type of an expression, returning the type."""
|
|
89
|
+
if isinstance(expr, Number):
|
|
90
|
+
expr.inferred_type = VarType.SCALAR
|
|
91
|
+
return VarType.SCALAR
|
|
92
|
+
|
|
93
|
+
if isinstance(expr, Var):
|
|
94
|
+
name = expr.display
|
|
95
|
+
t = self.types.get(name, VarType.UNKNOWN)
|
|
96
|
+
# Single-letter uppercase → default to matrix if unknown
|
|
97
|
+
if t == VarType.UNKNOWN and len(expr.name) == 1 and expr.name.isupper():
|
|
98
|
+
t = VarType.MATRIX
|
|
99
|
+
expr.inferred_type = t
|
|
100
|
+
return t
|
|
101
|
+
|
|
102
|
+
if isinstance(expr, UnaryOp):
|
|
103
|
+
inner = self._infer_expr(expr.operand)
|
|
104
|
+
if expr.op == "norm":
|
|
105
|
+
expr.inferred_type = VarType.SCALAR
|
|
106
|
+
elif expr.op == "transpose":
|
|
107
|
+
expr.inferred_type = inner # transpose preserves type
|
|
108
|
+
elif expr.op == "neg" or expr.op == "inverse":
|
|
109
|
+
expr.inferred_type = inner
|
|
110
|
+
else:
|
|
111
|
+
expr.inferred_type = inner
|
|
112
|
+
return expr.inferred_type
|
|
113
|
+
|
|
114
|
+
if isinstance(expr, BinOp):
|
|
115
|
+
lt = self._infer_expr(expr.left)
|
|
116
|
+
rt = self._infer_expr(expr.right)
|
|
117
|
+
expr.inferred_type = self._resolve_binop(expr, lt, rt)
|
|
118
|
+
return expr.inferred_type
|
|
119
|
+
|
|
120
|
+
if isinstance(expr, FuncCall):
|
|
121
|
+
for arg in expr.args:
|
|
122
|
+
self._infer_expr(arg)
|
|
123
|
+
self._infer_expr(expr.func)
|
|
124
|
+
if expr.args:
|
|
125
|
+
expr.inferred_type = expr.args[0].inferred_type
|
|
126
|
+
else:
|
|
127
|
+
expr.inferred_type = VarType.UNKNOWN
|
|
128
|
+
# Check if func is an inverse of a callable
|
|
129
|
+
if isinstance(expr.func, UnaryOp) and expr.func.op == "inverse":
|
|
130
|
+
base = expr.func.operand
|
|
131
|
+
if isinstance(base, Var):
|
|
132
|
+
self.types[base.display] = VarType.CALLABLE
|
|
133
|
+
return expr.inferred_type
|
|
134
|
+
|
|
135
|
+
expr.inferred_type = VarType.UNKNOWN
|
|
136
|
+
return VarType.UNKNOWN
|
|
137
|
+
|
|
138
|
+
def _resolve_binop(self, expr: BinOp, lt: VarType, rt: VarType) -> VarType:
|
|
139
|
+
"""Resolve the semantic meaning of a binary operation given operand types."""
|
|
140
|
+
op = expr.op
|
|
141
|
+
|
|
142
|
+
if op in ("+", "-"):
|
|
143
|
+
if lt == VarType.VECTOR or rt == VarType.VECTOR:
|
|
144
|
+
return VarType.VECTOR
|
|
145
|
+
if lt == VarType.MATRIX or rt == VarType.MATRIX:
|
|
146
|
+
return VarType.MATRIX
|
|
147
|
+
return VarType.SCALAR
|
|
148
|
+
|
|
149
|
+
if op == "/":
|
|
150
|
+
return VarType.SCALAR
|
|
151
|
+
|
|
152
|
+
if op == "*":
|
|
153
|
+
return self._resolve_multiply(expr, lt, rt)
|
|
154
|
+
|
|
155
|
+
if op == "pow":
|
|
156
|
+
return lt
|
|
157
|
+
|
|
158
|
+
if op in ("<", ">", "<=", ">=", "==", "!="):
|
|
159
|
+
return VarType.SCALAR
|
|
160
|
+
|
|
161
|
+
return VarType.UNKNOWN
|
|
162
|
+
|
|
163
|
+
def _resolve_multiply(self, expr: BinOp, lt: VarType, rt: VarType) -> VarType:
|
|
164
|
+
"""
|
|
165
|
+
Resolve multiplication semantics:
|
|
166
|
+
transposed_vector * vector → dot product (scalar result)
|
|
167
|
+
matrix * vector → matvec (vector result)
|
|
168
|
+
callable * vector → matvec (vector result, matrix-free seam)
|
|
169
|
+
scalar * vector → scale (vector result)
|
|
170
|
+
scalar * scalar → multiply (scalar result)
|
|
171
|
+
"""
|
|
172
|
+
left_is_transposed = isinstance(expr.left, UnaryOp) and expr.left.op == "transpose"
|
|
173
|
+
|
|
174
|
+
if left_is_transposed and rt == VarType.VECTOR:
|
|
175
|
+
expr.op = "dot"
|
|
176
|
+
return VarType.SCALAR
|
|
177
|
+
|
|
178
|
+
if left_is_transposed and rt == VarType.MATRIX:
|
|
179
|
+
expr.op = "dot"
|
|
180
|
+
return VarType.VECTOR
|
|
181
|
+
|
|
182
|
+
if lt == VarType.MATRIX and rt == VarType.VECTOR:
|
|
183
|
+
expr.op = "matvec"
|
|
184
|
+
return VarType.VECTOR
|
|
185
|
+
|
|
186
|
+
# Matrix-free operator seam (11-ALGO2CODE §8.3): a `callable` operator A
|
|
187
|
+
# applied to a vector — `A \cdot p` — is a matvec whose body is an
|
|
188
|
+
# injected in-place call A(out, p), not a dense stored-matrix multiply.
|
|
189
|
+
# The backend distinguishes the two by the operand's CALLABLE type.
|
|
190
|
+
if lt == VarType.CALLABLE and rt == VarType.VECTOR:
|
|
191
|
+
expr.op = "matvec"
|
|
192
|
+
return VarType.VECTOR
|
|
193
|
+
|
|
194
|
+
if lt == VarType.MATRIX and rt == VarType.MATRIX:
|
|
195
|
+
expr.op = "matmul"
|
|
196
|
+
return VarType.MATRIX
|
|
197
|
+
|
|
198
|
+
if lt == VarType.SCALAR and rt == VarType.VECTOR:
|
|
199
|
+
expr.op = "scale"
|
|
200
|
+
return VarType.VECTOR
|
|
201
|
+
|
|
202
|
+
if lt == VarType.VECTOR and rt == VarType.SCALAR:
|
|
203
|
+
expr.op = "scale"
|
|
204
|
+
return VarType.VECTOR
|
|
205
|
+
|
|
206
|
+
if lt == VarType.SCALAR and rt == VarType.MATRIX:
|
|
207
|
+
expr.op = "scale"
|
|
208
|
+
return VarType.MATRIX
|
|
209
|
+
|
|
210
|
+
# Default: scalar multiply
|
|
211
|
+
return VarType.SCALAR
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def infer_types(algo: Algorithm):
|
|
215
|
+
"""Run type inference on an Algorithm AST, mutating nodes in place."""
|
|
216
|
+
inferrer = TypeInferrer(algo.type_annotations)
|
|
217
|
+
inferrer.infer_algorithm(algo)
|
|
218
|
+
# Write back discovered types
|
|
219
|
+
algo.type_annotations = inferrer.types
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: algo2code
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Transpile LaTeX algorithm boxes (algpseudocode) to executable Taichi/NumPy/C code
|
|
5
|
+
Project-URL: Repository, https://github.com/CEmM2/MechDSL
|
|
6
|
+
Author: Shmuel Osovski
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: algorithm,code-generation,latex,taichi,transpiler
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: <3.14,>=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# algo2code
|
|
19
|
+
|
|
20
|
+
Transpile LaTeX algorithm boxes (`algpseudocode`) to executable code targeting Taichi, NumPy, or C/PETSc.
|
|
21
|
+
|
|
22
|
+
Parses `\begin{algorithmic}...\end{algorithmic}` environments with type-directed code generation.
|
|
23
|
+
Zero runtime dependencies — standard library only.
|
|
24
|
+
|
|
25
|
+
## Documentation
|
|
26
|
+
|
|
27
|
+
- **User docs:** <https://sosovski.group/MechDSL/algo2code/> — introduction, getting
|
|
28
|
+
started, usage, and examples.
|
|
29
|
+
- **Design spec:** `dev/design_docs/11-ALGO2CODE.md` (authoritative source of truth).
|
|
30
|
+
- See the [monorepo root](../../README.md) for the full project overview.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
algo2code/__init__.py,sha256=FWaPHkz13pKOXfuA0ahh_uKXGRe80UGCz_1nUs_hpIc,2061
|
|
2
|
+
algo2code/algo_parser.py,sha256=eC0Dl2X7vPojmViTk-3XCXBpySevBeptgI24vVS7KI4,15880
|
|
3
|
+
algo2code/ast_nodes.py,sha256=p7n-ZPoR7SizrcbgIMUWigWrk8kfifBLYfSchlrlQxo,4561
|
|
4
|
+
algo2code/errors.py,sha256=506gr0O_ufY1eqceHicuP8RcGzwNT1TuCGoquJHoA1Q,1098
|
|
5
|
+
algo2code/expr_parser.py,sha256=R5PQ3ey3DQYTTl-xOJ4KvqKSpUbEvhrs82htSae6W0A,21564
|
|
6
|
+
algo2code/type_inference.py,sha256=Pa3bK-QV_IlMx32jh1xx39lj_-dC4DOC-eO9aU1keF4,7596
|
|
7
|
+
algo2code/backends/__init__.py,sha256=hNwHmY9S56cZsMNlP95kFYMz8qojJKqXl7s1aFcXg84,32
|
|
8
|
+
algo2code/backends/c_petsc_codegen.py,sha256=YlRVHC8vwugD1ZGz0UVGMKzMzhZBlAzLBwkbO90FpXM,33
|
|
9
|
+
algo2code/backends/numpy_codegen.py,sha256=WpOm7GkpD51L_kYkoZiVnv-lwn_5ZwtcveU9porEdWY,31
|
|
10
|
+
algo2code/backends/taichi_codegen.py,sha256=ME7BGQDX8nOOYiQFe7Mw0hDYezGU-KVQ9f0bv-nKKYw,35942
|
|
11
|
+
algo2code/library/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
algo2code/library/__init__.py,sha256=HZf5doUkzAjS2D1It3Jc-9fUIRp04BH4wYteaOVPGqc,686
|
|
13
|
+
algo2code/library/pcg.py,sha256=uFQnr3U5a0L3hz_d_S3g9D4pUu8vFthiN55bOy-FBeA,5639
|
|
14
|
+
algo2code/library/radial_return_j2.py,sha256=qW-3avbzW9WQQytX2fZwg7zwQaWhDMYcJCx_B6m9aXM,3136
|
|
15
|
+
algo2code/library/radial_return_j2_kinematic.py,sha256=MaP0IPmrZSafjzH8KuzdBtC2XbKw8tYK7uaLW3M7LQc,3100
|
|
16
|
+
algo2code/library/radial_return_j2_mixed.py,sha256=q9AKkf6Z7uRtMcKTCFWXFI74xHJi0qiMje8hzGWdPl8,3298
|
|
17
|
+
algo2code-0.2.1.dist-info/METADATA,sha256=EOl2z0f4pzkiX-les0_93qdcL-fZ_fHznqmLfGD9uUI,1215
|
|
18
|
+
algo2code-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
+
algo2code-0.2.1.dist-info/licenses/LICENSE,sha256=09AX3cq_TtJwbbZ00zsqmmGL_p_FINJ4l8asH_rhVhk,1071
|
|
20
|
+
algo2code-0.2.1.dist-info/RECORD,,
|