codex/support-polygon-reactions.py

support-polygon-reactions.py
"""Original planar support examples: reaction fractions and a lifted foot."""
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np


def reactions(point):
    x, y = point
    return np.array([1 - x - y, x, y])


def main():
    feet = np.array([[0, 0], [1, 0], [0, 1]])
    inside, outside, walking = np.array([0.3, 0.2]), np.array([0.7, 0.5]), np.array([0.6, 0.6])
    fractions = reactions(inside)
    assert np.allclose(fractions, [0.5, 0.3, 0.2])
    assert np.allclose(fractions @ feet, inside)
    assert np.isclose(np.sum(fractions), 1)
    assert np.allclose(fractions * 98.1, [49.05, 29.43, 19.62])
    assert np.isclose(reactions(outside)[0], -0.2)
    assert np.isclose(reactions(walking)[0], -0.2)
    assert np.isclose(min(inside[0], inside[1], (1 - sum(inside)) / np.sqrt(2)), 0.2)

    fig, (triangle, lifting) = plt.subplots(1, 2, figsize=(10, 4.4), dpi=100,
                                          layout="constrained")
    fig.set_facecolor("white")
    for ax in (triangle, lifting):
        ax.add_patch(Polygon(feet, closed=True, facecolor="#dceaf1", edgecolor="#286f99",
                             linewidth=1.8, zorder=2))
        ax.scatter(feet[:, 0], feet[:, 1], color="#286f99", s=42, zorder=5)
        ax.set(xlim=(-0.12, 1.22), ylim=(-0.12, 1.25), aspect="equal", xlabel="x (m)", ylabel="y (m)")
        ax.set_xticks([0, 0.5, 1])
        ax.set_yticks([0, 0.5, 1])
        ax.grid(alpha=0.15)
    triangle.set_title("Three supports: reactions must be nonnegative")
    for (x, y), label, offset in zip(feet, ["1", "2", "3"], [(7, 4), (7, 4), (7, 4)]):
        triangle.annotate(label, (x, y), xytext=offset, textcoords="offset points", color="#286f99")
    triangle.scatter(*inside, color="#52762a", s=45, zorder=6)
    triangle.annotate("c = (0.30, 0.20)\nN = (49.05, 29.43, 19.62) N", inside,
                      xytext=(0.04, 0.70), fontsize=9,
                      arrowprops={"arrowstyle": "->", "color": "#52762a"})
    triangle.scatter(*outside, color="#9f4b27", marker="x", s=55, linewidth=2, zorder=6)
    triangle.annotate("Outside: (0.70, 0.50)\nwould require N₁ = −19.62 N", outside,
                      xytext=(0.38, 1.05), fontsize=9,
                      arrowprops={"arrowstyle": "->", "color": "#9f4b27"})
    lifting.add_patch(Polygon([[0, 0], [1, 0], [1, 1], [0, 1]], closed=True,
                             facecolor="#eeeeee", edgecolor="#888888", linestyle="--", zorder=1))
    lifting.scatter(1, 1, marker="x", color="#9f4b27", s=65, linewidth=2, zorder=6)
    lifting.annotate("Lifted foot", (1, 1), xytext=(0.65, 1.17), fontsize=9,
                     arrowprops={"arrowstyle": "->", "color": "#9f4b27"})
    lifting.scatter(*walking, color="#9f4b27", s=45, zorder=6)
    lifting.annotate("c = (0.60, 0.60)\nInside square; outside triangle", walking,
                     xytext=(0.05, 0.91), fontsize=9,
                     arrowprops={"arrowstyle": "->", "color": "#9f4b27"})
    lifting.text(0.05, 0.20, "Remaining support region", fontsize=9, color="#286f99")
    lifting.set_title("Lifting a foot changes the support polygon")
    fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()