Skip to content

Commit 0b44870

Browse files
committed
feat: Add Python compilation target with examples and benchmarks
1 parent a3b4a61 commit 0b44870

9 files changed

Lines changed: 1518 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,65 @@
9898
**Features**:
9999
- Built-in targets: `javascript` (executable functions) and `glsl` (shader code)
100100
- Register custom targets with `ce.registerCompilationTarget(name, target)`
101+
102+
- **Python/NumPy Compilation Target**: Added a complete Python/NumPy compilation
103+
target for scientific computing workflows. The `PythonTarget` class compiles
104+
mathematical expressions to NumPy-compatible Python code.
105+
106+
```javascript
107+
import { ComputeEngine, PythonTarget } from '@cortex-js/compute-engine';
108+
109+
const ce = new ComputeEngine();
110+
const python = new PythonTarget({ includeImports: true });
111+
112+
// Register the target
113+
ce.registerCompilationTarget('python', python);
114+
115+
// Compile expressions to Python
116+
const expr = ce.parse('\\sin(x) + \\cos(y)');
117+
const code = expr.compile({ to: 'python' });
118+
console.log(code.toString());
119+
// → import numpy as np
120+
//
121+
// np.sin(x) + np.cos(y)
122+
123+
// Generate complete Python functions
124+
const func = python.compileFunction(
125+
ce.parse('\\sqrt{x^2 + y^2}'),
126+
'magnitude',
127+
['x', 'y'],
128+
'Calculate vector magnitude'
129+
);
130+
// Generates:
131+
// import numpy as np
132+
//
133+
// def magnitude(x, y):
134+
// """Calculate vector magnitude"""
135+
// return np.sqrt(x ** 2 + y ** 2)
136+
```
137+
138+
**Features**:
139+
- Compiles to NumPy-compatible Python code (works with arrays)
140+
- 50+ function mappings (trig, exponential, linear algebra, statistics)
141+
- Generate complete Python functions with docstrings
142+
- Create Python lambda expressions
143+
- Generate NumPy-vectorized functions
144+
- Configuration options for imports and SciPy support
145+
- Ideal for scientific computing, ML, data analysis, and education
146+
147+
**Use Cases**:
148+
- **Scientific Computing**: Generate NumPy code for numerical analysis
149+
- **Machine Learning**: Create feature engineering functions
150+
- **Data Analysis**: Convert formulas to Pandas/NumPy operations
151+
- **Education**: Show Python equivalents of mathematical notation
152+
- **Code Generation**: Automated function creation from LaTeX
153+
154+
**Supported Functions**: Trigonometric (sin, cos, tan), exponential (exp, ln, log),
155+
power (sqrt, power), rounding (abs, floor, ceil), statistics (sum, mean, std),
156+
linear algebra (dot, cross, norm, det, inv), and more.
157+
158+
See the [Python/NumPy Target Guide](/compute-engine/guides/python-target/) for
159+
complete documentation and examples.
101160
- Switch between targets using the `to` option in `compile()`
102161
- Direct target override with the `target` option for one-time use
103162
- Extend or replace built-in targets

benchmarks/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Python bytecode
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Virtual environments
7+
venv/
8+
env/
9+
ENV/
10+
11+
# IDE
12+
.vscode/
13+
.idea/
14+
15+
# OS
16+
.DS_Store
17+
Thumbs.db

benchmarks/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Python/NumPy Performance Benchmarks
2+
3+
This directory contains generated Python benchmarks for comparing NumPy performance
4+
with JavaScript compilation performance.
5+
6+
## Setup
7+
8+
Install dependencies:
9+
10+
```bash
11+
pip install numpy
12+
```
13+
14+
## Running Benchmarks
15+
16+
Run the benchmark script:
17+
18+
```bash
19+
python benchmarks/python-performance.py
20+
```
21+
22+
## Regenerating Benchmarks
23+
24+
The Python benchmark script is generated from TypeScript tests. To regenerate:
25+
26+
```bash
27+
npm run test compute-engine/compile-python-generate
28+
```
29+
30+
## Comparing with JavaScript
31+
32+
To see JavaScript performance for the same expressions:
33+
34+
```bash
35+
npm run test compute-engine/compile-performance
36+
```
37+
38+
## Expected Results
39+
40+
- **NumPy**: Fast for array operations, some overhead for scalar operations
41+
- **JavaScript Compiled**: Very fast for scalar operations, optimized by V8 JIT
42+
- **Both**: Much faster than interpreted evaluation (40-2900x speedup)
43+
44+
## Use Cases
45+
46+
- **NumPy/Python**: Ideal for scientific computing, data analysis, ML workflows
47+
- **JavaScript**: Ideal for browser/Node.js applications, real-time computation
48+
- **GLSL**: Ideal for GPU parallel computation, graphics, WebGL
49+
50+
## Performance Tips
51+
52+
For NumPy:
53+
- Use vectorized operations (arrays) instead of scalar loops
54+
- Leverage NumPy's C-optimized implementations
55+
- Avoid Python loops when possible
56+
57+
For JavaScript:
58+
- Compiled functions are optimized by V8 JIT
59+
- Minimal overhead for function calls
60+
- Works great in browser and Node.js

