BRAGA

02 Writing02-02 Notes02-02-091

Triangle Inequality

Published 16 Apr 202612 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
∣a+b∣≤∣a∣+∣b∣|a+b| \le |a| + |b|
(1)

Deep Breakdown

What it tells me (Situation Awareness): The triangle inequality states that the absolute value of the sum of two real numbers is at most the sum of their absolute values: ∣a+b∣≤∣a∣+∣b∣|a+b| \le |a| + |b|. Equality holds exactly when aa and bb have the same sign (or one is zero). In geometry, it says the direct path between two points is never longer than any detour path — hence the name. In DA/DS, this appears at the foundation of every distance-based algorithm: k-nearest neighbors, DBSCAN, hierarchical clustering, and similarity search all rely on the distance function satisfying the triangle inequality. If your custom distance function violates it, clustering algorithms produce incoherent results, nearest-neighbor search is invalid, and convergence guarantees for metric-based optimizers break. The inequality also appears in error bounding (total error ≤\le sum of individual absolute errors), L1 regularization analysis, and verifying that a feature-distance is a true metric.

Flowchart

Yes (always true)NoTwo realnumbers: a, bCompute |a + b|Compute |a| +|b||a+b| ≤ |a|+|b|?Triangleinequality holdsImpossible forreal numbersEquality: a,bsame sign (orone is zero)Strict inequality:a,b oppositesigns —cancellationoccursDA: requiredproperty for avalid distancemetricKNN, DBSCAN,clustering: allneed this to holdCustomdistance: mustverify beforedeployingError bounding:|total_error| ≤Σ|individualerrors|

Real-world use

Where it is used

Any algorithm or procedure that requires a distance metric: k-nearest neighbors classification and regression, DBSCAN density-based clustering, hierarchical clustering, cosine similarity converted to a distance, Levenshtein edit distance for text data, and any application of the Cauchy-Schwarz inequality in linear algebra. Also used in error analysis: when a model's total prediction error is the sum of a bias component and a variance component, the triangle inequality guarantees that the absolute total error never exceeds the sum of the individual absolute component errors.

Problem it solves

A distance function that violates the triangle inequality will produce non-transitive "nearest neighbor" relationships: A might be closest to B, B closest to C, but A could be arbitrarily far from C. This breaks clustering coherence, makes proximity queries invalid, and invalidates convergence proofs. The triangle inequality is one of four required properties — non-negativity, identity of indiscernibles, symmetry, and triangle inequality — that together define a metric space. Without it, "distance" is just a number with no geometric meaning or algorithmic guarantee.

How it solves the problem

The inequality says: the shortest path between two points is the direct path. In a metric space: d(x,z)≤d(x,y)+d(y,z)d(x, z) \le d(x, y) + d(y, z) for any three points x,y,zx, y, z. The absolute value formulation ∣a+b∣≤∣a∣+∣b∣|a + b| \le |a| + |b| is the one-dimensional case (where d(a,b)=∣a−b∣d(a, b) = |a - b|). When you verify that a proposed distance function dd satisfies this for all triples, you confirm that shortest paths are meaningful and that clustering algorithms will behave as expected.

Example in a business

A data scientist builds a custom weighted distance for a recommendation system: d(u,v)=w1∣u1−v1∣+w2∣u2−v2∣d(u, v) = w_1|u_1 - v_1| + w_2|u_2 - v_2| (weighted L1 / Manhattan distance). Before deploying KNN, they verify the triangle inequality.

For any triple of user vectors u,v,wu, v, w:

d(u,w)=∑iwi∣ui−wi∣≤∑iwi(∣ui−vi∣+∣vi−wi∣)=d(u,v)+d(v,w)d(u, w) = \sum_i w_i|u_i - w_i| \le \sum_i w_i(|u_i - v_i| + |v_i - w_i|) = d(u, v) + d(v, w)
(2)

The key step uses ∣ui−wi∣=∣(ui−vi)+(vi−wi)∣≤∣ui−vi∣+∣vi−wi∣|u_i - w_i| = |(u_i - v_i) + (v_i - w_i)| \le |u_i - v_i| + |v_i - w_i| — which is exactly the triangle inequality applied coordinate-wise. The distance function is valid; KNN deployment is safe.

The formula, piece by piece

