Spaces:
Sleeping
Sleeping
| 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) | |
| 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 | |