Spaces:
Sleeping
Sleeping
File size: 2,732 Bytes
2e818da | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 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
|