codexrna-sequence-and-folding.py
rna-sequence-and-folding.py
"""Draw an original numbered RNA hairpin; write basename.png in cwd.
Build: make _media/rna-sequence-and-folding.png
Uses the repository's Python 3.14, NumPy and Matplotlib dependencies.
The chosen pairing is illustrative, not an equilibrium-folding prediction.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
COLORS = {"A": "#f3c86a", "U": "#9dd9e8", "G": "#bdb0e2", "C": "#a7d2a2"}
def base(ax, x, y, letter, number, dx=0, dy=-0.45):
ax.text(x, y, letter, ha="center", va="center", fontsize=15,
bbox={"boxstyle": "circle,pad=0.3", "fc": COLORS[letter], "ec": "#526779"},
zorder=3)
ax.text(x + dx, y + dy, str(number), ha="center", va="center", fontsize=9,
color="#526779", zorder=4)
def main():
sequence = "GCAUUUUUAUGC"
pairs = [(1, 12), (2, 11), (3, 10), (4, 9)]
partners = {"A": "U", "U": "A", "G": "C", "C": "G"}
assert len(sequence) == 12
assert all(partners[sequence[i - 1]] == sequence[j - 1] for i, j in pairs)
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
fig, (linear, folded) = plt.subplots(1, 2, figsize=(9.2, 5.5),
gridspec_kw={"width_ratios": [1.45, 1]})
fig.subplots_adjust(left=0.025, right=0.98, top=0.82, bottom=0.2, wspace=0.09)
fig.suptitle("RNA: one continuous strand can pair with itself", fontsize=16, y=0.97)
linear.set(xlim=(-1.0, 12.1), ylim=(-1.9, 2.0))
folded.set(xlim=(-1.15, 3.25), ylim=(-0.75, 5.0), aspect="equal")
for ax in (linear, folded):
ax.axis("off")
linear.set_title("Numbered sequence, read 5′ → 3′", fontsize=12, pad=20)
linear.plot(range(12), [0] * 12, color="#526779", lw=2, zorder=1)
for i, letter in enumerate(sequence):
base(linear, i, 0, letter, i + 1)
linear.text(-0.8, 0, "5′", ha="center", va="center", fontsize=13)
linear.text(11.8, 0, "3′", ha="center", va="center", fontsize=13)
linear.annotate("", (11, -0.9), (0, -0.9),
arrowprops={"arrowstyle": "->", "color": "#176b92", "lw": 1.8})
linear.text(5.5, 1.25, "GCAU + UUUU + AUGC", ha="center", fontsize=15)
linear.text(5.5, -1.5, "4 paired bases + 4 loop bases + 4 paired bases",
ha="center", fontsize=10)
folded.set_title("Illustrative hairpin (stem-loop)", fontsize=12, pad=20)
left = np.column_stack((np.zeros(4), np.arange(4)))
angles = np.linspace(np.pi, 0, 6)[1:-1]
loop = np.column_stack((1 + np.cos(angles), 3 + 1.35 * np.sin(angles)))
right = np.column_stack((np.full(4, 2), np.arange(3, -1, -1)))
coords = np.vstack((left, loop, right))
folded.plot(coords[:, 0], coords[:, 1], color="#526779", lw=2, zorder=1)
for i, j in pairs:
folded.plot([0.32, 1.68], [coords[i - 1, 1]] * 2,
color="#526779", lw=1.8, ls=":", zorder=2)
for i, (letter, (x, y)) in enumerate(zip(sequence, coords), start=1):
if i <= 4:
dx, dy = -0.48, 0
elif i >= 9:
dx, dy = 0.48, 0
else:
dx, dy = 0, 0.42
base(folded, x, y, letter, i, dx, dy)
folded.text(0, -0.5, "5′", ha="center", fontsize=13)
folded.text(2, -0.5, "3′", ha="center", fontsize=13)
for x, start, end in [(-0.84, 0.3, 2.5), (2.85, 2.5, 0.3)]:
folded.annotate("", (x, end), (x, start),
arrowprops={"arrowstyle": "->", "color": "#176b92", "lw": 1.8})
fig.text(0.5, 0.12, "Pairs: 1–12 (G–C), 2–11 (C–G), 3–10 (A–U), 4–9 (U–A)",
ha="center", fontsize=11)
fig.text(0.5, 0.065, "Solid line: backbone Dotted lines: base pairs Numbers: sequence positions",
ha="center", fontsize=10, color="#526779")
fig.text(0.5, 0.025, "Schematic pairing pattern; not a measured shape or a folding prediction",
ha="center", fontsize=10, color="#526779")
fig.savefig(Path(Path(__file__).stem + ".png"), dpi=140,
facecolor="white", transparent=False)
plt.close(fig)
print(f"{sequence}: {len(sequence)} nucleotides, {len(pairs)} pairs, loop positions 5–8")
if __name__ == "__main__":
main()