codexunbonded-fibre-bundle-scaling.py
unbonded-fibre-bundle-scaling.py
"""Original equal-material model, not measured fibre data; save to cwd.
Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Axial loading assumes equal extension of every filament. Bending assumes
independent neutral axes, free inter-filament sliding and equal curvature.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import numpy as np
area = 1.0 # square millimetres of material, excluding voids
young = 70_000.0 # N/mm^2, stipulated illustrative material
length = 100.0 # millimetres
radius = np.sqrt(area / np.pi)
counts = np.logspace(0, 3, 301)
small_radii = radius / np.sqrt(counts)
axial = counts * young * np.pi * small_radii**2 / length
bending = counts * young * np.pi * small_radii**4 / 4
assert np.allclose(axial, 700)
assert np.allclose(bending / bending[0], 1 / counts)
assert np.isclose(bending[0], young * area**2 / (4 * np.pi))
plt.rcParams.update({"font.size": 10, "figure.facecolor": "white"})
fig = plt.figure(figsize=(9.6, 4.8), dpi=100)
grid = fig.add_gridspec(1, 3, width_ratios=[1, 1, 1.8], left=0.055,
right=0.98, top=0.79, bottom=0.27, wspace=0.26)
solid, bundle, scaling = [fig.add_subplot(grid[0, i]) for i in range(3)]
fig.suptitle("Same material area: axial and bending responses scale differently", fontsize=14, y=0.96)
solid.add_patch(Circle((0, 0), radius, facecolor="#d9e9f3", edgecolor="#226c99", lw=1.4))
r = radius / 10
for row in range(10):
for col in range(10):
bundle.add_patch(Circle(((col - 4.5) * 2.15*r, (row - 4.5) * 2.15*r), r,
facecolor="#f3dfd0", edgecolor="#ae581e", lw=0.7))
for ax in [solid, bundle]:
ax.set(xlim=(-0.7, 0.7), ylim=(-0.7, 0.7), xticks=[], yticks=[])
ax.set_aspect("equal")
ax.spines[:].set_visible(False)
solid.set_title("One solid circular rod", fontsize=10)
bundle.set_title("100 sliding filaments", fontsize=10)
solid.set_xlabel("Material area: 1 mm²")
bundle.set_xlabel("Total material area: 1 mm²")
scaling.loglog(counts, axial/axial[0], color="#226c99", lw=2,
label="Axial stiffness: 1")
scaling.loglog(counts, bending/bending[0], color="#ae581e", lw=2,
label="Independent bending: 1/N")
scaling.scatter([100], [0.01], color="#ae581e", s=28, zorder=4)
scaling.set(xlabel="Number of filaments N", ylabel="Ratio to the solid rod",
xlim=(1, 1000), ylim=(0.0008, 1.7))
scaling.legend(loc="lower left", frameon=False, fontsize=9)
scaling.grid(which="major", alpha=0.2)
scaling.spines[["top", "right"]].set_visible(False)
fig.text(0.5, 0.13, "E, material area and length fixed. Bending model permits sliding and neglects friction.",
ha="center", fontsize=10)
fig.text(0.5, 0.065, "Drawings exclude voids from material area; the two load cases use their stated constraints.",
ha="center", fontsize=10)
fig.savefig(Path("unbonded-fibre-bundle-scaling.png"), facecolor="white")
plt.close(fig)