benchmarks/python-performance.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Python/NumPy Performance Benchmarks
4+
Generated from Compute Engine expressions
5+
6+
This script benchmarks NumPy-compiled mathematical expressions
7+
and compares performance with pure Python evaluation.
8+
9+
Run with: python benchmarks/python-performance.py
10+
11+
Requirements:
12+
pip install numpy
13+
"""
14+
15+
import numpy as np
16+
import time
17+
from typing import Dict, Any, Callable
18+
19+
def benchmark(fn: Callable, iterations: int, **kwargs) -> float:
20+
"""Benchmark a function over multiple iterations"""
21+
start = time.perf_counter()
22+
for _ in range(iterations):
23+
fn(**kwargs)
24+
end = time.perf_counter()
25+
return (end - start) * 1000 # Convert to milliseconds
26+
27+
# Generated benchmark functions
28+
29+
def simple_power(x, y, z):
30+
r"""Simple Power: x^2 + y^2 + z^2"""
31+
return x ** 2 + y ** 2 + z ** 2
32+
33+
34+
def polynomial(x):
35+
r"""Polynomial: x^4 + 3x^3 + 2x^2 + x + 1"""
36+
return x ** 4 + 3 * x ** 3 + 2 * x ** 2 + x + 1
37+
38+
39+
def trigonometric(x, y, z):
40+
r"""Trigonometric: \sin(x) + \cos(y) + \tan(z)"""
41+
return np.sin(x) + np.cos(y) + np.tan(z)
42+
43+
44+
def nested_expression(x, y, z, a, b, c):
45+
r"""Nested Expression: \sqrt{(x-a)^2 + (y-b)^2 + (z-c)^2}"""
46+
return np.sqrt((-a + x) ** 2 + (-b + y) ** 2 + (-c + z) ** 2)
47+
48+
49+
def large_expression__50_terms_(x):
50+
r"""Large Expression (50 terms): x^0 + x^1 + x^2 + x^3 + x^4 + x^5 + x^6 + x^7 + x^8 + x^9 + x^10 + x^11 + x^12 + x^13 + x^14 + x^15 + x^16 + x^17 + x^18 + x^19 + x^20 + x^21 + x^22 + x^23 + x^24 + x^25 + x^26 + x^27 + x^28 + x^29 + x^30 + x^31 + x^32 + x^33 + x^34 + x^35 + x^36 + x^37 + x^38 + x^39 + x^40 + x^41 + x^42 + x^43 + x^44 + x^45 + x^46 + x^47 + x^48 + x^49"""
51+
return x ** 9 + x ** 8 + x ** 7 + x ** 6 + x ** 5 + 0 * x ** 4 + 2 * x ** 4 + 3 * x ** 4 + 4 * x ** 4 + 5 * x ** 4 + 6 * x ** 4 + 7 * x ** 4 + 8 * x ** 4 + 9 * x ** 4 + x ** 4 + x ** 4 + 0 * x ** 3 + 2 * x ** 3 + 3 * x ** 3 + 4 * x ** 3 + 5 * x ** 3 + 6 * x ** 3 + 7 * x ** 3 + 8 * x ** 3 + 9 * x ** 3 + x ** 3 + x ** 3 + 0 * x ** 2 + 2 * x ** 2 + 3 * x ** 2 + 4 * x ** 2 + 5 * x ** 2 + 6 * x ** 2 + 7 * x ** 2 + 8 * x ** 2 + 9 * x ** 2 + x ** 2 + x ** 2 + x + x + 0 * x + 2 * x + 3 * x + 4 * x + 5 * x + 6 * x + 7 * x + 8 * x + 9 * x + x ** 0
52+
53+
54+
def many_variables__20_vars_(x_0, x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9, x_10, x_11, x_12, x_13, x_14, x_15, x_16, x_17, x_18, x_19):
55+
r"""Many Variables (20 vars): x_{0} + x_{1} + x_{2} + x_{3} + x_{4} + x_{5} + x_{6} + x_{7} + x_{8} + x_{9} + x_{10} + x_{11} + x_{12} + x_{13} + x_{14} + x_{15} + x_{16} + x_{17} + x_{18} + x_{19}"""
56+
return x_0 + x_1 + x_10 + x_11 + x_12 + x_13 + x_14 + x_15 + x_16 + x_17 + x_18 + x_19 + x_2 + x_3 + x_4 + x_5 + x_6 + x_7 + x_8 + x_9
57+
58+
59+
def distance_formula(x_1, y_1, x_2, y_2):
60+
r"""Distance Formula: \sqrt{(x_2-x_1)^2 + (y_2-y_1)^2}"""
61+
return np.sqrt((-x_1 + x_2) ** 2 + (-y_1 + y_2) ** 2)
62+
63+
64+
def quadratic_formula(a, b, c):
65+
r"""Quadratic Formula: \frac{-b + \sqrt{b^2 - 4ac}}{2a}"""
66+
return (-b + np.sqrt(b ** 2 + -4 * a * c)) / (2 * a)
67+
68+
69+
def kinematics(u, a, t):
70+
r"""Kinematics: u \cdot t + \frac{1}{2} a \cdot t^2"""
71+
return 0.5 * a * t ** 2 + t * u
72+
73+
74+
# Benchmark suite
75+
def run_benchmarks():
76+
"""Run all benchmarks and display results"""
77+
print("=" * 80)
78+
print("Python/NumPy Performance Benchmarks")
79+
print("=" * 80)
80+
print()
81+
82+
results = []
83+
84+
# Simple Power
85+
print(f"Running: Simple Power (10,000 iterations)")
86+
test_data_simple_power = {"x":3,"y":4,"z":5}
87+
time_simple_power = benchmark(simple_power, 10000, **test_data_simple_power)
88+
result_simple_power = simple_power(**test_data_simple_power)
89+
print(f" Time: {time_simple_power:.2f} ms")
90+
print(f" Result: {result_simple_power}")
91+
results.append({
92+
'name': 'Simple Power',
93+
'iterations': 10000,
94+
'time_ms': time_simple_power,
95+
'time_per_op_us': (time_simple_power * 1000) / 10000,
96+
'result': result_simple_power
97+
})
98+
print()
99+
100+
# Polynomial
101+
print(f"Running: Polynomial (10,000 iterations)")
102+
test_data_polynomial = {"x":2.5}
103+
time_polynomial = benchmark(polynomial, 10000, **test_data_polynomial)
104+
result_polynomial = polynomial(**test_data_polynomial)
105+
print(f" Time: {time_polynomial:.2f} ms")
106+
print(f" Result: {result_polynomial}")
107+
results.append({
108+
'name': 'Polynomial',
109+
'iterations': 10000,
110+
'time_ms': time_polynomial,
111+
'time_per_op_us': (time_polynomial * 1000) / 10000,
112+
'result': result_polynomial
113+
})
114+
print()
115+
116+
# Trigonometric
117+
print(f"Running: Trigonometric (10,000 iterations)")
118+
test_data_trigonometric = {"x":1,"y":2,"z":3}
119+
time_trigonometric = benchmark(trigonometric, 10000, **test_data_trigonometric)
120+
result_trigonometric = trigonometric(**test_data_trigonometric)
121+
print(f" Time: {time_trigonometric:.2f} ms")
122+
print(f" Result: {result_trigonometric}")
123+
results.append({
124+
'name': 'Trigonometric',
125+
'iterations': 10000,
126+
'time_ms': time_trigonometric,
127+
'time_per_op_us': (time_trigonometric * 1000) / 10000,
128+
'result': result_trigonometric
129+
})
130+
print()
131+
132+
# Nested Expression
133+
print(f"Running: Nested Expression (10,000 iterations)")
134+
test_data_nested_expression = {"x":5,"y":6,"z":7,"a":1,"b":2,"c":3}
135+
time_nested_expression = benchmark(nested_expression, 10000, **test_data_nested_expression)
136+
result_nested_expression = nested_expression(**test_data_nested_expression)
137+
print(f" Time: {time_nested_expression:.2f} ms")
138+
print(f" Result: {result_nested_expression}")
139+
results.append({
140+
'name': 'Nested Expression',
141+
'iterations': 10000,
142+
'time_ms': time_nested_expression,
143+
'time_per_op_us': (time_nested_expression * 1000) / 10000,
144+
'result': result_nested_expression
145+
})
146+
print()
147+
148+
# Large Expression (50 terms)
149+
print(f"Running: Large Expression (50 terms) (1,000 iterations)")
150+
test_data_large_expression__50_terms_ = {"x":1.1}
151+
time_large_expression__50_terms_ = benchmark(large_expression__50_terms_, 1000, **test_data_large_expression__50_terms_)
152+
result_large_expression__50_terms_ = large_expression__50_terms_(**test_data_large_expression__50_terms_)
153+
print(f" Time: {time_large_expression__50_terms_:.2f} ms")
154+
print(f" Result: {result_large_expression__50_terms_}")
155+
results.append({
156+
'name': 'Large Expression (50 terms)',
157+
'iterations': 1000,
158+
'time_ms': time_large_expression__50_terms_,
159+
'time_per_op_us': (time_large_expression__50_terms_ * 1000) / 1000,
160+
'result': result_large_expression__50_terms_
161+
})
162+
print()
163+
164+
# Many Variables (20 vars)
165+
print(f"Running: Many Variables (20 vars) (10,000 iterations)")
166+
test_data_many_variables__20_vars_ = {"x_0":1,"x_1":2,"x_2":3,"x_3":4,"x_4":5,"x_5":6,"x_6":7,"x_7":8,"x_8":9,"x_9":10,"x_10":11,"x_11":12,"x_12":13,"x_13":14,"x_14":15,"x_15":16,"x_16":17,"x_17":18,"x_18":19,"x_19":20}
167+
time_many_variables__20_vars_ = benchmark(many_variables__20_vars_, 10000, **test_data_many_variables__20_vars_)
168+
result_many_variables__20_vars_ = many_variables__20_vars_(**test_data_many_variables__20_vars_)
169+
print(f" Time: {time_many_variables__20_vars_:.2f} ms")
170+
print(f" Result: {result_many_variables__20_vars_}")
171+
results.append({
172+
'name': 'Many Variables (20 vars)',
173+
'iterations': 10000,
174+
'time_ms': time_many_variables__20_vars_,
175+
'time_per_op_us': (time_many_variables__20_vars_ * 1000) / 10000,
176+
'result': result_many_variables__20_vars_
177+
})
178+
print()
179+
180+
# Distance Formula
181+
print(f"Running: Distance Formula (10,000 iterations)")
182+
test_data_distance_formula = {"x_1":0,"y_1":0,"x_2":3,"y_2":4}
183+
time_distance_formula = benchmark(distance_formula, 10000, **test_data_distance_formula)
184+
result_distance_formula = distance_formula(**test_data_distance_formula)
185+
print(f" Time: {time_distance_formula:.2f} ms")
186+
print(f" Result: {result_distance_formula}")
187+
results.append({
188+
'name': 'Distance Formula',
189+
'iterations': 10000,
190+
'time_ms': time_distance_formula,
191+
'time_per_op_us': (time_distance_formula * 1000) / 10000,
192+
'result': result_distance_formula
193+
})
194+
print()
195+
196+
# Quadratic Formula
197+
print(f"Running: Quadratic Formula (10,000 iterations)")
198+
test_data_quadratic_formula = {"a":1,"b":-5,"c":6}
199+
time_quadratic_formula = benchmark(quadratic_formula, 10000, **test_data_quadratic_formula)
200+
result_quadratic_formula = quadratic_formula(**test_data_quadratic_formula)
201+
print(f" Time: {time_quadratic_formula:.2f} ms")
202+
print(f" Result: {result_quadratic_formula}")
203+
results.append({
204+
'name': 'Quadratic Formula',
205+
'iterations': 10000,
206+
'time_ms': time_quadratic_formula,
207+
'time_per_op_us': (time_quadratic_formula * 1000) / 10000,
208+
'result': result_quadratic_formula
209+
})
210+
print()
211+
212+
# Kinematics
213+
print(f"Running: Kinematics (10,000 iterations)")
214+
test_data_kinematics = {"u":10,"a":9.8,"t":2}
215+
time_kinematics = benchmark(kinematics, 10000, **test_data_kinematics)
216+
result_kinematics = kinematics(**test_data_kinematics)
217+
print(f" Time: {time_kinematics:.2f} ms")
218+
print(f" Result: {result_kinematics}")
219+
results.append({
220+
'name': 'Kinematics',
221+
'iterations': 10000,
222+
'time_ms': time_kinematics,
223+
'time_per_op_us': (time_kinematics * 1000) / 10000,
224+
'result': result_kinematics
225+
})
226+
print()
227+
228+
# Summary
229+
print("=" * 80)
230+
print("Summary")
231+
print("=" * 80)
232+
print()
233+
print(f"{'Benchmark':<30} {'Iterations':<12} {'Total (ms)':<12} {'Per Op (μs)':<12}")
234+
print("-" * 80)
235+
236+
for r in results:
237+
print(f"{r['name']:<30} {r['iterations']:<12,} {r['time_ms']:<12.2f} {r['time_per_op_us']:<12.6f}")
238+
239+
print()
240+
print("=" * 80)
241+
print("Comparison with JavaScript (from compile-performance.test.ts)")
242+
print("=" * 80)
243+
print()
244+
print("To compare with JavaScript performance:")
245+
print(" npm run test compute-engine/compile-performance")
246+
print()
247+
print("Expected results:")
248+
print(" - NumPy should be faster than JavaScript for vectorized operations")
249+
print(" - JavaScript may be faster for single evaluations (less overhead)")
250+
print(" - Both should be much faster than interpreted evaluation")
251+
print()
252+
253+
if __name__ == '__main__':
254+
run_benchmarks()

0 commit comments

Comments
 (0)