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 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,3 @@
1
+ from mathstore.algebra.solver import EquationSolver
2
+
3
+ __all__ = ["EquationSolver"]
@@ -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)
@@ -0,0 +1,3 @@
1
+ from mathstore.api.main import app, create_app, run
2
+
3
+ __all__ = ["app", "create_app", "run"]
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