File size: 4,955 Bytes
48ee375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""Deterministic linear-algebra tool: the basic matrix operations every other
analysis builds on. Direct requests like "A times B", "invert this matrix", or
"eigenvalues of A" must never depend on model-written freehand code -- they get
one schema-validated tool call."""

from __future__ import annotations

from typing import Any

import numpy as np

from controlai_agent.registry import registry


@registry.register(
    name="matrix_arithmetic",
    description=(
        "Exact matrix arithmetic on one or two numeric matrices. THE tool for any direct matrix "
        "computation the user states explicitly: 'A times B' / 'A*B' (operation='multiply'), "
        "addition, subtraction, transpose, inverse, determinant, rank, trace, eigenvalues, or "
        "elementwise multiply. Use this instead of writing Python code for plain matrix math. "
        "This is NOT a controller-design tool: a request to multiply or invert matrices is a "
        "linear-algebra question, not an LQR/pole-placement problem."
    ),
    parameters_schema={
        "type": "object",
        "properties": {
            "operation": {
                "type": "string",
                "enum": [
                    "multiply",
                    "add",
                    "subtract",
                    "elementwise_multiply",
                    "transpose",
                    "inverse",
                    "determinant",
                    "rank",
                    "trace",
                    "eigenvalues",
                ],
                "description": "Which operation to perform. Binary operations use matrix_a (op) matrix_b in that order.",
            },
            "matrix_a": {
                "type": "array",
                "items": {"type": "array", "items": {"type": "number"}},
                "description": "First matrix (2D, row-major). For unary operations this is the only operand.",
            },
            "matrix_b": {
                "type": "array",
                "items": {"type": "array", "items": {"type": "number"}},
                "description": "Second matrix, required for multiply / add / subtract / elementwise_multiply.",
            },
        },
        "required": ["operation", "matrix_a"],
    },
)
def matrix_arithmetic(
    operation: str,
    matrix_a: list[list[float]],
    matrix_b: list[list[float]] | None = None,
) -> dict[str, Any]:
    A = np.array(matrix_a, dtype=float)
    binary_ops = {"multiply", "add", "subtract", "elementwise_multiply"}

    if operation in binary_ops:
        if matrix_b is None:
            return {"status": "error", "error": f"operation '{operation}' requires matrix_b."}
        B = np.array(matrix_b, dtype=float)
        if operation == "multiply":
            if A.shape[1] != B.shape[0]:
                return {
                    "status": "error",
                    "error": f"Cannot multiply: matrix_a is {A.shape[0]}x{A.shape[1]} but matrix_b is {B.shape[0]}x{B.shape[1]} (inner dimensions must match).",
                }
            result = A @ B
        elif operation == "elementwise_multiply":
            if A.shape != B.shape:
                return {"status": "error", "error": f"Elementwise multiply needs equal shapes, got {A.shape} and {B.shape}."}
            result = A * B
        else:
            if A.shape != B.shape:
                return {"status": "error", "error": f"'{operation}' needs equal shapes, got {A.shape} and {B.shape}."}
            result = A + B if operation == "add" else A - B
        return {
            "operation": operation,
            "shape_a": list(A.shape),
            "shape_b": list(B.shape),
            "result": result.tolist(),
            "result_shape": list(result.shape),
        }

    # Unary operations
    out: dict[str, Any] = {"operation": operation, "shape_a": list(A.shape)}
    if operation == "transpose":
        out["result"] = A.T.tolist()
    elif operation == "rank":
        out["result"] = int(np.linalg.matrix_rank(A))
    elif operation in ("inverse", "determinant", "trace", "eigenvalues"):
        if A.shape[0] != A.shape[1]:
            return {"status": "error", "error": f"'{operation}' requires a square matrix, got {A.shape[0]}x{A.shape[1]}."}
        if operation == "inverse":
            det = float(np.linalg.det(A))
            if abs(det) < 1e-12:
                return {"status": "error", "error": f"Matrix is singular (determinant = {det:g}); no inverse exists."}
            out["result"] = np.linalg.inv(A).tolist()
            out["determinant"] = det
        elif operation == "determinant":
            out["result"] = float(np.linalg.det(A))
        elif operation == "trace":
            out["result"] = float(np.trace(A))
        else:  # eigenvalues
            eig = np.linalg.eigvals(A)
            out["result"] = [[float(v.real), float(v.imag)] for v in eig]
            out["spectral_radius"] = float(np.max(np.abs(eig)))
    return out