latex2code 0.2.0__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.
latex2code/printers.py ADDED
@@ -0,0 +1,205 @@
1
+ import sympy as sp
2
+ from sympy.printing.numpy import NumPyPrinter
3
+ from sympy.printing.pycode import PythonCodePrinter
4
+
5
+ from .backends import BACKENDS, Backend
6
+ from .parsing import _StableBeta, _StableSigmoid, _StableSoftplus, _StableSwish
7
+
8
+
9
+ class _PythonPrinter(PythonCodePrinter):
10
+ def _require_math(self) -> None:
11
+ self.module_imports["math"].add("*")
12
+
13
+ def _print__StableSigmoid(self, expr: _StableSigmoid) -> str:
14
+ self._require_math()
15
+ value = self._print(expr.args[0])
16
+ return f"(0.5 * (1 + math.tanh(({value}) / 2)))"
17
+
18
+ def _print__StableSoftplus(self, expr: _StableSoftplus) -> str:
19
+ self._require_math()
20
+ value = self._print(expr.args[0])
21
+ return f"(max(({value}), 0) + math.log1p(math.exp(-abs({value}))))"
22
+
23
+ def _print__StableSwish(self, expr: _StableSwish) -> str:
24
+ value = self._print(expr.args[0])
25
+ sigmoid = self._print(_StableSigmoid(expr.args[0]))
26
+ return f"(({value}) * {sigmoid})"
27
+
28
+ def _print__StableBeta(self, expr: _StableBeta) -> str:
29
+ self._require_math()
30
+ first, second = (self._print(value) for value in expr.args)
31
+ sign_first = (
32
+ f"(1 if ({first}) > 0 else math.copysign(1, math.sin(math.pi * ({first}))))"
33
+ )
34
+ sign_second = f"(1 if ({second}) > 0 else math.copysign(1, math.sin(math.pi * ({second}))))"
35
+ total = f"(({first}) + ({second}))"
36
+ sign_total = (
37
+ f"(1 if {total} > 0 else math.copysign(1, math.sin(math.pi * {total})))"
38
+ )
39
+ log_magnitude = (
40
+ f"(math.lgamma({first}) + math.lgamma({second}) - math.lgamma({total}))"
41
+ )
42
+ return f"(({sign_first}) * ({sign_second}) / ({sign_total}) * math.exp({log_magnitude}))"
43
+
44
+ def _print_Sum(self, expr: sp.Sum) -> str:
45
+ self.module_imports["builtins"].add("sum")
46
+ return super()._print_Sum(expr)
47
+
48
+
49
+ class _FrameworkPrinter(NumPyPrinter):
50
+ def __init__(self, backend: Backend):
51
+ self.backend = backend
52
+ self.backend_spec = BACKENDS[backend]
53
+ if self.backend_spec.module_alias is None:
54
+ raise ValueError(f"Backend {backend!r} does not use an array printer.")
55
+ self._module = self.backend_spec.module_alias
56
+ user_functions = {
57
+ name: f"{self._module}.{target}"
58
+ for name, target in self.backend_spec.function_aliases.items()
59
+ }
60
+ user_functions.update(self.backend_spec.special_functions)
61
+ super().__init__(
62
+ {
63
+ "user_functions": user_functions,
64
+ }
65
+ )
66
+
67
+ def _print_Pi(self, expr: sp.Expr) -> str:
68
+ return f"{self.backend_spec.module_alias}.pi"
69
+
70
+ def _print_Exp1(self, expr: sp.Expr) -> str:
71
+ return f"{self.backend_spec.module_alias}.e"
72
+
73
+ def _print_Abs(self, expr: sp.Expr) -> str:
74
+ value = self._print(expr.args[0])
75
+ return f"{self.backend_spec.module_alias}.abs({value})"
76
+
77
+ def _print_gamma(self, expr: sp.Expr) -> str:
78
+ value = self._print(expr.args[0])
79
+ if self.backend == "torch":
80
+ return (
81
+ f"torch.where(({value}) < 0, "
82
+ f"torch.sign(torch.sin(torch.pi * ({value}))), 1) "
83
+ f"* torch.exp(torch.lgamma({value}))"
84
+ )
85
+ if self.backend == "numpy":
86
+ return f"np.vectorize(math.gamma)({value})"
87
+ return f"jax.scipy.special.gamma({value})"
88
+
89
+ def _print__StableSigmoid(self, expr: _StableSigmoid) -> str:
90
+ value = self._print(expr.args[0])
91
+ if self.backend == "numpy":
92
+ return f"np.exp(-np.logaddexp(0, -({value})))"
93
+ module = "torch" if self.backend == "torch" else "jax.nn"
94
+ return f"{module}.sigmoid({value})"
95
+
96
+ def _print__StableSoftplus(self, expr: _StableSoftplus) -> str:
97
+ value = self._print(expr.args[0])
98
+ if self.backend == "numpy":
99
+ return f"np.logaddexp(0, {value})"
100
+ module = "torch.nn.functional" if self.backend == "torch" else "jax.nn"
101
+ return f"{module}.softplus({value})"
102
+
103
+ def _print__StableSwish(self, expr: _StableSwish) -> str:
104
+ value = self._print(expr.args[0])
105
+ return f"({value} * {self._print(_StableSigmoid(expr.args[0]))})"
106
+
107
+ def _print__StableBeta(self, expr: _StableBeta) -> str:
108
+ first, second = (self._print(value) for value in expr.args)
109
+ total = f"({first} + {second})"
110
+ if self.backend == "numpy":
111
+ log_magnitude = (
112
+ f"np.vectorize(math.lgamma)({first})"
113
+ f" + np.vectorize(math.lgamma)({second})"
114
+ f" - np.vectorize(math.lgamma)({total})"
115
+ )
116
+ sign = (
117
+ f"np.where({first} < 0, np.sign(np.sin(np.pi * {first})), 1)"
118
+ f" * np.where({second} < 0, np.sign(np.sin(np.pi * {second})), 1)"
119
+ f" / np.where({total} < 0, np.sign(np.sin(np.pi * {total})), 1)"
120
+ )
121
+ return f"({sign} * np.exp({log_magnitude}))"
122
+ module = "torch" if self.backend == "torch" else "jnp"
123
+ if self.backend == "torch":
124
+ log_gamma = "torch.lgamma"
125
+
126
+ def sign_gamma(value: str) -> str:
127
+ return (
128
+ f"torch.where(({value}) < 0, "
129
+ f"torch.sign(torch.sin(torch.pi * ({value}))), 1)"
130
+ )
131
+
132
+ else:
133
+ log_gamma = "jax.scipy.special.gammaln"
134
+
135
+ def sign_gamma(value: str) -> str:
136
+ return f"jax.scipy.special.gammasgn({value})"
137
+
138
+ log_magnitude = (
139
+ f"{log_gamma}({first}) + {log_gamma}({second}) - {log_gamma}({total})"
140
+ )
141
+ sign = f"{sign_gamma(first)} * {sign_gamma(second)} / {sign_gamma(total)}"
142
+ return f"({sign} * {module}.exp({log_magnitude}))"
143
+
144
+ def _print_Piecewise(self, expr: sp.Piecewise) -> str:
145
+ module = self.backend_spec.module_alias
146
+ assert module is not None
147
+ default = "float('nan')" if self.backend == "torch" else f"{module}.nan"
148
+ branches = list(expr.args)
149
+ for value, condition in reversed(branches):
150
+ printed_value = self._print(value)
151
+ if condition is sp.true:
152
+ default = printed_value
153
+ continue
154
+ printed_condition = self._print(condition)
155
+ default = f"{module}.where({printed_condition}, {printed_value}, {default})"
156
+ return default
157
+
158
+ def _print_Max(self, expr: sp.Max) -> str:
159
+ return self._print_extreme(expr, "maximum")
160
+
161
+ def _print_Min(self, expr: sp.Min) -> str:
162
+ return self._print_extreme(expr, "minimum")
163
+
164
+ def _print_extreme(self, expr: sp.Expr, operation: str) -> str:
165
+ module = self.backend_spec.module_alias
166
+ assert module is not None
167
+ args = list(expr.args)
168
+ if self.backend == "torch":
169
+ dynamic_args = [arg for arg in args if not arg.is_number]
170
+ if dynamic_args:
171
+ first = dynamic_args.pop(0)
172
+ result = self._print(first)
173
+ args.remove(first)
174
+ for arg in args:
175
+ value = self._print(arg)
176
+ if arg.is_number:
177
+ value = (
178
+ f"torch.as_tensor({value}, dtype={result}.dtype, "
179
+ f"device={result}.device)"
180
+ )
181
+ result = f"torch.{operation}({result}, {value})"
182
+ return result
183
+ result = self._print(args[0])
184
+ for arg in args[1:]:
185
+ result = f"{module}.{operation}({result}, {self._print(arg)})"
186
+ return result
187
+
188
+
189
+ def _expression_tree(expr: sp.Expr | sp.MatrixBase) -> dict[str, object]:
190
+ if isinstance(expr, sp.MatrixBase):
191
+ return {
192
+ "type": type(expr).__name__,
193
+ "shape": list(expr.shape),
194
+ "args": [
195
+ [_expression_tree(value) for value in row] for row in expr.tolist()
196
+ ],
197
+ }
198
+ node: dict[str, object] = {
199
+ "type": getattr(expr.func, "__name__", type(expr).__name__),
200
+ }
201
+ if expr.args:
202
+ node["args"] = [_expression_tree(argument) for argument in expr.args]
203
+ else:
204
+ node["value"] = str(expr)
205
+ return node
latex2code/py.typed ADDED
File without changes
@@ -0,0 +1,481 @@
1
+ Metadata-Version: 2.4
2
+ Name: latex2code
3
+ Version: 0.2.0
4
+ Summary: A lightweight Python library and CLI tool to transpile LaTeX mathematical expressions into executable Python functions.
5
+ Author: Thúlio Brandão
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ThulioASB/latex2code
8
+ Project-URL: Repository, https://github.com/ThulioASB/latex2code
9
+ Project-URL: Issues, https://github.com/ThulioASB/latex2code/issues
10
+ Keywords: latex,sympy,python,transpile,mathematics,numpy
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: sympy>=1.12
23
+ Requires-Dist: antlr4-python3-runtime==4.11.0
24
+ Requires-Dist: numpy>=1.24.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: black>=24.0; extra == "dev"
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
29
+ Requires-Dist: hypothesis>=6.0.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
32
+ Requires-Dist: build>=1.2.0; extra == "dev"
33
+ Provides-Extra: torch
34
+ Requires-Dist: torch>=2.0; extra == "torch"
35
+ Provides-Extra: jax
36
+ Requires-Dist: jax>=0.4; extra == "jax"
37
+ Dynamic: license-file
38
+
39
+ # latex2code 🧮 ➡️ 🐍
40
+
41
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+ [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
44
+
45
+ A lightweight Python library and CLI tool that transpiles a supported subset of LaTeX mathematical expressions into Python functions.
46
+
47
+ Designed for scientific computing, machine learning researchers, and engineers who want to bridge the gap between academic papers and executable code.
48
+
49
+ ---
50
+
51
+ ## Features
52
+
53
+ - 📐 **Common Math:** Translates arithmetic, fractions, powers, roots, limits, binomial coefficients, and common functions such as `\sin`, `\cos`, `\tan`, `\exp`, `\log`, `\operatorname{erf}`, `\max`, and hyperbolic functions.
54
+ - 🔢 **Greek Symbols & Constants:** Common Greek-letter commands become variables; `\pi` and `e` are numeric constants.
55
+ - 🔁 **Matrix & Array Support:** Converts LaTeX matrix environments (`\begin{pmatrix}`, `\begin{bmatrix}`, `\begin{matrix}`) and tabular/array layouts like `\begin{array}{cc} ... \end{array}` into `numpy.ndarray` objects.
56
+ - 🧩 **Piecewise Expressions:** Recognizes `\begin{cases} ... \end{cases}` and emits Python conditional expressions.
57
+ - 🔒 **Absolute Values & Floor/Ceiling:** Handles common wrappers like `\left|x\right|`, `\left\lfloor x \right\rfloor`, and `\left\lceil x \right\rceil`.
58
+ - 🧮 **Symbolic Operations:** Uses SymPy to evaluate supported derivatives, limits, integrals, finite products, and nth roots; finite sums are emitted as executable Python.
59
+ - 📐 **Extended Trig & Inverse Functions:** Supports reciprocal and inverse trig functions such as `\csc`, `\arccot`, `\arcsec`, and `\arccsc`.
60
+ - 🤖 **Machine-Learning Functions:** Supports `\operatorname{sigmoid}(x)`, `\operatorname{relu}(x)`, `\operatorname{softplus}(x)`, `\operatorname{logit}(x)`, `\operatorname{softsign}(x)`, and `\operatorname{swish}(x)` in scalar, NumPy, PyTorch, and JAX modes.
61
+ - 🏷️ **Type Annotations:** Automatically generates functions with PEP 484 type hints.
62
+ - 🔍 **Inspection Reports:** Shows the parsed symbolic expression, variables, warnings, and generated Python source.
63
+ - 💻 **Command Line Interface:** Transpiles formulas directly from your terminal and accepts input from stdin.
64
+
65
+ This is a focused expression transpiler, not a complete LaTeX implementation or a computer algebra system.
66
+
67
+ ---
68
+
69
+ ## Installation
70
+
71
+ Clone the repository and install in editable mode:
72
+
73
+ ```bash
74
+ git clone https://github.com/ThulioASB/latex2code.git
75
+ cd latex2code
76
+ python -m venv venv
77
+ source venv/bin/activate # On Windows: venv\Scripts\activate
78
+ pip install -e .
79
+
80
+ ```
81
+
82
+ Install an optional differentiable backend with one of the extras:
83
+
84
+ ```powershell
85
+ pip install -e ".[torch]"
86
+ pip install -e ".[jax]"
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Usage
92
+
93
+ ### As a Python Library
94
+
95
+ ```python
96
+ from latex2code import transpile_latex
97
+
98
+ # Transpile a fraction with trigonometric functions
99
+ code = transpile_latex(r"\frac{\sin(x) + \cos(y)}{\sqrt{z}}", function_name="calculate_wave")
100
+ print(code)
101
+
102
+ ```
103
+
104
+ **Output:**
105
+
106
+ ```python
107
+ import math
108
+
109
+ def calculate_wave(x: float, y: float, z: float) -> float:
110
+ return (math.sin(x) + math.cos(y))/math.sqrt(z)
111
+
112
+ ```
113
+
114
+ For direct evaluation in a notebook or application, use `compile_latex`. The returned object is
115
+ callable and retains the generated source and inspection details:
116
+
117
+ ```python
118
+ import numpy as np
119
+ from latex2code import compile_latex
120
+
121
+ formula = compile_latex(
122
+ r"\frac{\sin(x)}{\sqrt{x^2 + 1}}",
123
+ backend="numpy",
124
+ )
125
+
126
+ x = np.linspace(0, 10, 100)
127
+ result = formula(x)
128
+ ```
129
+
130
+ The convenience helper `latex_to_code` accepts the same keyword-only generation options as the
131
+ main transpiler. Use `variable_map` to rename parsed symbols:
132
+
133
+ ```python
134
+ from latex2code import latex_to_code
135
+
136
+ code = latex_to_code(
137
+ r"\theta + \lambda",
138
+ function_name="signal_model",
139
+ type_hints=False,
140
+ variable_map={"lambda": "lambda_value"},
141
+ )
142
+ print(code)
143
+ ```
144
+
145
+ The legacy `custom_symbol_map` and `use_numpy` Python arguments are deprecated. Use
146
+ `variable_map` and `backend="numpy"` instead.
147
+
148
+ Choose a backend just as with `transpile_latex`; optional frameworks must be installed when
149
+ compiling a formula for that backend. The callable uses only the arguments supplied by the caller
150
+ and does not automatically move or cast array/tensor inputs.
151
+
152
+ ### Constants and Greek Variables
153
+
154
+ ```python
155
+ from latex2code import transpile_latex
156
+
157
+ code = transpile_latex(r"\sin(\pi x) + e + \theta", function_name="signal_response")
158
+ print(code)
159
+ ```
160
+
161
+ Standard constants `\pi` and `e` are emitted as numeric constants by default. Other Greek-letter
162
+ symbols such as `\theta` remain variables and appear in the generated function signature. Pass a
163
+ `constants` mapping to customize substitutions; for example, `constants={"pi": math.pi}` leaves
164
+ `e` as a function argument. Use `args=["x", "w", "b"]` to explicitly set argument order; the
165
+ default follows each variable's first appearance in the input.
166
+
167
+ Text wrappers preserve their contents: `\mathrm{e}^x` retains the `e`, and `\text{...}` is not
168
+ removed as if it were whitespace.
169
+
170
+ ### Inspecting a Transpilation
171
+
172
+ Use the inspection API to check how an expression was interpreted before using its generated source:
173
+
174
+ ```python
175
+ from latex2code import inspect_latex
176
+
177
+ details = inspect_latex(r"\sin(\pi x) + \theta")
178
+ print(details.input_expression)
179
+ print(details.backend)
180
+ print(details.parsed_expression)
181
+ print(details.expression_tree)
182
+ print(details.variables)
183
+ print(details.generated_code)
184
+ print(details.warnings)
185
+ ```
186
+
187
+ The CLI's `--inspect` option prints the same information as JSON. Parse and unsupported-feature
188
+ errors include a line and column with a short source excerpt when the location can be identified:
189
+
190
+ ```powershell
191
+ latex2code "\sin(\pi x) + \theta" --inspect
192
+ ```
193
+
194
+ Use `variable_map` to give parsed symbols clearer Python argument names without changing the
195
+ mathematics. Keys are parsed symbol names, and values must be unique Python identifiers:
196
+
197
+ ```python
198
+ from latex2code import compile_latex
199
+
200
+ model = compile_latex(
201
+ r"\theta + \lambda",
202
+ variable_map={"theta": "angle", "lambda": "wavelength"},
203
+ )
204
+ print(model(angle=0.5, wavelength=2.0))
205
+ ```
206
+
207
+ Inspection reports include `expression_tree`, a JSON-compatible tree of SymPy operations and
208
+ arguments, as well as the readable `parsed_expression` string.
209
+
210
+ ### Matrix Transpilation
211
+
212
+ ```python
213
+ from latex2code import transpile_latex
214
+
215
+ latex_matrix = r"\begin{pmatrix} x^2 & 1 \\ 0 & y \end{pmatrix}"
216
+ code = transpile_latex(latex_matrix, function_name="create_matrix", type_hints=False)
217
+ print(code)
218
+
219
+ ```
220
+
221
+ **Output:**
222
+
223
+ ```python
224
+ import numpy as np
225
+
226
+ def create_matrix(x, y):
227
+ return np.array([[x**2, 1], [0, y]])
228
+
229
+ ```
230
+
231
+ ### Piecewise Expressions
232
+
233
+ ```python
234
+ from latex2code import transpile_latex
235
+
236
+ code = transpile_latex(
237
+ r"\begin{cases} x & x > 0 \\ -x & x \le 0 \end{cases}",
238
+ function_name="abs_value",
239
+ type_hints=False,
240
+ )
241
+ print(code)
242
+ ```
243
+
244
+ **Output:**
245
+
246
+ ```python
247
+ def abs_value(x):
248
+ return (x) if (x > 0) else (-x)
249
+ ```
250
+
251
+ ### Machine-Learning Activations
252
+
253
+ Common activation and link functions can be generated for scalar, NumPy, PyTorch, or JAX inputs:
254
+
255
+ ```python
256
+ from latex2code import transpile_latex
257
+
258
+ code = transpile_latex(
259
+ r"\operatorname{sigmoid}(x)",
260
+ function_name="logistic_probability",
261
+ backend="numpy",
262
+ )
263
+ print(code)
264
+ ```
265
+
266
+ The default `python` backend emits ordinary Python math. `backend="numpy"` supports NumPy arrays.
267
+ The optional `torch` and `jax` backends generate operations using framework primitives so tensor
268
+ autodiff can trace expressions; pass tensors/arrays of the chosen framework as inputs. These
269
+ backends do not imply that every supported LaTeX construct has equivalent batching or gradient
270
+ semantics. Named `sigmoid`, `softplus`, and `swish` operators use stable scalar or
271
+ framework-native implementations. Writing equivalent expanded formulas manually (such as
272
+ `log(1 + exp(x))`) is not guaranteed to receive this stabilization. Check input domains for
273
+ `logit`, logarithms, and roots; the transpiler does not silently clip inputs.
274
+
275
+ ```python
276
+ torch_code = transpile_latex(r"\sin(x)^2 + x", backend="torch")
277
+ jax_code = transpile_latex(r"\sin(x)^2 + x", backend="jax")
278
+ ```
279
+
280
+ ### Using Generated Code in Training or Analysis
281
+
282
+ Choose tensor dtype/device and array shape in your application; generated functions preserve
283
+ framework operations and do not move or silently cast tensor inputs:
284
+
285
+ ```python
286
+ import torch
287
+
288
+ source = transpile_latex(r"\operatorname{softplus}(x) + x^2", backend="torch")
289
+ namespace = {}
290
+ exec(source, namespace)
291
+ formula = namespace["formula"]
292
+
293
+ x = torch.tensor([0.25, 1.0], dtype=torch.float64, device="cpu", requires_grad=True)
294
+ loss = formula(x).mean()
295
+ loss.backward()
296
+ ```
297
+
298
+ For JAX, generated functions can be passed to `jax.jit`, `jax.vmap`, and `jax.grad` when the
299
+ expression uses supported JAX primitives. Test the actual shapes and dtypes used by your workload.
300
+
301
+ Piecewise functions use eager elementwise selection (`where`) for array/tensor inputs: both branch
302
+ expressions may be evaluated before selection. Avoid invalid operations in inactive branches (such
303
+ as taking `log(x)` in a branch where `x <= 0`); use a formula with valid branch expressions or
304
+ write a framework-specific implementation when true lazy control flow is required. Transpiling
305
+ piecewise expressions for NumPy, PyTorch, or JAX emits a `PiecewiseEvaluationWarning`. Automatic
306
+ domain clipping is intentionally not performed because it changes the formula and its gradients.
307
+
308
+ ### Reproducible Scientific Example: Gaussian Density
309
+
310
+ This normal probability density uses `x`, `mu`, and `sigma` in the same units; its output has
311
+ inverse units. Require `sigma > 0`. At the mean, with `sigma = 2`, the density is
312
+ `1 / (2 sqrt(2 pi))`, approximately `0.19947114020071635`:
313
+
314
+ ```python
315
+ from latex2code import compile_latex
316
+
317
+ density = compile_latex(
318
+ r"\frac{1}{\sigma \sqrt{2 \pi}}"
319
+ r"\exp(-\frac{(x-\mu)^2}{2 \sigma^2})"
320
+ )
321
+ value = density(x=0.0, mu=0.0, sigma=2.0)
322
+ print(value) # 0.19947114020071635
323
+ ```
324
+
325
+ The expression does not enforce `sigma > 0`; validate model parameters in the calling
326
+ application. To evaluate arrays/tensors, select `backend="numpy"`, `"torch"`, or `"jax"` and
327
+ choose the input dtype to suit the calculation's precision requirements.
328
+
329
+ ### Limits and Binomial Coefficients
330
+
331
+ ```python
332
+ from latex2code import transpile_latex
333
+
334
+ limit_code = transpile_latex(r"\lim_{x \to 0} \frac{\sin(x)}{x}", function_name="sinc_limit", type_hints=False)
335
+ print(limit_code)
336
+
337
+ binomial_code = transpile_latex(r"\binom{n}{k}", function_name="choose", type_hints=False)
338
+ print(binomial_code)
339
+
340
+ product_code = transpile_latex(r"\prod_{i=1}^{n} i", function_name="factorial_product", type_hints=False)
341
+ print(product_code)
342
+ ```
343
+
344
+ **Output:**
345
+
346
+ ```python
347
+ def sinc_limit():
348
+ return 1
349
+
350
+ import math
351
+
352
+ def choose(n, k):
353
+ return (math.gamma(n + 1)/(math.gamma(k + 1)*math.gamma(-k + n + 1)))
354
+
355
+ import math
356
+
357
+ def factorial_product(n):
358
+ return math.factorial(n)
359
+ ```
360
+
361
+ ---
362
+
363
+ ## CLI Usage
364
+
365
+ You can run `latex2code` directly from your command line:
366
+
367
+ ```powershell
368
+ latex2code "\frac{a}{b}" --name divide
369
+ ```
370
+
371
+ You can also pipe input from standard input:
372
+
373
+ ```powershell
374
+ echo '\sin(x) + \cos(y)' | latex2code - --name signal
375
+ ```
376
+
377
+ Write the generated source directly to a Python file with `--output`:
378
+
379
+ ```powershell
380
+ latex2code "\frac{a}{b}" --name divide --output divide.py
381
+ ```
382
+
383
+ Select an output backend with `--backend`:
384
+
385
+ ```powershell
386
+ latex2code "\sin(x)^2 + x" --backend torch --output differentiable.py
387
+ latex2code "\sin(x)^2 + x" --backend jax --output differentiable.py
388
+ ```
389
+
390
+ Generated source for PyTorch/JAX imports the selected framework, so install that framework in the
391
+ environment where the generated file will run.
392
+ Use `--inspect` to print a JSON report with the parsed expression, function variables, warnings, and source.
393
+ Rename parsed symbols with repeatable `--map-variable SYMBOL=NAME` options:
394
+
395
+ ```powershell
396
+ latex2code "\theta + x" --map-variable theta=angle
397
+ ```
398
+
399
+ Start an interactive session to paste and transpile expressions. Use `--inspect` to show the
400
+ structured JSON report for each expression; `quit` or `exit` ends the session. For multiline
401
+ expressions, enter `:begin`, paste the expression, then enter `:end`:
402
+
403
+ ```powershell
404
+ latex2code --interactive
405
+ latex2code --interactive --inspect --backend numpy
406
+ ```
407
+
408
+ ### Options
409
+
410
+ | Flag | Long Option | Description |
411
+ | --- | --- | --- |
412
+ | `-n` | `--name` | Name of the generated Python function (default: `formula`). |
413
+ | | `--no-types` | Disable type hints in the generated signature. |
414
+ | | `--backend` | Select `python`, `numpy`, `torch`, or `jax` output (default: `python`). |
415
+ | `-o` | `--output` | Write generated Python source to a file instead of stdout. |
416
+ | | `--inspect` | Print JSON containing the parsed expression, variables, warnings, and generated source. |
417
+ | | `--map-variable SYMBOL=NAME` | Rename a parsed variable; may be repeated. |
418
+ | `-i` | `--interactive` | Read and transpile expressions until `quit`, `exit`, or end-of-file. |
419
+
420
+ ---
421
+
422
+ ## Running Tests
423
+
424
+ Run the test suite using `pytest`:
425
+
426
+ ```powershell
427
+ pytest
428
+ ```
429
+
430
+ ## Supported Scope and Limitations
431
+
432
+ - The table describes the supported shapes of input; it does not claim full TeX macro or document parsing.
433
+
434
+ | Area | Supported examples | Notes |
435
+ | --- | --- | --- |
436
+ | Arithmetic | `+`, `-`, products, fractions, powers, roots, nth roots | Parsed expressions follow SymPy's supported LaTeX grammar. |
437
+ | Functions | Trigonometric, inverse/reciprocal trig, hyperbolic, exponential/logarithmic, `erf`, `gamma`, `beta`, `max`, `min` | Function support can depend on backend printer support; beta is generated using log-gamma arithmetic. |
438
+ | ML operators | `sigmoid`, `relu`, `softplus`, `logit`, `softsign`, `swish` | Use `\operatorname{...}(x)`; sigmoid, softplus, and swish use stable scalar/framework-native implementations. |
439
+ | Symbolic calculus | Derivatives, limits, integrals, finite sums and reducible finite products | SymPy evaluates operations when possible; unresolved products fail rather than emitting incomplete code. |
440
+ | Arrays and cases | Matrix/array layouts, `cases`, selected aligned equation layouts | Aligned equations are represented as rows of values, not solved as systems. |
441
+ | Inspection and names | Parsed expression tree and variable-to-identifier mapping | Mappings rename arguments only; they do not substitute values or change expression semantics. |
442
+
443
+ Backend behavior below describes tested pathways, not a guarantee that every parser-supported
444
+ function has equivalent semantics across targets:
445
+
446
+ | Capability | Python | NumPy | PyTorch | JAX |
447
+ | --- | --- | --- | --- | --- |
448
+ | Scalar arithmetic and common functions | Yes | Yes | Yes, for generated native operations | Yes, for generated native operations |
449
+ | Elementwise arrays/tensors | No array contract | NumPy broadcasting | Pass tensors with desired dtype/device | Pass arrays with desired dtype |
450
+ | Matrix/array constructors | NumPy output for compatibility | NumPy arrays | Tensor output; gradients through dynamic entries | JAX arrays |
451
+ | Piecewise array evaluation | Python conditional expression | `where`, eager branches | `where`, eager branches | `where`, eager branches |
452
+ | Automatic differentiation | No | No | Yes, for differentiable native operations | Yes, for differentiable native operations |
453
+ | JIT compilation | No library guarantee | No library guarantee | No library guarantee | `jax.jit` tested for supported primitives |
454
+
455
+ PyTorch and JAX are optional runtime dependencies. Operations without a native differentiable
456
+ implementation are rejected rather than silently replaced with Python math. JAX commonly defaults
457
+ to 32-bit floating point; enable and verify 64-bit mode in the application when needed. Python and
458
+ NumPy error/gamma functions can use Python `math` implementations wrapped for arrays, which may be
459
+ slower than native array kernels.
460
+
461
+ - Input is parsed strictly by SymPy's ANTLR-based LaTeX parser plus a restricted safe fallback, so malformed trailing tokens are rejected rather than silently discarded. Document markup, arbitrary TeX macros, and arbitrary custom functions are outside scope.
462
+ - Presentation-only spacing commands such as `\quad`, `\qquad`, `\,`, `\hspace{...}`, and `\vspace{...}` are ignored. `\displaystyle` and related math-style commands are also ignored. Text wrappers and unknown macros are not stripped because they can carry mathematical meaning; remove or rewrite them explicitly if unsupported.
463
+ - Function application and implicit multiplication follow the parser's documented grammar; no assumptions are made about custom function names or subscript semantics. Inspect the parsed expression/tree and explicitly rename variables instead of relying on guessed intent.
464
+ - Unsupported commands and functions raise `UnsupportedLaTeXFeatureError`; close command/function spellings are suggested when possible. Malformed input raises `InvalidLaTeXSyntaxError`, which includes a source location and excerpt when identifiable, plus delimiter or environment hints when detected.
465
+ - For example, a misspelled `\sinn(x)` reports the likely `\sin` command, while a fraction with an unmatched brace points out the unbalanced delimiters.
466
+ - Piecewise cases without an explicit default remain undefined outside their listed conditions; generated Python returns `None` there, while NumPy mode represents those values as `numpy.nan`.
467
+ - `\pi` and `e` are numeric constants; other Greek symbols may become function parameters. Use `inspect_latex` or CLI `--inspect` to review parsed variables and generated code.
468
+ - Derivatives and integrals are evaluated symbolically when SymPy can do so. An indefinite integral returns one antiderivative and does not add the arbitrary constant of integration.
469
+ - Finite sums are emitted as executable Python sum expressions. Matrices use the selected array backend (Python's matrix output remains a NumPy array for compatibility).
470
+ - Finite products are symbolically evaluated when SymPy can reduce them; unresolved symbolic products raise `CodeGenerationError` rather than returning incomplete Python.
471
+ - NumPy, PyTorch, and JAX operate elementwise where the framework supports the operation. Finite sums still use scalar iteration bounds.
472
+ - Piecewise outputs use elementwise selection in array backends. Validate branch domains when an unselected branch could itself produce invalid values.
473
+ - SymPy's current LaTeX grammar requires the `antlr4-python3-runtime` package (declared with a compatible version range); SymPy's Lark backend was evaluated but does not cover the expression grammar and constructs supported here.
474
+ - Generated tensor code does not promise a particular device, dtype, or shape policy. Gamma has poles at non-positive integers; behavior at singularities and outside mathematical domains follows the selected backend.
475
+ - Generated Python is not a mathematical proof or a guarantee of numerical stability. Review and test output before relying on it in research or production calculations.
476
+
477
+ ---
478
+
479
+ ## License
480
+
481
+ Distributed under the MIT License. See `LICENSE` for more information.