Skip to content

Commit fd3c4c5

Browse files
committed
Add adv_math_engine module with calculus, algebra, series, CLI, and tests
1 parent 9ea690e commit fd3c4c5

17 files changed

Lines changed: 763 additions & 0 deletions

adv_math_engine/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# adv_math_engine
2+
3+
Production-grade module for:
4+
- Vector algebra
5+
- Vector calculus
6+
- Taylor / Maclaurin series expansion
7+
8+
## Structure
9+
10+
```text
11+
adv_math_engine/
12+
├── vector_algebra.py
13+
├── vector_calculus.py
14+
├── series_expansion.py
15+
├── utils.py
16+
├── benchmarks/
17+
├── visualizations/
18+
├── examples/
19+
└── tests/
20+
```
21+
22+
## CLI
23+
24+
```bash
25+
python main.py --function "sin(x)" --series taylor --order 5 --x 0.5 --center 0.0
26+
```
27+
28+
## Visualizations
29+
30+
Run:
31+
32+
```bash
33+
python adv_math_engine/visualizations/plot_gradient_field.py
34+
python adv_math_engine/visualizations/plot_series_approximation.py
35+
```
36+
37+
Outputs:
38+
- `adv_math_engine/visualizations/gradient_field.png`
39+
- `adv_math_engine/visualizations/series_approximation.png`
40+
41+
## Benchmarks
42+
43+
```bash
44+
python adv_math_engine/benchmarks/benchmark_diff.py
45+
```
46+
47+
See `adv_math_engine/benchmarks/results.md`.
48+
49+
## Coverage
50+
51+
Suggested command:
52+
53+
```bash
54+
pytest adv_math_engine/tests --cov=adv_math_engine --cov-report=term-missing
55+
```
56+
57+
See `adv_math_engine/tests/coverage_report.txt`.