Breaking the notation:

  • aa: any real number (first operand).
  • bb: any real number (second operand).
  • a+ba + b: their sum — possibly negative, zero, or positive.
  • ∣⋅∣|\cdot|: absolute value operator — returns the non-negative magnitude.
  • ∣a+b∣|a + b|: the magnitude of the combined quantity.
  • ∣a∣|a|: the magnitude of aa alone.
  • ∣b∣|b|: the magnitude of bb alone.
  • ∣a∣+∣b∣|a| + |b|: sum of the individual magnitudes — always ≥0\ge 0.
  • ≤\le: less than or equal — the left side is bounded above by the right. Equality condition: ∣a+b∣=∣a∣+∣b∣|a+b| = |a| + |b| if and only if aa and bb have the same sign or at least one is zero (ab≥0ab \ge 0).

When they have opposite signs, cancellation reduces ∣a+b∣|a+b| strictly below ∣a∣+∣b∣|a| + |b|. Generalization to nn terms: ∣a1+a2+⋯+an∣≤∣a1∣+∣a2∣+⋯+∣an∣|a_1 + a_2 + \cdots + a_n| \le |a_1| + |a_2| + \cdots + |a_n|. In DA this means the total signed error is bounded by the sum of individual absolute errors — cancellation can only reduce the total, never increase it. Vector form (L1 norm): for vectors u,v∈Rn\mathbf{u}, \mathbf{v} \in \mathbb{R}^n: ∥u+v∥1≤∥u∥1+∥v∥1\|\mathbf{u} + \mathbf{v}\|_1 \le \|\mathbf{u}\|_1 + \|\mathbf{v}\|_1. Manhattan distance satisfies the triangle inequality. So does L2 (Euclidean) and L∞L\infty (Chebyshev).

The formula in words

"The magnitude of the sum of two numbers is always at most the sum of their individual magnitudes. Combining two signed quantities can never produce a total larger than if you just added their absolute sizes — cancellation can only help, never hurt. If both numbers have the same sign, there is no cancellation and you get equality. If they have opposite signs, they partially cancel and the total magnitude is strictly less. In code: abs(a + b) <= abs(a) + abs(b) is always True for real numbers."

Critical Concepts

  1. Metric space requirement: for a function dd to be a valid distance metric, it must satisfy d(x,z)≤d(x,y)+d(y,z)d(x, z) \le d(x, y) + d(y, z) for all x,y,zx, y, z. Euclidean (L2), Manhattan (L1), and Chebyshev (L∞L\infty) distances all satisfy this. A custom distance that fails this property is not a metric and cannot be used with KNN, DBSCAN, or any metric-based algorithm without risk of incorrect results.
  2. Equality when signs match: ∣a+b∣=∣a∣+∣b∣|a+b| = |a| + |b| if and only if ab≥0ab \ge 0. In a prediction error context: if bias error and noise error are both positive (pointing in the same direction), the total error magnitude equals their sum. If they partially cancel, the total is strictly less.
  3. Error bounding: total model error ∣etotal∣=∣ebias+enoise∣≤∣ebias∣+∣enoise∣|e_\text{total}| = |e_\text{bias} + e_\text{noise}| \le |e_\text{bias}| + |e_\text{noise}|. This gives a worst-case upper bound on total error without knowing the sign of each component — useful when you know the magnitude of each source of error but not their direction.
  4. L1 vs. L2 norm: both satisfy the triangle inequality. L1 grows linearly with distance, making it more robust to outliers; L2 grows quadratically and is more sensitive to large deviations. L2 additionally satisfies the Cauchy-Schwarz inequality. Choose based on the geometry of your data, not on which one is easier to compute.
  5. Verifying custom distances: scipy.spatial.distance provides validated implementations of Euclidean, Manhattan, Chebyshev, cosine, and many other metrics.

When building a custom distance, test the triangle inequality empirically over thousands of random triplets before deploying in a clustering or nearest-neighbor pipeline.

Step-by-step calculation (tiny example)

Task: verify the triangle inequality for a=3,b=−7a = 3, b = -7, and compute the corresponding error-bounding interpretation.

Step 1: Compute ∣a+b∣|a+b| a+b=3+(−7)=−4a + b = 3 + (-7) = -4. ∣a+b∣=∣−4∣=4|a + b| = |-4| = 4.

Step 2: Compute ∣a∣+∣b∣|a| + |b| ∣a∣=∣3∣=3|a| = |3| = 3. ∣b∣=∣−7∣=7|b| = |-7| = 7. ∣a∣+∣b∣=3+7=10|a| + |b| = 3 + 7 = 10.

Step 3: Check the inequality 4≤104 \le 10. The inequality holds. Since aa and bb have opposite signs, cancellation occurs and we get strict inequality: 4<104 < 10.

Step 4: Equality case for contrast a=3,b=5a = 3, b = 5 (same sign): ∣3+5∣=8|3+5| = 8, ∣3∣+∣5∣=8|3|+|5| = 8. 8≤88 \le 8. Equality holds.

