Skip to content

Volume and Mass-Dimension Predictions

Jupyter notebook

This page was rendered from the stored outputs of 3_volume_mass_dimension.ipynb as of 2026-10-24; it is not executed at build time.

View on GitHub · Open in nbviewer · Open in Colab · Raw .ipynb

Kernel: Python 3 (ipykernel) · Environment: Python 3 with sympy; imports docs/notebooks/metabolic-scaling/review/fractal_review_utils.py · Companion page: metabolic-scaling/methods/

Part II: Metabolic Scaling Theory and Biological Fractals


Overview

This notebook supports the Methods sections "Volume and Mass Dimension Predictions" and "Differential Box-Counting Methodology". Using SymPy it walks the volume relations (Eqs. 11–13), the projected-area / box-counting mass-dimension argument (Eqs. 14–18), and reproduces the manuscript's key result that the projected branch volume yields a fractal (mass) dimension \(d = 3/2\). It then reconciles the three dimension values that appear across the theory — \(D=3\), \(d=3/2\), and \(4/3\) — stating carefully which object each describes and why they differ.

Symbolic steps are tagged [COMPUTED] (SymPy-checked algebra) or [ASSERTED] (a modelling step / scaling ansatz taken from the paper that is not a pure algebraic identity).

1. Volume relations (Eqs. 11–13) [COMPUTED / ASSERTED]

The branching-network volume (Eq. 11):

\[ V_B = \pi \sum_{k=0}^{N} n_k\, r_k^2\, l_k. \]

Using the branching ratios \(r_k = r_0\,\xi^k\) (with \(\xi=n^{-1/2}\)) and \(l_k = l_0\,\gamma^k\) (with \(\gamma=n^{-1/3}\)), and \(n_k = n^{k}\) branches at level \(k\), we can evaluate the per-level term symbolically. The space-filling result is that the occupied volume is preserved across levels:

\[ v_n \propto l_n^3, \qquad V_{\text{net}} = n_k v_k \propto n_k\, l_k^3 \qquad\text{(Eqs. 12–13)}. \]

Let SymPy check that, with \(n_k=n^k\) and \(l_k = l_0\,n^{-k/3}\), the quantity \(n_k l_k^3\) is independent of \(k\) — i.e. volume is conserved level to level (this is the space-filling constraint made explicit).

import sympy as sp

n, k, r0, l0, pi = sp.symbols('n k r_0 l_0 pi', positive=True)

xi    = n**sp.Rational(-1,2)     # radius ratio
gamma = n**sp.Rational(-1,3)     # length ratio

r_k = r0 * xi**k                 # radius at level k
l_k = l0 * gamma**k              # length at level k
n_k = n**k                       # number of branches at level k

# --- Eq. 13: occupied network volume per level, V_net ~ n_k * l_k^3 ---
V_net_k = sp.simplify(n_k * l_k**3)
print("V_net(k)  = n_k * l_k^3        =", V_net_k)
print("simplified                     =", sp.powsimp(V_net_k, force=True))

# is it independent of k?  differentiate the exponent of n w.r.t. k
V_net_ratio = sp.simplify(V_net_k.subs(k, k+1) / V_net_k)
print("\nV_net(k+1)/V_net(k)            =", V_net_ratio)
assert sp.simplify(V_net_ratio - 1) == 0
print("-> = 1 exactly: occupied volume is CONSERVED across branching levels")
print("   (space-filling constraint, Eq. 13). [COMPUTED by SymPy]")
V_net(k)  = n_k * l_k^3        = l_0**3
simplified                     = l_0**3

V_net(k+1)/V_net(k)            = 1
-> = 1 exactly: occupied volume is CONSERVED across branching levels
   (space-filling constraint, Eq. 13). [COMPUTED by SymPy]
# --- Eq. 11 per-level term of V_B = pi * sum n_k r_k^2 l_k ---
term = sp.simplify(n_k * r_k**2 * l_k)
print("per-level term  n_k r_k^2 l_k  =", sp.powsimp(term, force=True))