adv_math_engine/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Advanced mathematics engine for vector algebra, vector calculus, and series expansions."""
2+
3+
from adv_math_engine.series_expansion import (
4+
estimate_lagrange_remainder,
5+
maclaurin_series,
6+
taylor_series,
7+
)
8+
from adv_math_engine.vector_algebra import (
9+
angle_between,
10+
check_linear_independence,
11+
cross_product,
12+
dot_product,
13+
gram_schmidt,
14+
vector_projection,
15+
)
16+
from adv_math_engine.vector_calculus import (
17+
curl,
18+
divergence,
19+
gradient,
20+
line_integral,
21+
partial_derivative,
22+
surface_integral,
23+
)
24+
25+
__all__ = [
26+
"angle_between",
27+
"check_linear_independence",
28+
"cross_product",
29+
"curl",
30+
"divergence",
31+
"dot_product",
32+
"estimate_lagrange_remainder",
33+
"gradient",
34+
"gram_schmidt",
35+
"line_integral",
36+
"maclaurin_series",
37+
"partial_derivative",
38+
"surface_integral",
39+
"taylor_series",
40+
"vector_projection",
41+
]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
5+
from adv_math_engine.series_expansion import _numerical_nth_derivative
6+
from adv_math_engine.utils import timed_call
7+
8+
try:
9+
import sympy as sp
10+
except Exception: # pragma: no cover
11+
sp = None
12+
13+
14+
def numerical_benchmark(iterations: int = 2000) -> float:
15+
func = lambda x: np.sin(x) * np.exp(x)
16+
17+
def run() -> float:
18+
total = 0.0
19+
for value in np.linspace(-1.0, 1.0, iterations):
20+
total += _numerical_nth_derivative(func, n=1, at=float(value))
21+
return total
22+
23+
_, elapsed = timed_call(run)
24+
return elapsed
25+
26+
27+
def symbolic_benchmark(iterations: int = 2000) -> float | None:
28+
if sp is None:
29+
return None
30+
x = sp.symbols("x")
31+
derivative_expr = sp.diff(sp.sin(x) * sp.exp(x), x)
32+
evaluator = sp.lambdify(x, derivative_expr, "numpy")
33+
34+
def run() -> float:
35+
total = 0.0
36+
for value in np.linspace(-1.0, 1.0, iterations):
37+
total += float(evaluator(value))
38+
return total
39+
40+
_, elapsed = timed_call(run)
41+
return elapsed
42+
43+
44+
if __name__ == "__main__":
45+
num_time = numerical_benchmark()
46+
sym_time = symbolic_benchmark()
47+
print(f"numerical_time_seconds={num_time:.6f}")
48+
if sym_time is None:
49+
print("symbolic_time_seconds=unavailable")
50+
else:
51+
print(f"symbolic_time_seconds={sym_time:.6f}")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Benchmark Results
2+
3+
## Status
4+
Benchmark execution is prepared via:
5+
6+
```bash
7+
python adv_math_engine/benchmarks/benchmark_diff.py
8+
```
9+
10+
In this environment, benchmark execution could not be completed because required numerical dependencies (NumPy/SymPy) were unavailable to the active Python interpreter.
11+
12+
## Expected Output Format
13+
14+
```text
15+
numerical_time_seconds=<float>
16+
symbolic_time_seconds=<float|unavailable>
17+
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
5+
from adv_math_engine.series_expansion import maclaurin_series
6+
from adv_math_engine.vector_algebra import dot_product, gram_schmidt
7+
from adv_math_engine.vector_calculus import gradient
8+
9+
10+
if __name__ == "__main__":
11+
print("Dot:", dot_product([1, 2, 3], [4, 5, 6]))
12+
print("Orthonormal basis:\n", gram_schmidt([[1, 1, 0], [1, 0, 1], [0, 1, 1]]))
13+
14+
f = lambda x, y, z: x**2 + y**2 + z**2
15+
print("Gradient at (1,2,3):", gradient(f, [1, 2, 3]))
16+
17+
x = np.array([0.1, 0.2, 0.3])
18+
print("sin(x) Maclaurin order=7:", maclaurin_series(x, order=7, function_name="sin"))
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Taylor/Maclaurin series expansion utilities."""
2+
3+
from __future__ import annotations
4+
5+
import math
6+
from collections.abc import Callable
7+
from typing import Literal
8+
9+
import numpy as np
10+
11+
HAS_SYMPY = True
12+
try:
13+
import sympy as sp
14+
except Exception: # pragma: no cover - optional dependency guard
15+
HAS_SYMPY = False
16+
17+
18+
SupportedFunction = Literal["exp", "sin", "cos", "log1p"]
19+
20+
21+
def _builtin_derivative_value(name: SupportedFunction, n: int, at: float) -> float:
22+
if name == "exp":
23+
return float(math.exp(at))
24+
if name == "sin":
25+
return float(math.sin(at + n * math.pi / 2.0))
26+
if name == "cos":
27+
return float(math.cos(at + n * math.pi / 2.0))
28+
if name == "log1p":
29+
if n == 0:
30+
return float(math.log1p(at))
31+
return float(((-1) ** (n - 1)) * math.factorial(n - 1) / (1.0 + at) ** n)
32+
msg = f"Unsupported function: {name}"
33+
raise ValueError(msg)
34+
35+
36+
def _numerical_nth_derivative(func: Callable[[float], float], n: int, at: float, h: float = 1e-4) -> float:
37+
if n == 0:
38+
return float(func(at))
39+
if n == 1:
40+
return float((func(at + h) - func(at - h)) / (2.0 * h))
41+
return float(
42+
(_numerical_nth_derivative(func, n - 1, at + h, h) - _numerical_nth_derivative(func, n - 1, at - h, h)) / (2.0 * h)
43+
)
44+
45+
46+
def taylor_series(
47+
x: float | np.ndarray,
48+
order: int,
49+
center: float = 0.0,
50+
*,
51+
function_name: SupportedFunction | None = None,
52+
function: Callable[[float], float] | None = None,
53+
) -> np.ndarray:
54+
"""Evaluate Taylor polynomial of given order around center.
55+
56+
f(x) ≈ Σ[n=0..order] f^(n)(a) / n! * (x-a)^n
57+
"""
58+
if function_name is None and function is None:
59+
msg = "Provide either function_name or function."
60+
raise ValueError(msg)
61+
62+
x_arr = np.asarray(x, dtype=float)
63+
approximation = np.zeros_like(x_arr, dtype=float)
64+
65+
for n in range(order + 1):
66+
if function_name is not None:
67+
derivative_at_center = _builtin_derivative_value(function_name, n, center)
68+
else:
69+
assert function is not None
70+
derivative_at_center = _numerical_nth_derivative(function, n, center)
71+
72+
approximation = approximation + derivative_at_center / math.factorial(n) * (x_arr - center) ** n
73+
74+
return approximation
75+
76+
77+
def maclaurin_series(
78+
x: float | np.ndarray,
79+
order: int,
80+
*,
81+
function_name: SupportedFunction | None = None,
82+
function: Callable[[float], float] | None = None,
83+
) -> np.ndarray:
84+
"""Evaluate Maclaurin polynomial (Taylor around 0)."""
85+
return taylor_series(x, order=order, center=0.0, function_name=function_name, function=function)
86+
87+
88+
def estimate_lagrange_remainder(max_derivative: float, x: float | np.ndarray, order: int, center: float = 0.0) -> np.ndarray:
89+
"""Estimate Lagrange remainder bound: M|x-a|^(n+1)/(n+1)!"""
90+
x_arr = np.asarray(x, dtype=float)
91+
return np.abs(max_derivative) * np.abs(x_arr - center) ** (order + 1) / math.factorial(order + 1)
92+
93+
94+
def symbolic_taylor_expression(expr_str: str, symbol: str = "x", center: float = 0.0, order: int = 6) -> str:
95+
"""Return symbolic Taylor expression as a string when SymPy is available."""
96+
if not HAS_SYMPY:
97+
msg = "SymPy is not available."
98+
raise RuntimeError(msg)
99+
x = sp.symbols(symbol)
100+
expr = sp.sympify(expr_str)
101+
series = sp.series(expr, x, center, order + 1).removeO()
102+
return str(sp.expand(series))

