DBLQUAD
Overview
The DBLQUAD function computes the double integral of a function over a two-dimensional region defined by the integration limits for both variables. This wrapper around scipy.integrate.dblquad evaluates expressions supplied as strings, exposing the essential tolerances while keeping the interface Excel-friendly.
The double integral is calculated as:
where is evaluated with as the first variable and as the second. Unlike the SciPy function, which expects Python callables, this wrapper accepts expression strings (case-insensitive for variables and standard math functions) and converts them to callables internally. This example function is provided as-is without any representation of accuracy.
Usage
To use the function in Excel:
=DBLQUAD(func_expr, a, b, gfun_expr, hfun_expr, [epsabs], [epsrel])func_expr(str, required): Expression for . Usexandy(any case). The expression must evaluate to a numeric value.a(float, required): Lower limit of integration for . Must be finite.b(float, required): Upper limit of integration for . Must satisfyb >= aand be finite.gfun_expr(str or float, required): Expression or constant for the lower boundary . Strings may referencex(any case).hfun_expr(str or float, required): Expression or constant for the upper boundary . Strings may referencex(any case).epsabs(float, optional, default=1.49e-8): Absolute tolerance forwarded toscipy.integrate.dblquad.epsrel(float, optional, default=1.49e-8): Relative tolerance forwarded toscipy.integrate.dblquad.
The function returns a float representing the computed integral, or an error message string when validation fails or SciPy reports an issue.
Examples
Example 1: Rectangular Region
This example computes the double integral of over and .
Inputs:
| func_expr | a | b | gfun_expr | hfun_expr |
|---|---|---|---|---|
| x * y^2 | 0 | 2 | 0 | 1 |
Excel formula:
=DBLQUAD("x * y^2", 0, 2, 0, 1)Expected output:
| Result |
|---|
| 0.667 |
Example 2: Curved Boundaries
This example integrates over with between and , tightening the absolute tolerance.
Inputs:
| func_expr | a | b | gfun_expr | hfun_expr | epsabs |
|---|---|---|---|---|---|
| 1 | 0 | 0.7853981633974483 | sin(x) | cos(x) | 1E-9 |
Excel formula:
=DBLQUAD("1", 0, PI()/4, "SIN(X)", "COS(X)", 1E-9)Expected output:
| Result |
|---|
| 0.414 |
Example 3: Gaussian Over a Square
This example evaluates over the square with both tolerances refined.
Inputs:
| func_expr | a | b | gfun_expr | hfun_expr | epsabs | epsrel |
|---|---|---|---|---|---|---|
| exp(-(x^2 + y^2)) | -1 | 1 | -1 | 1 | 1E-9 | 1E-9 |
Excel formula:
=DBLQUAD("EXP(-(X^2 + Y^2))", -1, 1, -1, 1, 1E-9, 1E-9)Expected output:
| Result |
|---|
| 2.231 |
Example 4: Linear Plane With Relative Tolerance
This example computes over and while only overriding the relative tolerance.
Inputs:
| func_expr | a | b | gfun_expr | hfun_expr | epsrel |
|---|---|---|---|---|---|
| 3 * x * y | 0 | 1 | x | 2 - x | 1E-9 |
Excel formula:
=DBLQUAD("3 * X * Y", 0, 1, "X", "2 - X", , 1E-9)Expected output:
| Result |
|---|
| 1.000 |
Python Code
import math
import re
from scipy.integrate import dblquad as scipy_dblquad
def dblquad(
func_expr,
a,
b,
gfun_expr,
hfun_expr,
epsabs=1.49e-8,
epsrel=1.49e-8,
):
"""
Compute the double integral of a function over a two-dimensional region.
Args:
func_expr (str): Expression for the function f(y, x) to integrate. Use 'x' and 'y' (case-insensitive) as variables.
a (float): Lower limit for x.
b (float): Upper limit for x.
gfun_expr (str or float): Lower boundary for y as a function of x or a constant.
hfun_expr (str or float): Upper boundary for y as a function of x or a constant.
epsabs (float, optional): Absolute tolerance. Default is 1.49e-8.
epsrel (float, optional): Relative tolerance. Default is 1.49e-8.
Returns:
float: The computed double integral, or an error message (str) if invalid.
This example function is provided as-is without any representation of accuracy.
"""
def _convert_float(value, name):
try:
converted = float(value)
except Exception:
return f"Invalid input: {name} must be a number."
if math.isnan(converted) or math.isinf(converted):
return f"Invalid input: {name} must be finite."
return converted
def _parse_callable(expr, varnames):
# expr must be a string expression that can be evaluated to a numeric value
if not isinstance(expr, str):
return "Invalid input: expression arguments must be strings."
processed = re.sub(r"\^", "**", expr.strip())
if not processed:
return "Invalid input: expression arguments must not be empty."
allowed = {"__builtins__": {}}
for _name in dir(math):
if _name.startswith("_"):
continue
func = getattr(math, _name)
allowed[_name] = func
allowed[_name.upper()] = func
for varname in varnames:
allowed[varname] = 0.0
allowed[varname.upper()] = 0.0
try:
code = compile(processed, "<dblquad_expr>", "eval")
except Exception:
return "Invalid input: unable to parse expression."
def _callable(*args):
local_env = {}
for var, value in zip(varnames, args):
numeric = float(value)
local_env[var] = numeric
local_env[var.upper()] = numeric
try:
result = eval(code, allowed, local_env)
except Exception as exc: # noqa: BLE001 - need to surface parse errors cleanly
raise ValueError(f"Expression evaluation failed: {exc}") from exc
if isinstance(result, bool):
return float(result)
if isinstance(result, (int, float)):
numeric = float(result)
if math.isnan(numeric) or math.isinf(numeric):
raise ValueError("Expression produced non-finite value.")
return numeric
raise ValueError("Expression must evaluate to a numeric value.")
return _callable
# Validate scalar numeric inputs
converted_a = _convert_float(a, "a")
if isinstance(converted_a, str):
return converted_a
converted_b = _convert_float(b, "b")
if isinstance(converted_b, str):
return converted_b
if converted_b < converted_a:
return "Invalid input: lower limit must be less than or equal to upper limit."
converted_epsabs = _convert_float(epsabs, "epsabs")
if isinstance(converted_epsabs, str):
return converted_epsabs
converted_epsrel = _convert_float(epsrel, "epsrel")
if isinstance(converted_epsrel, str):
return converted_epsrel
parsed_func = _parse_callable(func_expr, ["y", "x"])
if isinstance(parsed_func, str):
return parsed_func
if isinstance(gfun_expr, str):
parsed_gfun = _parse_callable(gfun_expr, ["x"])
if isinstance(parsed_gfun, str):
return parsed_gfun
else:
g_value = _convert_float(gfun_expr, "gfun_expr")
if isinstance(g_value, str):
return g_value
def parsed_gfun(x: float) -> float:
return g_value
if isinstance(hfun_expr, str):
parsed_hfun = _parse_callable(hfun_expr, ["x"])
if isinstance(parsed_hfun, str):
return parsed_hfun
else:
h_value = _convert_float(hfun_expr, "hfun_expr")
if isinstance(h_value, str):
return h_value
def parsed_hfun(x: float) -> float:
return h_value
try:
result, _ = scipy_dblquad(
parsed_func,
converted_a,
converted_b,
parsed_gfun,
parsed_hfun,
epsabs=converted_epsabs,
epsrel=converted_epsrel,
)
except ValueError as error:
return f"Invalid input: {error}"
except Exception as exc: # noqa: BLE001 - want to surface SciPy errors
return f"scipy.integrate.dblquad error: {exc}"
if not isinstance(result, (int, float)):
return "scipy.integrate.dblquad error: non-numeric result."
numeric_result = float(result)
if math.isnan(numeric_result) or math.isinf(numeric_result):
return "scipy.integrate.dblquad error: result is not finite."
return numeric_result