Step 5: Error bounding interpretation If bias error =3= 3 (systematic overestimate) and noise error =−7= -7 (random component in the opposite direction), the actual total error magnitude is ∣3+(−7)∣=4|3 + (-7)| = 4. The triangle inequality guarantees the total can be at most 3+7=103 + 7 = 10. Actual total is 4, well within the bound. The partial cancellation was beneficial — but you cannot count on it in advance, so the bound of 10 is the safe guarantee.

Code examples

Beginner example (Python)

python
# Triangle inequality verification in Python
import math
import random   # Basic verification for two real numbers
a = 3.0
b = -7.0
left_side = abs(a + b)
right_side = abs(a) + abs(b)
print(f"a = {a}, b = {b}")
print(f"|a + b| = |{a + b}| = {left_side}")
print(f"|a| + |b| = {abs(a)} + {abs(b)} = {right_side}")
print(f"Triangle inequality holds: {left_side <= right_side}")
print(f"Equality (same sign): {math.isclose(left_side, right_side)}")   # Equality case (same sign)
a2 = 3.0
b2 = 5.0
print(f"\na = {a2}, b = {b2} (same sign)")
print(f"|{a2} + {b2}| = {abs(a2 + b2)} |a|+|b| = {abs(a2) + abs(b2)}")
print(f"Equality: {math.isclose(abs(a2 + b2), abs(a2) + abs(b2))}")   # Error bounding: model total error bounded by sum of component errors
bias_error = 3.2   # systematic overestimate
variance_error = -2.8   # noise component (opposite sign)
total_error = bias_error + variance_error
abs_total = abs(total_error)
component_bound = abs(bias_error) + abs(variance_error)
print(f"\nError bounding:")
print(f" bias error = {bias_error:+.2f}")
print(f" variance error = {variance_error:+.2f}")
print(f" total error = {total_error:+.2f} → |total| = {abs_total:.2f}")
print(f" |bias| + |var| = {component_bound:.2f} (upper bound)")
print(f" bound holds: {abs_total <= component_bound}")   # Verify triangle inequality over a large random sample
random.seed(42)
N_TESTS = 10_000
FLOAT_TOL = 1e-10
violations = 0
for _ in range(N_TESTS):
    x = random.uniform(-100, 100)
y = random.uniform(-100, 100)
if abs(x + y) > abs(x) + abs(y) + FLOAT_TOL:
    violations += 1
print(f"\nRandom test: {N_TESTS} pairs — violations: {violations}")

Beginner example (R)

r
# Triangle inequality verification in R # Basic check
a <- 3.0
b <- -7.0
left_side <- abs(a + b)
right_side <- abs(a) + abs(b)
cat(sprintf("a = %.1f, b = %.1f\n", a, b))
cat(sprintf("|a + b| = %.1f\n", left_side))
cat(sprintf("|a| + |b| = %.1f\n", right_side))
cat(sprintf("Holds: %s\n", left_side <= right_side))  # Equality case (same sign)
a2 <- 3.0; b2 <- 5.0
cat(sprintf("\nSame sign: |%.1f + %.1f| = %.1f, sum of abs = %.1f, equal: %s\n", a2, b2, abs(a2 + b2), abs(a2) + abs(b2), isTRUE(all.equal(abs(a2 + b2), abs(a2) + abs(b2)))))  # Error bounding
bias_error <- 3.2
variance_error <- -2.8
total_error <- bias_error + variance_error
abs_total <- abs(total_error)
component_bound <- abs(bias_error) + abs(variance_error)
cat(sprintf("\nError bounding:\n"))
cat(sprintf(" total error magnitude: %.2f\n", abs_total))
cat(sprintf(" component bound: %.2f\n", component_bound))
cat(sprintf(" bound holds: %s\n", abs_total <= component_bound))  # Random verification over many pairs
set.seed(42)
N_TESTS <- 10000
x_vals <- runif(N_TESTS, -100, 100)
y_vals <- runif(N_TESTS, -100, 100)
violations <- sum(abs(x_vals + y_vals) > abs(x_vals) + abs(y_vals) + 1e-10)
cat(sprintf("\nRandom test: %d pairs — violations: %d\n", N_TESTS, violations))

Professional example (Python)

python
import numpy as np
import pandas as pd
from scipy.spatial import distance as scipy_distance
from typing import Callable   # Scenario: validate that candidate distance functions satisfy the triangle inequality
# before deploying in a KNN or clustering pipeline.
# Tests standard metrics (L1, L2, Chebyshev, cosine) and one deliberately broken function.
np.random.seed(42)
N_TRIPLETS = 5_000
N_FEATURES = 10
FLOAT_TOL = 1e-9   # numerical tolerance for floating-point comparisons