adv_math_engine/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Test package for adv_math_engine."""
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Coverage command configured:
2+
3+
pytest adv_math_engine/tests --cov=adv_math_engine --cov-report=term-missing
4+
5+
Execution status in this environment:
6+
- Could not run with coverage because pytest-cov and numerical dependencies are not available in the active interpreter.
7+
- Network-restricted package installation prevented fetching missing dependencies.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
import math
4+
5+
import numpy as np
6+
7+
from adv_math_engine.series_expansion import (
8+
estimate_lagrange_remainder,
9+
maclaurin_series,
10+
taylor_series,
11+
)
12+
from adv_math_engine.utils import validate_tolerance
13+
14+
15+
def test_maclaurin_exp_convergence() -> None:
16+
x = np.linspace(-1.0, 1.0, 50)
17+
approx = maclaurin_series(x, order=12, function_name="exp")
18+
actual = np.exp(x)
19+
assert validate_tolerance(approx, actual, epsilon=1e-4)
20+
21+
22+
def test_taylor_sin_about_nonzero_center() -> None:
23+
x = np.array([0.2, 0.3, 0.5])
24+
approx = taylor_series(x, order=9, center=0.3, function_name="sin")
25+
assert np.allclose(approx, np.sin(x), atol=1e-6)
26+
27+
28+
def test_log1p_remainder_bound() -> None:
29+
x = 0.2
30+
order = 6
31+
approx = maclaurin_series(x, order=order, function_name="log1p")
32+
actual = np.log1p(x)
33+
remainder = estimate_lagrange_remainder(max_derivative=math.factorial(order), x=x, order=order)
34+
assert abs(actual - approx) <= remainder + 1e-6
35+
36+
37+
def test_arbitrary_function_numeric_derivative() -> None:
38+
f = lambda val: np.cos(val) * np.exp(val)
39+
x = np.array([0.0, 0.1, 0.2])
40+
approx = taylor_series(x, order=8, center=0.0, function=f)
41+
assert np.allclose(approx, f(x), atol=5e-3)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
3+
import numpy as np
4+
import pytest
5+
6+
from adv_math_engine.vector_algebra import (
7+
angle_between,
8+
check_linear_independence,
9+
cross_product,
10+
dot_product,
11+
gram_schmidt,
12+
vector_projection,
13+
)
14+
15+
16+
def test_dot_product_vectorized() -> None:
17+
a = np.array([[1, 2, 3], [4, 5, 6]])
18+
b = np.array([[7, 8, 9], [1, 2, 3]])
19+
result = dot_product(a, b)
20+
assert np.allclose(result, np.array([50, 32]))
21+
22+
23+
def test_cross_product_3d() -> None:
24+
result = cross_product([1, 0, 0], [0, 1, 0])
25+
assert np.allclose(result, np.array([0, 0, 1]))
26+
27+
28+
def test_projection_rejects_zero_vector() -> None:
29+
with pytest.raises(ValueError):
30+
vector_projection([1, 2, 3], [0, 0, 0])
31+
32+
33+
def test_angle_between_orthogonal_vectors() -> None:
34+
assert np.isclose(angle_between([1, 0], [0, 1]), np.pi / 2)
35+
36+
37+
def test_gram_schmidt_orthonormality() -> None:
38+
basis = gram_schmidt([[1, 1, 0], [1, 0, 1], [0, 1, 1]])
39+
assert np.allclose(basis @ basis.T, np.eye(3), atol=1e-10)
40+
41+
42+
def test_linear_independence_rank_check() -> None:
43+
independent = check_linear_independence([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
44+
dependent = check_linear_independence([[1, 2, 3], [2, 4, 6]])
45+
assert independent
46+
assert not dependent

0 commit comments

Comments
 (0)