mathstore 0.1.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.
- mathstore/__init__.py +38 -0
- mathstore/algebra/__init__.py +3 -0
- mathstore/algebra/solver.py +73 -0
- mathstore/api/__init__.py +3 -0
- mathstore/api/main.py +140 -0
- mathstore/api/routes/__init__.py +13 -0
- mathstore/api/routes/algebra.py +74 -0
- mathstore/api/routes/calculus.py +137 -0
- mathstore/api/routes/matrix.py +49 -0
- mathstore/api/routes/statistics.py +197 -0
- mathstore/api/routes/study.py +166 -0
- mathstore/api/schemas.py +251 -0
- mathstore/calculus/__init__.py +3 -0
- mathstore/calculus/analyzer.py +161 -0
- mathstore/cli.py +369 -0
- mathstore/core/__init__.py +4 -0
- mathstore/core/matrix.py +275 -0
- mathstore/core/safe.py +87 -0
- mathstore/reference/__init__.py +13 -0
- mathstore/reference/sheets.py +291 -0
- mathstore/statistics/__init__.py +3 -0
- mathstore/statistics/analyzer.py +435 -0
- mathstore/study/__init__.py +27 -0
- mathstore/study/practice.py +899 -0
- mathstore/study/steps.py +359 -0
- mathstore-0.1.0.dist-info/METADATA +549 -0
- mathstore-0.1.0.dist-info/RECORD +29 -0
- mathstore-0.1.0.dist-info/WHEEL +4 -0
- mathstore-0.1.0.dist-info/entry_points.txt +4 -0
mathstore/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from mathstore.algebra.solver import EquationSolver
|
|
2
|
+
from mathstore.calculus.analyzer import CalculusAnalyzer
|
|
3
|
+
from mathstore.core.matrix import MatrixAnalyzer
|
|
4
|
+
from mathstore.reference import get_reference, list_topics
|
|
5
|
+
from mathstore.statistics.analyzer import StatsAnalyzer
|
|
6
|
+
from mathstore.study import (
|
|
7
|
+
PracticeQuestion,
|
|
8
|
+
PracticeSession,
|
|
9
|
+
generate_question,
|
|
10
|
+
get_derivative_steps,
|
|
11
|
+
get_equation_steps,
|
|
12
|
+
get_integral_steps,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
print("Hello from mathstore!")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CalculusAnalyzer",
|
|
22
|
+
"EquationSolver",
|
|
23
|
+
"MatrixAnalyzer",
|
|
24
|
+
"PracticeQuestion",
|
|
25
|
+
"PracticeSession",
|
|
26
|
+
"StatsAnalyzer",
|
|
27
|
+
"generate_question",
|
|
28
|
+
"get_derivative_steps",
|
|
29
|
+
"get_equation_steps",
|
|
30
|
+
"get_integral_steps",
|
|
31
|
+
"get_reference",
|
|
32
|
+
"list_topics",
|
|
33
|
+
"main",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import sympy as sp
|
|
2
|
+
|
|
3
|
+
from mathstore.core.safe import safe_sympify
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EquationSolver:
|
|
7
|
+
"""Handles algebraic equation solving isolating the sympy implementation."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.x, self.y, self.z = sp.symbols("x y z")
|
|
11
|
+
|
|
12
|
+
def solve_linear(self, equation_str: str, variable: str = "x") -> list:
|
|
13
|
+
"""Solves an equation like '2*x + 4 = 10'."""
|
|
14
|
+
try:
|
|
15
|
+
var = sp.Symbol(variable)
|
|
16
|
+
if "=" not in equation_str:
|
|
17
|
+
raise ValueError("Equation must contain '=' separating left and right sides.")
|
|
18
|
+
parts = equation_str.split("=")
|
|
19
|
+
if len(parts) != 2:
|
|
20
|
+
raise ValueError("Equation must contain exactly one '=' sign.")
|
|
21
|
+
left, right = parts
|
|
22
|
+
eq = sp.Eq(safe_sympify(left.strip()), safe_sympify(right.strip()))
|
|
23
|
+
|
|
24
|
+
return sp.solve(eq, var)
|
|
25
|
+
except Exception as e: # noqa: BLE001
|
|
26
|
+
raise ValueError(f"Failed to parse or solve the equation: {e}") from e
|
|
27
|
+
|
|
28
|
+
def simplify_expression(self, expression_str: str) -> str:
|
|
29
|
+
"""Simplifies algebraic expressions."""
|
|
30
|
+
try:
|
|
31
|
+
expr = safe_sympify(expression_str)
|
|
32
|
+
return str(sp.simplify(expr))
|
|
33
|
+
except Exception as e: # noqa: BLE001
|
|
34
|
+
raise ValueError(f"Failed to parse or simplify the expression: {e}")
|
|
35
|
+
|
|
36
|
+
def format_solution(self, solutions: list, variable: str = "x", format: str = "str") -> str:
|
|
37
|
+
"""
|
|
38
|
+
Formats equation solutions into standard text, LaTeX, or pretty Unicode.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
solutions: List of solutions returned by solve_linear.
|
|
42
|
+
variable: The variable solved for (default: 'x').
|
|
43
|
+
format: Output format ('str', 'latex', or 'pretty').
|
|
44
|
+
"""
|
|
45
|
+
if format == "latex":
|
|
46
|
+
if not solutions:
|
|
47
|
+
return r"\emptyset"
|
|
48
|
+
if len(solutions) == 1:
|
|
49
|
+
return f"{variable} = {sp.latex(solutions[0])}"
|
|
50
|
+
inner = ", ".join(sp.latex(s) for s in solutions)
|
|
51
|
+
return f"{variable} \\in \\left\\{{ {inner} \\right\\}}"
|
|
52
|
+
|
|
53
|
+
if format == "pretty":
|
|
54
|
+
if not solutions:
|
|
55
|
+
return "No solution (∅)"
|
|
56
|
+
if len(solutions) == 1:
|
|
57
|
+
return f"{variable} = {sp.pretty(solutions[0], use_unicode=True)}"
|
|
58
|
+
inner = ", ".join(sp.pretty(s, use_unicode=True) for s in solutions)
|
|
59
|
+
return f"{variable} ∈ {{{inner}}}"
|
|
60
|
+
|
|
61
|
+
return f"Solution: {variable} = {solutions}"
|
|
62
|
+
|
|
63
|
+
def solve_steps(self, equation_str: str, variable: str = "x") -> list[str]:
|
|
64
|
+
"""
|
|
65
|
+
Returns step-by-step algebraic isolation and solution breakdown for an equation.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
equation_str: The equation to solve (e.g., '2*x + 4 = 10').
|
|
69
|
+
variable: The variable to isolate (default: 'x').
|
|
70
|
+
"""
|
|
71
|
+
from mathstore.study.steps import get_equation_steps
|
|
72
|
+
|
|
73
|
+
return get_equation_steps(equation_str, variable=variable)
|
mathstore/api/main.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import uvicorn
|
|
4
|
+
from fastapi import APIRouter, FastAPI
|
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
|
|
7
|
+
from mathstore.api.routes import (
|
|
8
|
+
algebra_router,
|
|
9
|
+
calculus_router,
|
|
10
|
+
matrix_router,
|
|
11
|
+
statistics_router,
|
|
12
|
+
study_router,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_app() -> FastAPI:
|
|
17
|
+
"""Creates and configures the MathStore FastAPI application."""
|
|
18
|
+
app = FastAPI(
|
|
19
|
+
title="MathStore API",
|
|
20
|
+
description=(
|
|
21
|
+
"Clean, modular mathematical API toolkit for university students, educators, "
|
|
22
|
+
"and STEM applications. Provides symbolic calculus, step-by-step derivations, "
|
|
23
|
+
"linear algebra, probability, statistics, reference cheat sheets, and active-recall practice."
|
|
24
|
+
),
|
|
25
|
+
version="0.1.0",
|
|
26
|
+
docs_url="/docs",
|
|
27
|
+
redoc_url="/redoc",
|
|
28
|
+
openapi_url="/openapi.json",
|
|
29
|
+
swagger_ui_oauth2_redirect_url="/docs/oauth2-redirect",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Enable CORS for cross-origin and remote frontend calling
|
|
33
|
+
app.add_middleware(
|
|
34
|
+
CORSMiddleware,
|
|
35
|
+
allow_origins=["*"],
|
|
36
|
+
allow_credentials=True,
|
|
37
|
+
allow_methods=["*"],
|
|
38
|
+
allow_headers=["*"],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Core math router with all mathematical endpoints
|
|
42
|
+
math_router = APIRouter()
|
|
43
|
+
|
|
44
|
+
# Register sub-routers
|
|
45
|
+
math_router.include_router(calculus_router)
|
|
46
|
+
math_router.include_router(algebra_router)
|
|
47
|
+
math_router.include_router(matrix_router)
|
|
48
|
+
math_router.include_router(statistics_router)
|
|
49
|
+
math_router.include_router(study_router)
|
|
50
|
+
|
|
51
|
+
def math_root_info() -> dict[str, Any]:
|
|
52
|
+
"""Root API metadata index for the Math API prefix."""
|
|
53
|
+
return {
|
|
54
|
+
"name": "MathStore API",
|
|
55
|
+
"description": "Mathematical Toolkit API for university study and revision",
|
|
56
|
+
"version": "0.1.0",
|
|
57
|
+
"prefix": "/math",
|
|
58
|
+
"docs_url": "/math/docs",
|
|
59
|
+
"redoc_url": "/math/redoc",
|
|
60
|
+
"openapi_url": "/math/openapi.json",
|
|
61
|
+
"health_url": "/math/health",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def math_health_info() -> dict[str, Any]:
|
|
65
|
+
"""Health check endpoint for the Math API prefix."""
|
|
66
|
+
return {
|
|
67
|
+
"status": "healthy",
|
|
68
|
+
"service": "mathstore",
|
|
69
|
+
"prefix": "/math",
|
|
70
|
+
"version": "0.1.0",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Math prefix health and index endpoints
|
|
74
|
+
@app.get("/math/health", summary="Math API health check")
|
|
75
|
+
@app.get("/api/v1/math/health", summary="API v1 Math health check", include_in_schema=False)
|
|
76
|
+
@app.get("/api/v1/health", summary="API v1 health check", include_in_schema=False)
|
|
77
|
+
def math_health() -> dict[str, Any]:
|
|
78
|
+
return math_health_info()
|
|
79
|
+
|
|
80
|
+
@app.get("/math", summary="Math API Index", include_in_schema=False)
|
|
81
|
+
@app.get("/math/", summary="Math API Index")
|
|
82
|
+
@app.get("/api/v1/math", summary="API v1 Math Index", include_in_schema=False)
|
|
83
|
+
@app.get("/api/v1/math/", summary="API v1 Math Index", include_in_schema=False)
|
|
84
|
+
def math_root() -> dict[str, Any]:
|
|
85
|
+
return math_root_info()
|
|
86
|
+
|
|
87
|
+
# Documentation and schema endpoints for /math prefix
|
|
88
|
+
@app.get("/math/docs", include_in_schema=False)
|
|
89
|
+
def math_docs():
|
|
90
|
+
from fastapi.openapi.docs import get_swagger_ui_html
|
|
91
|
+
return get_swagger_ui_html(
|
|
92
|
+
openapi_url="/math/openapi.json",
|
|
93
|
+
title=app.title + " - Swagger UI",
|
|
94
|
+
oauth2_redirect_url="/docs/oauth2-redirect",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.get("/math/redoc", include_in_schema=False)
|
|
99
|
+
def math_redoc():
|
|
100
|
+
from fastapi.openapi.docs import get_redoc_html
|
|
101
|
+
return get_redoc_html(openapi_url="/math/openapi.json", title=app.title + " - ReDoc")
|
|
102
|
+
|
|
103
|
+
@app.get("/math/openapi.json", include_in_schema=False)
|
|
104
|
+
def math_openapi():
|
|
105
|
+
from fastapi.responses import JSONResponse
|
|
106
|
+
return JSONResponse(content=app.openapi())
|
|
107
|
+
|
|
108
|
+
# Mount mathematical endpoints directly at root: /diff, /integrate, etc.
|
|
109
|
+
app.include_router(math_router)
|
|
110
|
+
|
|
111
|
+
# Mount mathematical endpoints under /math: /math/diff, /math/integrate, etc.
|
|
112
|
+
app.include_router(math_router, prefix="/math")
|
|
113
|
+
|
|
114
|
+
# Maintain backward compatibility for /api/v1/math and /api/v1 callers
|
|
115
|
+
app.include_router(math_router, prefix="/api/v1/math", include_in_schema=False)
|
|
116
|
+
app.include_router(math_router, prefix="/api/v1", include_in_schema=False)
|
|
117
|
+
|
|
118
|
+
# Root informational routes
|
|
119
|
+
@app.get("/", summary="Root API Index", include_in_schema=False)
|
|
120
|
+
def root() -> dict[str, Any]:
|
|
121
|
+
return math_root_info()
|
|
122
|
+
|
|
123
|
+
@app.get("/health", summary="Global health check", include_in_schema=False)
|
|
124
|
+
def global_health() -> dict[str, Any]:
|
|
125
|
+
return {"status": "ok", "service": "mathstore", "version": "0.1.0"}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
return app
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
app = create_app()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def run(host: str = "0.0.0.0", port: int = 8000, reload: bool = False) -> None:
|
|
135
|
+
"""Runs the FastAPI server using uvicorn."""
|
|
136
|
+
uvicorn.run("mathstore.api.main:app", host=host, port=port, reload=reload)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
run()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from mathstore.api.routes.algebra import router as algebra_router
|
|
2
|
+
from mathstore.api.routes.calculus import router as calculus_router
|
|
3
|
+
from mathstore.api.routes.matrix import router as matrix_router
|
|
4
|
+
from mathstore.api.routes.statistics import router as statistics_router
|
|
5
|
+
from mathstore.api.routes.study import router as study_router
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"algebra_router",
|
|
9
|
+
"calculus_router",
|
|
10
|
+
"matrix_router",
|
|
11
|
+
"statistics_router",
|
|
12
|
+
"study_router",
|
|
13
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
2
|
+
|
|
3
|
+
from mathstore.algebra.solver import EquationSolver
|
|
4
|
+
from mathstore.api.schemas import (
|
|
5
|
+
SimplifyRequest,
|
|
6
|
+
SimplifyResponse,
|
|
7
|
+
SolveRequest,
|
|
8
|
+
SolveResponse,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
router = APIRouter(tags=["Algebra"])
|
|
12
|
+
solver = EquationSolver()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.post("/solve", response_model=SolveResponse, summary="Solve an algebraic equation")
|
|
16
|
+
def solve_post(req: SolveRequest) -> SolveResponse:
|
|
17
|
+
"""Solves linear and polynomial equations with optional step-by-step breakdown."""
|
|
18
|
+
try:
|
|
19
|
+
steps_list = (
|
|
20
|
+
solver.solve_steps(req.equation, variable=req.variable)
|
|
21
|
+
if req.steps
|
|
22
|
+
else None
|
|
23
|
+
)
|
|
24
|
+
solutions = solver.solve_linear(req.equation, variable=req.variable)
|
|
25
|
+
formatted = solver.format_solution(
|
|
26
|
+
solutions, variable=req.variable, format=req.format
|
|
27
|
+
)
|
|
28
|
+
return SolveResponse(
|
|
29
|
+
equation=req.equation,
|
|
30
|
+
variable=req.variable,
|
|
31
|
+
solutions=[str(s) for s in solutions],
|
|
32
|
+
formatted=formatted,
|
|
33
|
+
format=req.format,
|
|
34
|
+
steps=steps_list,
|
|
35
|
+
)
|
|
36
|
+
except ValueError as e:
|
|
37
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get("/solve", response_model=SolveResponse, summary="Solve an equation (GET query)")
|
|
41
|
+
def solve_get(
|
|
42
|
+
equation: str = Query(..., description="Equation to solve (e.g. 2*x + 4 = 10)"),
|
|
43
|
+
variable: str = Query("x", description="Variable to isolate"),
|
|
44
|
+
steps: bool = Query(False, description="Include steps"),
|
|
45
|
+
format: str = Query("str", description="Output format"),
|
|
46
|
+
) -> SolveResponse:
|
|
47
|
+
return solve_post(
|
|
48
|
+
SolveRequest(
|
|
49
|
+
equation=equation,
|
|
50
|
+
variable=variable,
|
|
51
|
+
steps=steps,
|
|
52
|
+
format=format,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@router.post("/simplify", response_model=SimplifyResponse, summary="Simplify an algebraic expression")
|
|
58
|
+
def simplify_post(req: SimplifyRequest) -> SimplifyResponse:
|
|
59
|
+
"""Simplifies mathematical expressions."""
|
|
60
|
+
try:
|
|
61
|
+
res = solver.simplify_expression(req.expression)
|
|
62
|
+
return SimplifyResponse(
|
|
63
|
+
expression=req.expression,
|
|
64
|
+
simplified=res,
|
|
65
|
+
)
|
|
66
|
+
except ValueError as e:
|
|
67
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.get("/simplify", response_model=SimplifyResponse, summary="Simplify (GET query)")
|
|
71
|
+
def simplify_get(
|
|
72
|
+
expression: str = Query(..., description="Expression to simplify"),
|
|
73
|
+
) -> SimplifyResponse:
|
|
74
|
+
return simplify_post(SimplifyRequest(expression=expression))
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
2
|
+
|
|
3
|
+
from mathstore.api.schemas import (
|
|
4
|
+
DiffRequest,
|
|
5
|
+
DiffResponse,
|
|
6
|
+
IntegrateRequest,
|
|
7
|
+
IntegrateResponse,
|
|
8
|
+
LimitRequest,
|
|
9
|
+
LimitResponse,
|
|
10
|
+
)
|
|
11
|
+
from mathstore.calculus.analyzer import CalculusAnalyzer
|
|
12
|
+
|
|
13
|
+
router = APIRouter(tags=["Calculus"])
|
|
14
|
+
analyzer = CalculusAnalyzer()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.post("/diff", response_model=DiffResponse, summary="Differentiate a mathematical expression")
|
|
18
|
+
def differentiate_post(req: DiffRequest) -> DiffResponse:
|
|
19
|
+
"""Calculates the nth derivative of an algebraic expression with optional steps."""
|
|
20
|
+
try:
|
|
21
|
+
steps_list = (
|
|
22
|
+
analyzer.differentiate_steps(req.expression, variable=req.variable, order=req.order)
|
|
23
|
+
if req.steps
|
|
24
|
+
else None
|
|
25
|
+
)
|
|
26
|
+
res = analyzer.differentiate(
|
|
27
|
+
req.expression, variable=req.variable, order=req.order, format=req.format
|
|
28
|
+
)
|
|
29
|
+
return DiffResponse(
|
|
30
|
+
expression=req.expression,
|
|
31
|
+
variable=req.variable,
|
|
32
|
+
order=req.order,
|
|
33
|
+
derivative=str(res),
|
|
34
|
+
format=req.format,
|
|
35
|
+
steps=steps_list,
|
|
36
|
+
)
|
|
37
|
+
except ValueError as e:
|
|
38
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.get("/diff", response_model=DiffResponse, summary="Differentiate (GET query)")
|
|
42
|
+
def differentiate_get(
|
|
43
|
+
expression: str = Query(..., description="Expression to differentiate"),
|
|
44
|
+
variable: str = Query("x", description="Variable of differentiation"),
|
|
45
|
+
order: int = Query(1, ge=1, description="Derivative order"),
|
|
46
|
+
steps: bool = Query(False, description="Include steps"),
|
|
47
|
+
format: str = Query("str", description="Output format"),
|
|
48
|
+
) -> DiffResponse:
|
|
49
|
+
return differentiate_post(
|
|
50
|
+
DiffRequest(
|
|
51
|
+
expression=expression,
|
|
52
|
+
variable=variable,
|
|
53
|
+
order=order,
|
|
54
|
+
steps=steps,
|
|
55
|
+
format=format,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@router.post("/integrate", response_model=IntegrateResponse, summary="Integrate an expression")
|
|
61
|
+
def integrate_post(req: IntegrateRequest) -> IntegrateResponse:
|
|
62
|
+
"""Calculates indefinite or definite integrals with optional step derivations."""
|
|
63
|
+
try:
|
|
64
|
+
steps_list = (
|
|
65
|
+
analyzer.integrate_steps(req.expression, variable=req.variable, limits=req.limits)
|
|
66
|
+
if req.steps
|
|
67
|
+
else None
|
|
68
|
+
)
|
|
69
|
+
res = analyzer.integrate(
|
|
70
|
+
req.expression, variable=req.variable, limits=req.limits, format=req.format
|
|
71
|
+
)
|
|
72
|
+
return IntegrateResponse(
|
|
73
|
+
expression=req.expression,
|
|
74
|
+
variable=req.variable,
|
|
75
|
+
limits=req.limits,
|
|
76
|
+
integral=str(res),
|
|
77
|
+
format=req.format,
|
|
78
|
+
steps=steps_list,
|
|
79
|
+
)
|
|
80
|
+
except ValueError as e:
|
|
81
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.get("/integrate", response_model=IntegrateResponse, summary="Integrate (GET query)")
|
|
85
|
+
def integrate_get(
|
|
86
|
+
expression: str = Query(..., description="Expression to integrate"),
|
|
87
|
+
variable: str = Query("x", description="Variable of integration"),
|
|
88
|
+
lower_limit: float | None = Query(None, description="Lower bound for definite integral"),
|
|
89
|
+
upper_limit: float | None = Query(None, description="Upper bound for definite integral"),
|
|
90
|
+
steps: bool = Query(False, description="Include steps"),
|
|
91
|
+
format: str = Query("str", description="Output format"),
|
|
92
|
+
) -> IntegrateResponse:
|
|
93
|
+
limits = (lower_limit, upper_limit) if lower_limit is not None and upper_limit is not None else None
|
|
94
|
+
return integrate_post(
|
|
95
|
+
IntegrateRequest(
|
|
96
|
+
expression=expression,
|
|
97
|
+
variable=variable,
|
|
98
|
+
limits=limits,
|
|
99
|
+
steps=steps,
|
|
100
|
+
format=format,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@router.post("/limit", response_model=LimitResponse, summary="Evaluate a limit")
|
|
106
|
+
def limit_post(req: LimitRequest) -> LimitResponse:
|
|
107
|
+
"""Calculates the limit of an expression approaching a target value."""
|
|
108
|
+
try:
|
|
109
|
+
res = analyzer.get_limit(
|
|
110
|
+
req.expression, limits=req.target, variable=req.variable, format=req.format
|
|
111
|
+
)
|
|
112
|
+
return LimitResponse(
|
|
113
|
+
expression=req.expression,
|
|
114
|
+
target=req.target,
|
|
115
|
+
variable=req.variable,
|
|
116
|
+
limit=str(res),
|
|
117
|
+
format=req.format,
|
|
118
|
+
)
|
|
119
|
+
except ValueError as e:
|
|
120
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@router.get("/limit", response_model=LimitResponse, summary="Evaluate a limit (GET query)")
|
|
124
|
+
def limit_get(
|
|
125
|
+
expression: str = Query(..., description="Expression to evaluate"),
|
|
126
|
+
target: str = Query(..., description="Target value, e.g. 0, oo, -oo"),
|
|
127
|
+
variable: str = Query("x", description="Limit variable"),
|
|
128
|
+
format: str = Query("str", description="Output format"),
|
|
129
|
+
) -> LimitResponse:
|
|
130
|
+
return limit_post(
|
|
131
|
+
LimitRequest(
|
|
132
|
+
expression=expression,
|
|
133
|
+
target=target,
|
|
134
|
+
variable=variable,
|
|
135
|
+
format=format,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
|
|
5
|
+
from mathstore.api.schemas import MatrixRequest, MatrixResponse
|
|
6
|
+
from mathstore.core.matrix import MatrixAnalyzer
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/matrix", tags=["Linear Algebra / Matrix"])
|
|
9
|
+
analyzer = MatrixAnalyzer()
|
|
10
|
+
|
|
11
|
+
VALID_OPERATIONS = {
|
|
12
|
+
"det": analyzer.determinant,
|
|
13
|
+
"inv": analyzer.inverse,
|
|
14
|
+
"rref": analyzer.rref,
|
|
15
|
+
"eigen": analyzer.eigenvalues,
|
|
16
|
+
"eigenvects": analyzer.eigenvectors,
|
|
17
|
+
"rank": analyzer.rank,
|
|
18
|
+
"nullity": analyzer.nullity,
|
|
19
|
+
"trace": analyzer.trace,
|
|
20
|
+
"transpose": analyzer.transpose,
|
|
21
|
+
"charpoly": analyzer.characteristic_polynomial,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.post("/{operation}", response_model=MatrixResponse, summary="Perform matrix operation")
|
|
26
|
+
def matrix_operation(operation: str, req: MatrixRequest) -> MatrixResponse:
|
|
27
|
+
"""Executes a linear algebra operation on a given matrix string."""
|
|
28
|
+
op_lower = operation.lower().strip()
|
|
29
|
+
if op_lower not in VALID_OPERATIONS:
|
|
30
|
+
raise HTTPException(
|
|
31
|
+
status_code=400,
|
|
32
|
+
detail=f"Unsupported matrix operation '{operation}'. Choose from: {list(VALID_OPERATIONS.keys())}",
|
|
33
|
+
)
|
|
34
|
+
try:
|
|
35
|
+
fn = VALID_OPERATIONS[op_lower]
|
|
36
|
+
# rank and nullity do not accept format parameter
|
|
37
|
+
if op_lower in ("rank", "nullity"):
|
|
38
|
+
res: Any = fn(req.matrix)
|
|
39
|
+
else:
|
|
40
|
+
res = fn(req.matrix, format=req.format)
|
|
41
|
+
|
|
42
|
+
return MatrixResponse(
|
|
43
|
+
operation=op_lower,
|
|
44
|
+
matrix=req.matrix,
|
|
45
|
+
result=res,
|
|
46
|
+
format=req.format,
|
|
47
|
+
)
|
|
48
|
+
except (ValueError, TypeError) as e:
|
|
49
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|