Download src/peppa/schema.py from ChatterjeeLab/PepPA: direct link, hf CLI and curl.
- Browser
- Download file 6.63 kB
-
https://huggingface.co/ChatterjeeLab/PepPA/resolve/main/src/peppa/schema.py
- Command line
-
hf download hf://ChatterjeeLab/PepPA/src/peppa/schema.py
-
curl -L -o schema.py https://huggingface.co/ChatterjeeLab/PepPA/resolve/main/src/peppa/schema.py
6.63 kB
| """Typed molecular identity, scientific requirements, and visible decisions. | |
| Residues use one-based positions. Assay thresholds retain their original units. | |
| Chemical identity includes modifications, bonds, and terminal chemistry. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| from typing import Any, Literal | |
| from pydantic import BaseModel, ConfigDict, Field, model_validator | |
| def canonical(value: Any) -> str: | |
| """Serialize deterministically, rejecting NaN and infinite numbers.""" | |
| if isinstance(value, BaseModel): | |
| value = value.model_dump(mode="json") | |
| return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False) | |
| def digest(value: Any) -> str: | |
| return hashlib.sha256(canonical(value).encode()).hexdigest() | |
| class Record(BaseModel): | |
| model_config = ConfigDict(extra="forbid", validate_assignment=True, allow_inf_nan=False) | |
| class Modification(Record): | |
| position: int = Field(ge=1) | |
| residue: str = Field(min_length=1, max_length=1) | |
| ccd: str = Field(min_length=1) | |
| class Molecule(Record): | |
| sequence: str | |
| modifications: list[Modification] = Field(default_factory=list) | |
| # Each crosslink lists residue position and atom name at both ends. | |
| bonds: list[tuple[int, str, int, str]] = Field(default_factory=list) | |
| n_terminus: str = "free" | |
| c_terminus: str = "free" | |
| smiles: str | None = None | |
| def chemistry(self): | |
| if not self.sequence or any(a not in "ACDEFGHIKLMNPQRSTVWY" for a in self.sequence): | |
| raise ValueError("sequence must contain canonical uppercase parent residues") | |
| seen = set() | |
| for m in self.modifications: | |
| if m.position > len(self.sequence) or self.sequence[m.position - 1] != m.residue: | |
| raise ValueError("modification position/residue does not match the parent sequence") | |
| if m.position in seen: | |
| raise ValueError("duplicate modification position") | |
| seen.add(m.position) | |
| for i, a, j, b in self.bonds: | |
| if not (1 <= i <= len(self.sequence) and 1 <= j <= len(self.sequence)): | |
| raise ValueError("bond position outside sequence") | |
| if (i, a) == (j, b): | |
| raise ValueError("a bond requires two different atoms") | |
| return self | |
| def identity(self) -> str: | |
| value = self.model_dump() | |
| value["modifications"] = sorted(value["modifications"], key=lambda m: (m["position"], m["ccd"])) | |
| value["bonds"] = sorted(tuple(sorted(((i, a), (j, b)))) for i, a, j, b in self.bonds) | |
| return digest(value)[:20] | |
| class Target(Record): | |
| id: str | |
| accession: str | |
| molecule: Molecule | |
| species: str = "Homo sapiens" | |
| compartment: str | |
| structure_paths: list[str] = Field(default_factory=list) | |
| # Structural files must be matched to this construct and chemical identity. | |
| construct_start: int = Field(default=1, ge=1) | |
| class Requirement(Record): | |
| endpoint: str | |
| unit: str | |
| direction: Literal["ge", "le"] | |
| threshold: float | |
| assay: str | |
| scale: float = Field(gt=0) | |
| required: bool = True | |
| class DesignSpec(Record): | |
| episode_id: str | |
| task: Literal["binding", "ptm", "ternary", "direction"] | |
| objective: str | |
| targets: list[Target] = Field(min_length=1) | |
| countertargets: list[Target] = Field(default_factory=list) | |
| requirements: list[Requirement] = Field(min_length=1) | |
| administration: str | |
| peptide_format: Literal["linear", "head_to_tail", "modified"] = "linear" | |
| sequence_lengths: list[int] = Field(default_factory=lambda: [12, 18, 24]) | |
| # These endpoint definitions remain fixed within the comparison. | |
| budgets: dict[str, float] = Field(default_factory=lambda: {"tool_calls": 64, "gpu_minutes": 480, | |
| "controller_calls": 40, "controller_tokens": 128000, "synthesis_slots": 24}) | |
| seed: int = 2027 | |
| def scientific_contract(self): | |
| ids = [t.id for t in self.targets + self.countertargets] | |
| if len(ids) != len(set(ids)): | |
| raise ValueError("target IDs must be unique") | |
| if self.task == "ptm" and not self.countertargets: | |
| raise ValueError("a PTM task requires matched countertargets") | |
| if self.task == "ternary" and len(self.targets) != 2: | |
| raise ValueError("a ternary task requires two protein partners") | |
| if len({r.endpoint for r in self.requirements}) != len(self.requirements): | |
| raise ValueError("endpoint names must be unique") | |
| if not self.sequence_lengths or any(x < 2 for x in self.sequence_lengths): | |
| raise ValueError("invalid peptide length") | |
| if any(v < 0 for v in self.budgets.values()): | |
| raise ValueError("budgets must be nonnegative") | |
| return self | |
| class Measurement(Record): | |
| candidate_id: str | |
| endpoint: str | |
| value: float | |
| unit: str | |
| source_id: str | |
| kind: Literal["prediction", "experiment"] | |
| lower: float | None = None | |
| upper: float | None = None | |
| assay: str | |
| replicate_id: str | None = None | |
| censor: Literal["none", "left", "right"] = "none" | |
| def bounds(self): | |
| if self.lower is not None and self.lower > self.value: | |
| raise ValueError("lower bound exceeds estimate") | |
| if self.upper is not None and self.upper < self.value: | |
| raise ValueError("upper bound is below estimate") | |
| return self | |
| class Candidate(Record): | |
| molecule: Molecule | |
| parent_ids: list[str] = Field(default_factory=list) | |
| generator: str | |
| revision: str | |
| def id(self): | |
| return self.molecule.identity | |
| class Evidence(Record): | |
| id: str | |
| source_url: str | |
| source_version: str | |
| retrieved_at: str | |
| passage: str | |
| claim: str | |
| target_ids: list[str] | |
| species: str | |
| assay: str | |
| molecular_format: str | |
| direction: Literal["supports", "opposes", "unresolved"] | |
| evidence_type: Literal["experimental", "computational", "review"] | |
| class Decision(Record): | |
| """An explicit action proposal with a concise scientific explanation.""" | |
| tool: str | |
| arguments: dict[str, Any] | |
| hypothesis: str | |
| evidence_ids: list[str] | |
| decision_summary: str | |
| expected_observation: str | |
| stop: bool = False | |
| class ToolResult(Record): | |
| candidates: list[Candidate] = Field(default_factory=list) | |
| measurements: list[Measurement] = Field(default_factory=list) | |
| evidence: list[Evidence] = Field(default_factory=list) | |
| artifacts: dict[str, str] = Field(default_factory=dict) | |
| message: str = "" | |