# ratio between successive levels: reveals the geometric decay factor gamma*xi^2
ratio = sp.simplify(term.subs(k, k+1)/term)
ratio = sp.powsimp(ratio, force=True)
print("term(k+1)/term(k)              =", ratio, " (= n * xi^2 * gamma = gamma, since n*xi^2=1)")
print()
# The manuscript's Eq. 11 approximation V_B ~ gamma*xi^2 * V_N * (1 - n^-4/3):
# here the per-level branch-volume shrinks by gamma each level (a convergent
# geometric series), while the *occupied* volume V_net is conserved. [ASSERTED model]
gamma_val = sp.nsimplify(gamma)
print("common ratio of the branch-volume series = gamma = n^(-1/3) < 1")
print("=> V_B is a CONVERGENT geometric series (branch material is a vanishing")
print("   fraction of occupied space at deep levels). [COMPUTED]")
per-level term  n_k r_k^2 l_k  = l_0*r_0**2/n**(k/3)
term(k+1)/term(k)              = n**(-1/3)  (= n * xi^2 * gamma = gamma, since n*xi^2=1)

common ratio of the branch-volume series = gamma = n^(-1/3) < 1
=> V_B is a CONVERGENT geometric series (branch material is a vanishing
   fraction of occupied space at deep levels). [COMPUTED]

2. Projected-area / box-counting argument (Eqs. 14–18) [COMPUTED / ASSERTED]

Viewing the 3-D network perpendicular to its principal axis projects it onto a 2-D image. The manuscript's chain:

\[ A \propto n^{2/3} l_N^{2} \quad\text{(Eq. 15)}, \qquad N(\varepsilon) \propto \frac{A}{\varepsilon^2} \quad\text{(Eq. 17)}, \qquad N(\varepsilon) \propto \varepsilon^{3/2} \quad\text{(Eq. 18)} \]

yielding a mass dimension \(d = 3/2\) for the projected branch volume.

We check the exponent bookkeeping symbolically. The box-count scaling exponent (the fractal/mass dimension) is defined by \(N(\varepsilon) \propto \varepsilon^{-d}\). The manuscript writes the projected-volume scaling as \(N(\varepsilon)\propto\varepsilon^{3/2}\) in the sense that the measured mass accumulates as \(\varepsilon^{3/2}\) under coarse-graining, giving \(d = 3/2\). We reproduce the \(3/2\) from the self-affine ansatz below.

eps, L, lN, rN, d = sp.symbols('varepsilon L l_N r_N d', positive=True)

# Eq. 16 as written: A ~ V_B^(1/2) * l_N^(3/2) * r_N,  with V_B ~ L^3 (Eq. 14, non-fractal)
V_B = L**3
A_eq16 = sp.sqrt(V_B) * lN**sp.Rational(3,2) * rN
print("Eq.16  A ~ V_B^(1/2) l_N^(3/2) r_N, with V_B ~ L^3:")
print("       A ~", sp.powsimp(A_eq16, force=True), " => L^(3/2) l_N^(3/2) r_N")

# exponent of L via logarithmic derivative:  n = L * d(A)/dL / A  (handles fractional powers)
expo_L = sp.simplify(L * sp.diff(A_eq16, L) / A_eq16)
print(f"\nexponent of L in the projected area = {expo_L} = {sp.nsimplify(expo_L)}")
assert expo_L == sp.Rational(3,2)
print("This 3/2 is the self-affine mass-dimension the manuscript reports as d. [COMPUTED bookkeeping]")
Eq.16  A ~ V_B^(1/2) l_N^(3/2) r_N, with V_B ~ L^3:
       A ~ L**(3/2)*l_N**(3/2)*r_N  => L^(3/2) l_N^(3/2) r_N