def check_triangle_inequality( dist_fn: Callable[[np.ndarray, np.ndarray], float], triplets: np.ndarray, tol: float = FLOAT_TOL,
) -> dict:
    """ Empirically verify the triangle inequality for a distance function over random triplets. Checks: d(x, z) <= d(x, y) + d(y, z) for all (x, y, z) in triplets. Args: dist_fn: Callable taking two 1-D arrays and returning a non-negative float. triplets: Array of shape (n, 3, d) — n triplets of d-dimensional vectors. tol: Numerical tolerance for floating-point comparisons. Returns: Dictionary with n_tested, n_violations, violation_rate, and is_valid_metric flag. """
    n_tested = len(triplets)
    n_violations = 0
    for x, y, z in triplets:
        d_xz = dist_fn(x, z)
    d_xy = dist_fn(x, y)
    d_yz = dist_fn(y, z)
    if d_xz > d_xy + d_yz + tol:
        n_violations += 1
    return { "n_tested": n_tested, "n_violations": n_violations, "violation_rate": n_violations / n_tested, "is_valid_metric": n_violations == 0, }   # Generate random triplets once; reuse for all distance functions
triplets = np.random.randn(N_TRIPLETS, 3, N_FEATURES)   # Distance functions under test
distance_functions = { "euclidean_L2": lambda x, y: scipy_distance.euclidean(x, y), "manhattan_L1": lambda x, y: scipy_distance.cityblock(x, y), "chebyshev_Linf": lambda x, y: scipy_distance.chebyshev(x, y), "cosine_distance": lambda x, y: scipy_distance.cosine(x, y), "BROKEN_dot_abs": lambda x, y: abs(float(np.dot(x, y))),   # NOT a valid metric
}
rows = []
for name, fn in distance_functions.items():
    result = check_triangle_inequality(fn, triplets)
result["distance_fn"] = name
rows.append(result)
report = pd.DataFrame(rows)[ ["distance_fn", "n_tested", "n_violations", "violation_rate", "is_valid_metric"] ]
print("Triangle Inequality Validation Report")
print("=" * 72)
print(report.to_string(index=False))
print( "\nNote: 'BROKEN_dot_abs' (absolute dot product) is NOT a valid metric — " "it violates the triangle inequality and must not be used in KNN or clustering." )

Professional example (R)

r
# Validate triangle inequality for distance functions (L1, L2, Chebyshev, cosine)
set.seed(42)
N_TRIPLETS <- 5000
N_FEATURES <- 10
FLOAT_TOL <- 1e-9
triplets <- array(rnorm(N_TRIPLETS * 3 * N_FEATURES), dim = c(N_TRIPLETS, 3, N_FEATURES))
check_triangle <- function(dist_fn, triplets, tol = FLOAT_TOL) { n_violations <- 0
  for (i in seq_len(dim(triplets)[1])) { x <- triplets[i, 1, ]
    y <- triplets[i, 2, ]
    z <- triplets[i, 3, ]
    d_xz <- dist_fn(x, z)
    d_xy <- dist_fn(x, y)
    d_yz <- dist_fn(y, z)
    if (d_xz > d_xy + d_yz + tol)
    n_violations <- n_violations + 1
  } list(n_tested = dim(triplets)[1], n_violations = n_violations, violation_rate = n_violations / dim(triplets)[1], is_valid_metric = (n_violations == 0))
}
euclidean <- function(x, y) sqrt(sum((x - y)^2))
manhattan <- function(x, y) sum(abs(x - y))
chebyshev <- function(x, y) max(abs(x - y))
cosine_dist <- function(x, y) 1 - sum(x * y) / (sqrt(sum(x^2)) * sqrt(sum(y^2)))
broken_dot_abs <- function(x, y) abs(sum(x * y))
fns <- list(euclidean_L2 = euclidean, manhattan_L1 = manhattan, chebyshev_Linf = chebyshev, cosine_distance = cosine_dist, BROKEN_dot_abs = broken_dot_abs)
report <- do.call(rbind, lapply(names(fns), function(nm) { r <- check_triangle(fnsnm, triplets) data.frame(distance_fn = nm, n_tested = r$n_tested, n_violations = r$n_violations, violation_rate = r$violation_rate, is_valid_metric = r$is_valid_metric)
}))
print(report)
cat("Note: BROKEN_dot_abs is NOT a valid metric — do not use in KNN or clustering.\n")

Source: 1300 p. 22

#algebra#arithmetic-and-absolute-value#properties-of-absolute-value

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close