Source code for camel.toolkits.sympy_toolkit
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
import json
from typing import List, Optional
from camel.logger import get_logger
from camel.toolkits import FunctionTool
from camel.toolkits.base import BaseToolkit
logger = get_logger(__name__)
[docs]
class SymPyToolkit(BaseToolkit):
r"""A toolkit for performing symbolic computations using SymPy.
This includes methods for Algebraic manipulation calculus
and Linear Algebra.
"""
def __init__(
self,
default_variable: str = 'x',
timeout: Optional[float] = None,
):
r"""Initializes the toolkit with a default variable and logging.
Args:
default_variable (str): The default variable for
operations (default: :obj: `x`)
"""
super().__init__(timeout=timeout)
self.default_variable = default_variable
logger.info(f"Default variable set to: {self.default_variable}")
[docs]
def simplify_expression(self, expression: str) -> str:
r"""Simplifies a mathematical expression.
Args:
expression (str): The mathematical expression to simplify,
provided as a string.
Returns:
str: JSON string containing the simplified mathematical
expression in the `"result"` field. If an error occurs,
the `"status"` field will be set to `"error"` with a
corresponding `"message"`.
"""
import sympy as sp
try:
expr = sp.parsing.sympy_parser.parse_expr(expression)
simplified = sp.simplify(expr)
return json.dumps({"status": "success", "result": str(simplified)})
except Exception as e:
return self.handle_exception("simplify_expression", e)
[docs]
def expand_expression(self, expression: str) -> str:
r"""Expands an algebraic expression.
Args:
expression (str): The algebraic expression to expand,
provided as a string.
Returns:
str: JSON string containing the expanded algebraic expression
in the `"result"` field. If an error occurs, the JSON
string will include an `"error"` field with the corresponding
error message.
"""
import sympy as sp
try:
expr = sp.parsing.sympy_parser.parse_expr(expression)
expanded_expr = sp.expand(expr)
return json.dumps({"result": str(expanded_expr)})
except Exception as e:
return self.handle_exception("expand_expression", e)
[docs]
def factor_expression(self, expression: str) -> str:
r"""Factors an algebraic expression.
Args:
expression (str): The algebraic expression to factor,
provided as a string.
Returns:
str: JSON string containing the factored algebraic expression
in the `"result"` field. If an error occurs, the JSON string
will include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
expr = sp.parsing.sympy_parser.parse_expr(expression)
factored_expr = sp.factor(expr)
return json.dumps({"result": str(factored_expr)})
except Exception as e:
return self.handle_exception("factor_expression", e)
[docs]
def solve_linear_system(
self, equations: List[str], variables: List[str]
) -> str:
r"""Solves a system of linear equations.
Args:
equations (List[str]): A list of strings representing the linear
equations to be solved.
variables (List[str]): A list of strings representing the variables
involved in the equations.
Returns:
str: JSON string containing the solution to the system of equations
in the `"result"` field. Each solution is represented as
a tuple of values corresponding to the variables. If an
error occurs, the JSON string will include an `"error"`
field with the corresponding error message.
"""
import sympy as sp
try:
eqs = [sp.sympify(eq) for eq in equations]
vars = sp.symbols(variables)
solution = sp.linsolve(eqs, vars)
return json.dumps({"result": [str(sol) for sol in solution]})
except Exception as e:
return self.handle_exception("solve_linear_system", e)
[docs]
def solve_nonlinear_system(
self, sympy_equations: List[str], variables: List[str]
) -> str:
r"""Solves a system of nonlinear equations.
Args:
sympy_equations (List[str]): A list of strings representing the
nonlinear equations to be solved. The equation to solve, must
be compatible with SymPy, provided as a string.
variables (List[str]): A list of strings representing the variables
involved in the equations.
Returns:
str: JSON string containing the solutions to the system of
equations in the `"result"` field. Each solution is
represented as a tuple of values corresponding to the
variables. If an error occurs, the JSON string will
include an `"error"` field with the corresponding
error message.
"""
import sympy as sp
try:
eqs = [sp.sympify(eq) for eq in sympy_equations]
vars = sp.symbols(variables)
solution = sp.nonlinsolve(eqs, vars)
return json.dumps({"result": [str(sol) for sol in solution]})
except Exception as e:
return self.handle_exception("solve_nonlinear_system", e)
[docs]
def solve_univariate_inequality(
self, inequality: str, variable: str
) -> str:
r"""Solves a single-variable inequality.
Args:
inequality (str): A string representing the inequality
to be solved.
variable (str): The variable in the inequality.
Returns:
str: JSON string containing the solution to the inequality in the
`"result"` field. The solution is represented in a symbolic
format (e.g., intervals or expressions). If an error occurs,
the JSON string will include an `"error"` field with the
corresponding error message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
ineq = sp.sympify(inequality)
solution = sp.solve_univariate_inequality(ineq, var)
return json.dumps({"result": str(solution)})
except Exception as e:
return self.handle_exception("solve_univariate_inequality", e)
[docs]
def reduce_inequalities(self, inequalities: List[str]) -> str:
r"""Reduces a system of inequalities.
Args:
inequalities (List[str]): A list of strings representing the
inequalities to be reduced.
Returns:
str: JSON string containing the reduced system of inequalities
in the `"result"` field. The solution is represented in
a symbolic format (e.g., combined intervals or expressions).
If an error occurs, the JSON string will include an `"error"`
field with the corresponding error message.
"""
import sympy as sp
try:
ineqs = [sp.sympify(ineq) for ineq in inequalities]
solution = sp.reduce_inequalities(ineqs)
return json.dumps({"result": str(solution)})
except Exception as e:
return self.handle_exception("reduce_inequalities", e)
[docs]
def polynomial_representation(self, expression: str, variable: str) -> str:
r"""Represents an expression as a polynomial.
Args:
expression (str): The mathematical expression to represent as
a polynomial, provided as a string.
variable (str): The variable with respect to which the polynomial
representation will be created.
Returns:
str: JSON string containing the polynomial representation of the
expression in the `"result"` field. The polynomial is returned
in a symbolic format. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
poly = sp.Poly(expr, var)
return json.dumps({"result": str(poly)})
except Exception as e:
return self.handle_exception("polynomial_representation", e)
[docs]
def polynomial_degree(self, expression: str, variable: str) -> str:
r"""Returns the degree of a polynomial.
Args:
expression (str): The polynomial expression for which the degree
is to be determined, provided as a string.
variable (str): The variable with respect to which the degree
of the polynomial is calculated.
Returns:
str: JSON string containing the degree of the polynomial in the
`"result"` field. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
degree = int(sp.degree(expr, var))
return json.dumps({"result": degree})
except Exception as e:
return self.handle_exception("polynomial_degree", e)
[docs]
def polynomial_coefficients(self, expression: str, variable: str) -> str:
r"""Returns the coefficients of a polynomial.
Args:
expression (str): The polynomial expression from which the
coefficients are to be extracted, provided as a string.
variable (str): The variable with respect to which the polynomial
coefficients are determined.
Returns:
str: JSON string containing the list of coefficients of the
polynomial in the `"result"` field. The coefficients are
ordered from the highest degree term to the constant term.
If an error occurs, the JSON string will include an `"error"
field with the corresponding error message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
coeffs = sp.Poly(expr, var).all_coeffs()
return json.dumps({"result": [str(coeff) for coeff in coeffs]})
except Exception as e:
return self.handle_exception("polynomial_coefficients", e)
[docs]
def solve_equation(
self, sympy_equation: str, variable: Optional[str] = None
) -> str:
r"""Solves an equation for a specific variable.
Args:
sympy_equation(str): The equation to solve, must be compatible
with SymPy, provided as a string.
variable (str, optional): The variable to solve for. If not
specified, the function will use the default variable.
Returns:
str: JSON string containing the solutions to the equation in the
`"result"` field. Each solution is represented as a string.
If an error occurs, the JSON string will include an `"error"`
field with the corresponding error message.
"""
import sympy as sp
try:
variable = (
sp.symbols(variable)
if variable
else sp.symbols(self.default_variable)
)
eq = sp.sympify(sympy_equation)
solutions = sp.solve(eq, variable)
return json.dumps({"result": [str(sol) for sol in solutions]})
except Exception as e:
return self.handle_exception("solve_equation", e)
[docs]
def find_roots(self, expression: str) -> str:
r"""Finds the roots of a polynomial or algebraic equation.
Args:
expression (str): The polynomial or algebraic equation for which
the roots are to be found, provided as a string.
Returns:
str: JSON string containing the roots of the expression in the
`"result"` field. The roots are represented as a list of
solutions. If an error occurs, the JSON string will include
a `"status"` field set to `"error"` and a `"message"` field
with the corresponding error description.
"""
import sympy as sp
try:
expr = sp.parsing.sympy_parser.parse_expr(expression)
roots = sp.solve(expr)
return json.dumps({"status": "success", "result": str(roots)})
except Exception as e:
return self.handle_exception("find_roots", e)
[docs]
def differentiate(
self, expression: str, variable: Optional[str] = None
) -> str:
r"""Differentiates an expression with respect to a variable.
Args:
expression (str): The mathematical expression to differentiate,
provided as a string.
variable (str, optional): The variable with respect to which the
differentiation is performed. If not specified, the default
variable is used.
Returns:
str: JSON string containing the derivative of the expression in the
`"result"` field. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
variable = (
sp.symbols(variable)
if variable
else sp.symbols(self.default_variable)
)
expr = sp.parsing.sympy_parser.parse_expr(expression)
derivative = sp.diff(expr, variable)
return json.dumps({"result": str(derivative)})
except Exception as e:
return self.handle_exception("differentiate", e)
[docs]
def integrate(
self, expression: str, variable: Optional[str] = None
) -> str:
r"""Integrates an expression with respect to a variable.
Args:
expression (str): The mathematical expression to integrate,
provided as a string.
variable (str, optional): The variable with respect to which the
integration is performed. If not specified, the default
variable is used.
Returns:
str: JSON string containing the integral of the expression in the
`"result"` field. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
variable = (
sp.symbols(variable)
if variable
else sp.symbols(self.default_variable)
)
expr = sp.parsing.sympy_parser.parse_expr(expression)
integral = sp.integrate(expr, variable)
return json.dumps({"result": str(integral)})
except Exception as e:
return self.handle_exception("integrate", e)
[docs]
def definite_integral(
self, expression: str, variable: str, lower: float, upper: float
) -> str:
r"""Computes the definite integral of an expression within given
bounds.
Args:
expression (str): The mathematical expression to integrate,
provided as a string.
variable (str): The variable with respect to which the definite
integration is performed.
lower (float): The lower limit of the integration.
upper (float): The upper limit of the integration.
Returns:
str: JSON string containing the result of the definite integral
in the `"result"` field. If an error occurs, the JSON string
will include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
integral = sp.integrate(expr, (var, lower, upper))
return json.dumps({"result": str(integral)})
except Exception as e:
return self.handle_exception("definite_integral", e)
[docs]
def series_expansion(
self, expression: str, variable: str, point: float, order: int
) -> str:
r"""Expands an expression into a Taylor series around a given point up
to a specified order.
Args:
expression (str): The mathematical expression to expand, provided
as a string.
variable (str): The variable with respect to which the series
expansion is performed.
point (float): The point around which the Taylor series is
expanded.
order (int): The order up to which the series expansion is
computed.
Returns:
str: JSON string containing the Taylor series expansion of the
expression in the `"result"` field. If an error occurs,
the JSON string will include an `"error"` field with the
corresponding error message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
series = sp.series(expr, var, point, order)
return json.dumps({"result": str(series)})
except Exception as e:
return self.handle_exception("series_expansion", e)
[docs]
def compute_limit(
self,
expression: str,
variable: str,
point: float,
) -> str:
r"""Computes the limit of an expression as a variable approaches
a point.
Args:
expression (str): The mathematical expression for which the limit
is to be computed, provided as a string.
variable (str): The variable with respect to which the limit is
computed.
point (float): The point that the variable approaches.
Returns:
str: JSON string containing the computed limit of the expression
in the `"result"` field. If an error occurs, the JSON string
will include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
limit = sp.limit(expr, var, point)
return json.dumps({"result": str(limit)})
except Exception as e:
return self.handle_exception("compute_limit", e)
[docs]
def find_critical_points(self, expression: str, variable: str) -> str:
r"""Finds the critical points of an expression by setting its
derivative to zero.
Args:
expression (str): The mathematical expression for which critical
points are to be found, provided as a string.
variable (str): The variable with respect to which the critical
points are determined.
Returns:
str: JSON string containing the critical points of the expression
in the `"result"` field. The critical points are returned as a
list of values corresponding to the variable. If an error
occurs, the JSON string will include an `"error"` field with
the corresponding error message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
derivative = sp.diff(expr, var)
critical_points = sp.solve(derivative, var)
return json.dumps(
{"result": [str(point) for point in critical_points]}
)
except Exception as e:
return self.handle_exception("find_critical_points", e)
[docs]
def check_continuity(
self, expression: str, variable: str, point: float
) -> str:
r"""Checks if an expression is continuous at a given point.
Args:
expression (str): The mathematical expression to check for
continuity, provided as a string.
variable (str): The variable with respect to which continuity
is checked.
point (float): The point at which the continuity of the expression
is checked.
Returns:
str: JSON string containing the result of the continuity check in
the `"result"` field. The result will be `"True"` if the
expression is continuous at the given point, otherwise
`"False"`. If an error occurs, the JSON string will include
an `"error"` field with the corresponding error message.
"""
import sympy as sp
try:
var = sp.symbols(variable)
expr = sp.parsing.sympy_parser.parse_expr(expression)
left_limit = sp.limit(expr, var, point, dir='-')
right_limit = sp.limit(expr, var, point, dir='+')
value_at_point = expr.subs(var, point)
is_continuous = left_limit == right_limit == value_at_point
return json.dumps({"result": str(is_continuous)})
except Exception as e:
return self.handle_exception("check_continuity", e)
[docs]
def compute_determinant(self, matrix: List[List[float]]) -> str:
r"""Computes the determinant of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the determinant is to be computed.
Returns:
str: JSON string containing the determinant of the matrix in the
`"result"` field. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
determinant = mat.det()
return json.dumps({"result": str(determinant)})
except Exception as e:
return self.handle_exception("compute_determinant", e)
[docs]
def compute_inverse(self, matrix: List[List[float]]) -> str:
r"""Computes the inverse of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the inverse is to be computed.
Returns:
str: JSON string containing the inverse of the matrix in the
`"result"` field. The inverse is represented in a symbolic
matrix format. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
inverse = mat.inv()
return json.dumps({"result": str(inverse)})
except Exception as e:
return self.handle_exception("compute_inverse", e)
[docs]
def compute_eigenvalues(self, matrix: List[List[float]]) -> str:
r"""Computes the eigenvalues of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the eigenvalues are to be computed.
Returns:
str: JSON string containing the eigenvalues of the matrix in the
`"result"` field. The eigenvalues are represented as a
dictionary where keys are the eigenvalues (as strings) and
values are their multiplicities (as strings). If an error
occurs, the JSON string will include an `"error"` field
with the corresponding error message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
eigenvalues = mat.eigenvals()
return json.dumps(
{"result": {str(k): str(v) for k, v in eigenvalues.items()}}
)
except Exception as e:
return self.handle_exception("compute_eigenvalues", e)
[docs]
def compute_eigenvectors(self, matrix: List[List[float]]) -> str:
r"""Computes the eigenvectors of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the eigenvectors are to be computed.
Returns:
str: JSON string containing the eigenvectors of the matrix in the
`"result"` field. Each eigenvalue is represented as a
dictionary with the following keys:
- `"eigenvalue"`: The eigenvalue (as a string).
- `"multiplicity"`: The multiplicity of the eigenvalue
(as an integer).
- `"eigenvectors"`: A list of eigenvectors
(each represented as a string).
If an error occurs, the JSON string will include an `"error"`
field with the corresponding error message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
eigenvectors = mat.eigenvects()
result = [
{
"eigenvalue": str(eigenvalue),
"multiplicity": multiplicity,
"eigenvectors": [str(v) for v in vectors],
}
for eigenvalue, multiplicity, vectors in eigenvectors
]
return json.dumps({"result": result})
except Exception as e:
return self.handle_exception("compute_eigenvectors", e)
[docs]
def compute_nullspace(self, matrix: List[List[float]]) -> str:
r"""Computes the null space of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the null space is to be computed.
Returns:
str: JSON string containing the null space of the matrix in the
`"result"` field. The null space is represented as a list of
basis vectors, where each vector is given as a string in
symbolic format. If an error occurs, the JSON string will
include an `"error"` field with the corresponding error
message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
nullspace = mat.nullspace()
return json.dumps({"result": [str(vec) for vec in nullspace]})
except Exception as e:
return self.handle_exception("compute_nullspace", e)
[docs]
def compute_rank(self, matrix: List[List[float]]) -> str:
r"""Computes the rank of a matrix.
Args:
matrix (List[List[float]]): A two-dimensional list representing
the matrix for which the rank is to be computed.
Returns:
str: JSON string containing the rank of the matrix in the
`"result"` field. The rank is represented as an integer.
If an error occurs,the JSON string will include an
`"error"` field with the corresponding error message.
"""
import sympy as sp
try:
mat = sp.Matrix(matrix)
rank = mat.rank()
return json.dumps({"result": rank})
except Exception as e:
return self.handle_exception("compute_rank", e)
[docs]
def compute_inner_product(
self, vector1: List[float], vector2: List[float]
) -> str:
r"""Computes the inner (dot) product of two vectors.
Args:
vector1 (List[float]): The first vector as a list of floats.
vector2 (List[float]): The second vector as a list of floats.
Returns:
str: JSON string containing the inner product in the `"result"`
field. If an error occurs, the JSON string will include an
`"error"` field with the corresponding error message.
Raises:
ValueError: If the vectors have different dimensions.
"""
import sympy as sp
try:
# Convert the lists into sympy Matrix objects (column vectors)
v1 = sp.Matrix(vector1)
v2 = sp.Matrix(vector2)
# Check that the vectors have the same dimensions.
if v1.shape != v2.shape:
raise ValueError(
"Vectors must have the same dimensions to compute "
"the inner product."
)
# Compute the dot (inner) product.
inner_product = v1.dot(v2)
return json.dumps({"result": str(inner_product)})
except Exception as e:
return self.handle_exception("compute_inner_product", e)
[docs]
def handle_exception(self, func_name: str, error: Exception) -> str:
r"""Handles exceptions by logging and returning error details.
Args:
func_name (str): The name of the function where the
exception occurred.
error (Exception): The exception object containing
details about the error.
Returns:
str: JSON string containing the error details.
The JSON includes:
- `"status"`: Always set to `"error"`.
- `"message"`: A string representation of the
exception message.
"""
logger.error(f"Error in {func_name}: {error}")
return json.dumps(
{"status": "error", "message": f"Error in {func_name}: {error}"}
)
[docs]
def get_tools(self) -> List[FunctionTool]:
r"""Exposes the tool's methods to the agent framework.
Returns:
List[FunctionTool]: A list of `FunctionTool` objects representing
the toolkit's methods, making them accessible to the agent.
"""
return [
FunctionTool(self.simplify_expression),
FunctionTool(self.expand_expression),
FunctionTool(self.factor_expression),
FunctionTool(self.solve_linear_system),
FunctionTool(self.solve_nonlinear_system),
FunctionTool(self.solve_univariate_inequality),
FunctionTool(self.reduce_inequalities),
FunctionTool(self.polynomial_representation),
FunctionTool(self.polynomial_degree),
FunctionTool(self.polynomial_coefficients),
FunctionTool(self.solve_equation),
FunctionTool(self.find_roots),
FunctionTool(self.differentiate),
FunctionTool(self.integrate),
FunctionTool(self.definite_integral),
FunctionTool(self.series_expansion),
FunctionTool(self.compute_limit),
FunctionTool(self.find_critical_points),
FunctionTool(self.check_continuity),
FunctionTool(self.compute_determinant),
FunctionTool(self.compute_inverse),
FunctionTool(self.compute_eigenvalues),
FunctionTool(self.compute_eigenvectors),
FunctionTool(self.compute_nullspace),
FunctionTool(self.compute_rank),
FunctionTool(self.compute_inner_product),
]