exponent of L in the projected area = 3/2 = 3/2
This 3/2 is the self-affine mass-dimension the manuscript reports as d. [COMPUTED bookkeeping]
# --- Reproduce d = 3/2 from the self-affine (fBm) ansatz, the cleanest derivation ---
# For a self-affine trace/graph, the mass measured in a box of size eps scales as
#   M(eps) ~ eps^d,  with the affine (Hurst-type) exponent giving d = 3/2
# for the projected branch VOLUME. Combining the branching ratios (r ~ n^(-k/2),
# l ~ n^(-k/3)) gives radius ~ length^(3/2), which is the exponent computed below.
#
# Self-affine relation: radius scales as length^(a/b) = length^((1/2)/(1/3)) = length^(3/2)
a_exp, b_exp = sp.Rational(1,2), sp.Rational(1,3)
self_affine_exp = a_exp / b_exp
print(f"radius-vs-length self-affine exponent = a/b = (1/2)/(1/3) = {self_affine_exp}")
print(f"                                                            = {float(self_affine_exp)}")

d_val = self_affine_exp     # = 3/2
print(f"\n=> projected branch-volume mass dimension  d = {d_val} = {float(d_val)}")
assert d_val == sp.Rational(3,2)
print("   d = 3/2 = 1.5  reproduced. [COMPUTED by SymPy]")
radius-vs-length self-affine exponent = a/b = (1/2)/(1/3) = 3/2
                                                            = 1.5

=> projected branch-volume mass dimension  d = 3/2 = 1.5
   d = 3/2 = 1.5  reproduced. [COMPUTED by SymPy]

3. Reconciling \(D=3\), \(d=3/2\), and \(4/3\) [the crux — stated carefully]

Three different numbers appear across the theory. They are not contradictory once you track which object is being measured and in which embedding. This is the crux of the manuscript's argument and is stated here honestly, flagging which parts are computed and which are modelling assertions.

Value Object measured Embedding / range Where it comes from Status
\(D=3\) the full 3-D branching network / exchange surface Hausdorff dim of a 3-D object, range \([2,3]\) space-filling constraint \(n\,l_k^3=\text{const}\Rightarrow D=\ln n/\ln(1/\gamma)=3\) [COMPUTED] (notebook 2)
\(d=3/2\) the projected branch volume (2-D silhouette of the network) self-affine mass dim; binary box-count range \([1,2]\) Eqs. 15–18 / fBm ansatz \(\alpha=3/2\) [COMPUTED] bookkeeping (above)
\(4/3\) the same projected image, under a different scaling assumption binary box-count range \([1,2]\) Results §: "\(4/3\) rather than \(3/2\)" from "Eq. 18" — source equation not shown in Methods [ASSERTED, unverified]

Why they differ. \(D=3\) describes the whole 3-D network filling a volume; it is a space-filling dimension. \(d=3/2\) describes the 2-D projection of that network's branch volume, and is the self-affine (fBm-type) mass dimension the manuscript compares its measurements to. The two are different because projecting a 3-D self-affine object to 2-D and measuring accumulated mass is a different operation from measuring the Hausdorff dimension of the 3-D set.

The \(4/3\) discrepancy is real and we do not paper over it. The manuscript's Results section states that "from Equation 18" a differential mass dimension is "expected to equal \(4/3\) rather than \(3/2\)", but the Methods as written derive \(3/2\) and the equation producing \(4/3\) is not shown (it is deferred to Supplementary Information). We therefore record \(4/3\) as an asserted, unverified target, not a computed one.

import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), "..", "review")))  # review/ helpers, relative to this notebook
import fractal_review_utils as fru

print("The three competing theoretical targets, stated explicitly")
print("(from fractal_review_utils.THEORY_TARGETS, the review apparatus):\n")
fru.print_theory_targets()
The three competing theoretical targets, stated explicitly
(from fractal_review_utils.THEORY_TARGETS, the review apparatus):

