study-buddy / app /services /nucleic_compiler.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
2.73 kB
from __future__ import annotations
import math
from app.schemas.visual_lesson import (
CompiledNucleicForm,
CompiledNucleicGeometry,
NucleicAcidSpec,
NucleicFormParameters,
NucleicPoint,
)
class NucleicCompileError(ValueError):
pass
class NucleicCompiler:
"""Pure geometry compiler for illustrative helices; never emits atomistic coordinates."""
def compile_spec(self, spec: NucleicAcidSpec) -> CompiledNucleicGeometry:
if not spec.forms:
return CompiledNucleicGeometry(forms=[], assertions_passed=True)
compiled: list[CompiledNucleicForm] = []
for params in spec.forms:
compiled.append(CompiledNucleicForm(form=params.form, points=self._compile_form(spec, params)))
return CompiledNucleicGeometry(forms=compiled, assertions_passed=True)
@staticmethod
def _compile_form(spec: NucleicAcidSpec, params: NucleicFormParameters) -> list[NucleicPoint]:
if not spec.sequence or len(spec.sequence) > 60:
raise NucleicCompileError("A nucleic-acid sequence must contain between 1 and 60 symbols")
if params.bases_per_turn <= 0 or params.rise_angstrom <= 0 or params.radius_angstrom <= 0:
raise NucleicCompileError("Helix parameters must be positive")
if not 0 < params.strand_offset_degrees < 360:
raise NucleicCompileError("The strand angular offset must be between 0 and 360 degrees")
direction = -1.0 if params.handedness == "left" else 1.0
points: list[NucleicPoint] = []
for index, base in enumerate(spec.sequence):
display_base = "U" if params.form == "rna" and base == "T" else base
angle = direction * 2.0 * math.pi * index / params.bases_per_turn
z = (index - (len(spec.sequence) - 1) / 2.0) * params.rise_angstrom
a = [params.radius_angstrom * math.cos(angle), params.radius_angstrom * math.sin(angle), z]
opposite = angle + math.radians(params.strand_offset_degrees)
b = [] if params.form == "rna" else [
params.radius_angstrom * math.cos(opposite),
params.radius_angstrom * math.sin(opposite),
z,
]
values = [*a, *b]
if any(not math.isfinite(value) for value in values):
raise NucleicCompileError("Compiled helix geometry contains a non-finite coordinate")
points.append(NucleicPoint(
index=index,
base=display_base,
complement=spec.complement[index] if params.form != "rna" and index < len(spec.complement) else "",
strand_a=a,
strand_b=b,
))
return points