ROOT
Overview
The ROOT function solves square systems of nonlinear equations by calling scipy.optimize.root. Given equations in unknowns, the solver iteratively searches for a vector such that , where collects the residuals of each equation. This is useful for fixed-point problems, chemical equilibria, and engineering design conditions. This example function is provided as-is without any representation of accuracy.
Usage
To evaluate the function in Excel:
=ROOT(equations, variables)equations(2D list of string, required): Each entry is an equation expressed in terms of variables such asxandythat should evaluate to zero at the solution.variables(2D list of float, required): Initial guess for each variable; must provide the same number of values as there are equations.
The function returns a single-row 2D list containing the solution vector, or a string error message if validation fails or the solver does not converge.
Examples
Example 1: Unit Circle Intersection
Inputs:
| equations | variables | |
|---|---|---|
| x2 + y2 - 1 | 0.5 | 0.5 |
| x - y |
Excel formula:
=ROOT({"x**2 + y**2 - 1","x - y"}, {0.5,0.5})Expected output:
| x | y |
|---|---|
| 0.707 | 0.707 |
Example 2: Coupled Cubic System
Inputs:
| equations | variables | |
|---|---|---|
| x**3 + y - 1 | 0.7 | 0.7 |
| x + y**3 - 1 |
Excel formula:
=ROOT({"x**3 + y - 1","x + y**3 - 1"}, {0.7,0.7})Expected output:
| x | y |
|---|---|
| 0.683 | 0.683 |
Example 3: Alternate Initial Guess on the Unit Circle
Inputs:
| equations | variables | |
|---|---|---|
| x2 + y2 - 1 | -0.5 | -0.5 |
| x - y |
Excel formula:
=ROOT({"x**2 + y**2 - 1","x - y"}, {-0.5,-0.5})Expected output:
| x | y |
|---|---|
| -0.707 | -0.707 |
Example 4: Cubic System with Different Start
Inputs:
| equations | variables | |
|---|---|---|
| x**3 + y - 1 | 0.2 | 0.2 |
| x + y**3 - 1 |
Excel formula:
=ROOT({"x**3 + y - 1","x + y**3 - 1"}, {0.2,0.2})Expected output:
| x | y |
|---|---|
| 0.682 | 0.682 |
Python Code
import math
from typing import List, Union
import numpy as np
from scipy.optimize import root as scipy_root
Number = Union[int, float]
def root(equations: List[List[str]], variables: List[List[Number]]) -> Union[List[List[float]], str]:
"""Solve a square nonlinear system using SciPy's ``root`` solver.
Args:
equations: 2D list of equations written in terms of variable names such as ``x`` and
``y``. Each entry must be a string expression that evaluates to zero at the solution.
variables: 2D list providing the initial guess for each variable. The number of variables
must match the number of equations.
Returns:
Single-row 2D list containing the solution vector, or an error message string if
validation fails or the solver does not converge.
This example function is provided as-is without any representation of accuracy.
"""
if not isinstance(equations, list) or len(equations) == 0:
return "Invalid input: equations must be a 2D list of strings."
# Flatten nested equation lists while retaining only string entries.
flattened_equations: List[str] = []
for row in equations:
if not isinstance(row, list) or len(row) == 0:
return "Invalid input: equations must be a 2D list of strings."
for item in row:
if isinstance(item, str) and item.strip() != "":
flattened_equations.append(item)
if len(flattened_equations) == 0:
return "Invalid input: equations must contain at least one string expression."
# Normalize variables which may be passed as a scalar by the Excel add-in
def normalize_to_2d_list(value):
if not isinstance(value, list):
return [[value]]
return value
variables = normalize_to_2d_list(variables)
if not isinstance(variables, list) or len(variables) == 0 or not isinstance(variables[0], list):
return "Invalid input: variables must be a 2D list of numeric values."
initial_guess = variables[0]
if len(initial_guess) == 0:
return "Invalid input: variables must contain at least one initial guess value."
try:
x0 = np.asarray([float(value) for value in initial_guess], dtype=float)
except (TypeError, ValueError):
return "Invalid input: variables must contain numeric values."
equation_count = len(flattened_equations)
if x0.size != equation_count:
return (
f"Invalid input: number of variables ({x0.size}) must match number of equations ({equation_count})."
)
# Generate readable variable names (x, y, z, x3, x4, ...)
base_names = ["x", "y", "z"]
variable_names: List[str] = []
for index in range(equation_count):
if index < len(base_names):
variable_names.append(base_names[index])
else:
variable_names.append(f"x{index}")
safe_globals = {
name: getattr(math, name)
for name in dir(math)
if not name.startswith("_")
}
safe_globals.update({"np": np, "numpy": np})
def _system(vector: np.ndarray) -> np.ndarray:
local_context = {name: vector[i] for i, name in enumerate(variable_names)}
residuals: List[float] = []
for expression in flattened_equations:
try:
value = eval(expression, safe_globals, local_context)
except Exception:
return np.full(equation_count, np.nan, dtype=float)
try:
numeric_value = float(value)
except (TypeError, ValueError):
return np.full(equation_count, np.nan, dtype=float)
if not math.isfinite(numeric_value):
return np.full(equation_count, np.nan, dtype=float)
residuals.append(numeric_value)
return np.asarray(residuals, dtype=float)
try:
result = scipy_root(_system, x0=x0)
except ValueError as exc:
return f"root error: {exc}"
except Exception as exc:
return f"root error: {exc}"
if not result.success or result.x is None:
message = result.message if hasattr(result, "message") else "Solver did not converge."
return f"root failed: {message}"
if not np.all(np.isfinite(result.x)):
return "root failed: solution contains non-finite values."
solution = [float(value) for value in result.x]
return [solution]Example Workbook
Last updated on