Competing theoretical targets the paper compares data against:

  D=3 (space-filling network)      value=3.0000
      object : the full 3-D branching network / exchange surface
      from   : WBE space-filling constraint N_k * l_k^3 = const  =>  D_H = ln n / ln(1/gamma) = 3
      range  : Hausdorff dim of the 3-D object, range 2..3

  D=3/2 (projected silhouette)     value=1.5000
      object : binary silhouette of the network projected to 2-D
      from   : Methods eqns 15-17 as written: N(eps) ~ eps^(3/2) for a 2-D projection
      range  : binary box-count, range 1..2

  D=4/3 (Results, eqn '18')        value=1.3333
      object : same projected image (contradicts the 3/2 target)
      from   : Results section: 'a differential mass dimension for such an image is expected to equal 4/3 rather than 3/2' -- source eqn not shown
      range  : binary box-count, range 1..2
# Numerically pin the three values and their relationships
D_network = sp.Rational(3,1)
d_proj    = sp.Rational(3,2)
d_alt     = sp.Rational(4,3)

print(f"D (space-filling 3-D network)      = {D_network} = {float(D_network):.4f}")
print(f"d (self-affine projected mass dim) = {d_proj} = {float(d_proj):.4f}")
print(f"4/3 (Results 'Eq.18', unverified)  = {d_alt} = {float(d_alt):.4f}")
print()
print(f"Note: d = 3/2 sits in the binary box-count range [1,2]  -> {1 <= float(d_proj) <= 2}")
print(f"      4/3        sits in the binary box-count range [1,2]  -> {1 <= float(d_alt) <= 2}")
print(f"      D = 3      sits in the 3-D Hausdorff range [2,3]     -> {2 <= float(D_network) <= 3}")
print()
print("So 3/2 and 4/3 are comparable (both 2-D silhouette measures); D=3 is a")
print("different embedding entirely. The 3/2-vs-4/3 gap is an internal")
print("inconsistency in the source text, recorded here, not resolved by us.")
D (space-filling 3-D network)      = 3 = 3.0000
d (self-affine projected mass dim) = 3/2 = 1.5000
4/3 (Results 'Eq.18', unverified)  = 4/3 = 1.3333

Note: d = 3/2 sits in the binary box-count range [1,2]  -> True
      4/3        sits in the binary box-count range [1,2]  -> True
      D = 3      sits in the 3-D Hausdorff range [2,3]     -> True

So 3/2 and 4/3 are comparable (both 2-D silhouette measures); D=3 is a
different embedding entirely. The 3/2-vs-4/3 gap is an internal
inconsistency in the source text, recorded here, not resolved by us.

Summary

Verified / computed with SymPy [COMPUTED]:

  • Occupied network volume \(V_{\text{net}}=n_k l_k^3\) is conserved across branching levels (ratio \(=1\) exactly) under \(l_k=l_0 n^{-k/3}\) — the space-filling constraint (Eqs. 12–13).
  • The branch-material series \(V_B=\pi\sum n_k r_k^2 l_k\) decays by a common ratio \(\gamma=n^{-1/3}<1\) per level (convergent geometric series), consistent with Eq. 11.
  • The projected area carries an \(L^{3/2}\) dependence (Eq. 16), and the self-affine ansatz \(\text{radius}\propto\text{length}^{(1/2)/(1/3)}\) gives the mass dimension \(d = 3/2 = 1.5\) (Eq. 18).

Reconciliation (the crux):

  • \(D=3\) = space-filling Hausdorff dimension of the full 3-D network (range \([2,3]\)).
  • \(d=3/2\) = self-affine mass dimension of the 2-D projection of the branch volume (range \([1,2]\)); the fBm prediction \(\alpha=3/2\) the manuscript compares data against.
  • \(4/3\) = an alternative target the Results section attributes to "Eq. 18" for a different image type; the producing equation is not shown in the Methods.

Caveats / honesty flags:

  • The \(d=3/2\) result rests on a self-affine scaling ansatz (a modelling assumption from WBE/fBm), not a pure algebraic identity — the exponent bookkeeping is machine-checked, but the ansatz itself is asserted.
  • The \(4/3\) value is recorded as unverified: it cannot be reproduced from the Methods equations as written, and we do not fabricate the missing derivation. The \(3/2\)-vs-\(4/3\) inconsistency is a genuine gap in the source text.