ROOT

Overview

The ROOT function finds solutions to systems of nonlinear equations using SciPy’s scipy.optimize.root solver. Given a set of equations and initial guess values, it determines the variable values where all equations equal zero simultaneously—a fundamental problem in numerical analysis, engineering, and scientific computing.

Root-finding for systems of equations extends the concept of finding zeros of a single function to multiple dimensions. For a system of n equations with n unknowns, the solver seeks a vector \mathbf{x} such that:

\mathbf{F}(\mathbf{x}) = \begin{pmatrix} f_1(x_1, x_2, \ldots, x_n) \\ f_2(x_1, x_2, \ldots, x_n) \\ \vdots \\ f_n(x_1, x_2, \ldots, x_n) \end{pmatrix} = \mathbf{0}

The implementation uses the hybrid Powell method (hybr) by default, which combines the robustness of the steepest descent method with the fast convergence of Newton’s method near the solution. This algorithm, originally implemented in MINPACK, iteratively refines the initial guess using a modified trust-region approach. For more details, see the SciPy optimization documentation.

This function accepts equations as string expressions using standard mathematical notation, including support for common functions like sin, cos, exp, log, and sqrt. The ^ operator is automatically converted to Python’s exponentiation syntax. Variables are named sequentially as x, y, z, x3, x4, and so on, corresponding to the order of initial guess values provided.

Applications include solving equilibrium conditions in chemical systems, intersection of curves and surfaces in computational geometry, steady-state analysis in control systems, and calibration problems in financial modeling.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=ROOT(equations, variables)
  • equations (list[list], required): 2D list of equation strings expressed in terms of variables (x, y, z, etc.) that should evaluate to zero at the solution.
  • variables (list[list], required): 2D list providing initial guess values for each variable. The number of values must match the number of equations.

Returns (list[list]): 2D list [[x1, x2, …]], or error message string.

Examples

Example 1: Circle and line intersection

Inputs:

equations variables
x^2 + y^2 - 1 0.5 0.5
x - y

Excel formula:

=ROOT({"x^2 + y^2 - 1";"x - y"}, {0.5,0.5})

Expected output:

Result
0.70710678 0.70710678

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:

Result
0.68260619 0.68260619

Example 3: Circle and line negative solution

Inputs:

equations variables
x^2 + y^2 - 1 -0.5 -0.5
x - y

Excel formula:

=ROOT({"x^2 + y^2 - 1";"x - y"}, {-0.5,-0.5})

Expected output:

Result
-0.70710678 -0.70710678

Example 4: Coupled cubic with different initial guess

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:

Result
0.68260619 0.68260619

Python Code

import math
import re

import numpy as np
from scipy.optimize import root as scipy_root

def root(equations, variables):
    """
    Solve a square nonlinear system using SciPy's ``root`` solver.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        equations (list[list]): 2D list of equation strings expressed in terms of variables (x, y, z, etc.) that should evaluate to zero at the solution.
        variables (list[list]): 2D list providing initial guess values for each variable. The number of values must match the number of equations.

    Returns:
        list[list]: 2D list [[x1, x2, ...]], or error message string.
    """
    try:
      def to2d(x):
          return [[x]] if not isinstance(x, list) else x

      equations = to2d(equations)

      if not isinstance(equations, list) or len(equations) == 0:
          return "Error: equations must be a 2D list of strings."

      # Flatten nested equation lists while retaining only string entries.
      flattened_equations = []
      for row in equations:
          if not isinstance(row, list) or len(row) == 0:
              return "Error: 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 "Error: equations must contain at least one string expression."

      # Normalize variables which may be passed as a scalar by the Excel add-in
      variables = to2d(variables)
      if not isinstance(variables, list) or len(variables) == 0 or not isinstance(variables[0], list):
          return "Error: variables must be a 2D list of numeric values."

      initial_guess = variables[0]
      if len(initial_guess) == 0:
          return "Error: 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 "Error: variables must contain numeric values."

      equation_count = len(flattened_equations)
      if x0.size != equation_count:
          return (
              f"Error: number of variables ({x0.size}) must match number of equations ({equation_count})."
          )

      # Convert caret to exponentiation once before any processing
      flattened_equations = [re.sub(r'\^', '**', eq) for eq in flattened_equations]

      # Generate readable variable names (x, y, z, x3, x4, ...)
      base_names = ["x", "y", "z"]
      variable_names = []
      for index in range(equation_count):
          if index < len(base_names):
              variable_names.append(base_names[index])
          else:
              variable_names.append(f"x{index}")

      # Complete safe_globals dictionary with all math functions and aliases
      safe_globals = {
          "math": math,
          "np": np,
          "numpy": np,
          "__builtins__": {},
      }
      safe_globals.update({
          name: getattr(math, name)
          for name in dir(math)
          if not name.startswith("_")
      })
      safe_globals.update({
          "sin": np.sin,
          "cos": np.cos,
          "tan": np.tan,
          "asin": np.arcsin,
          "arcsin": np.arcsin,
          "acos": np.arccos,
          "arccos": np.arccos,
          "atan": np.arctan,
          "arctan": np.arctan,
          "sinh": np.sinh,
          "cosh": np.cosh,
          "tanh": np.tanh,
          "exp": np.exp,
          "log": np.log,
          "ln": np.log,
          "log10": np.log10,
          "sqrt": np.sqrt,
          "abs": np.abs,
          "pow": np.power,
          "pi": math.pi,
          "e": math.e,
          "inf": math.inf,
          "nan": math.nan,
      })

      def _system(vector):
          local_context = {name: vector[i] for i, name in enumerate(variable_names)}
          residuals = []
          for expression in flattened_equations:
              value = eval(expression, safe_globals, local_context)
              numeric_value = float(value)
              if not math.isfinite(numeric_value):
                  raise ValueError("Equation evaluation produced a non-finite value.")
              residuals.append(numeric_value)
          return np.asarray(residuals, dtype=float)

      result = scipy_root(_system, x0=x0)

      if not result.success or result.x is None:
          message = result.message if hasattr(result, "message") else "Solver did not converge."
          return f"Error: {message}"

      if not np.all(np.isfinite(result.x)):
          return "Error: solution contains non-finite values."

      solution = [float(value) for value in result.x]
      return [solution]
    except Exception as e:
      return f"Error: {str(e)}"

Online Calculator