Skip to content

molforge.sequence

sequence

Sequence-level operations: alignment, mutations, composition, properties.

What's here: - :func:align / :func:needleman_wunsch / :func:smith_waterman — pairwise alignment with affine gaps and BLOSUM62/PAM250 matrices. - :func:identity — convenience for "what's the sequence identity" - :class:Mutation, :func:apply_mutation, :func:apply_mutations, :func:mutate_protein — point mutations on sequences and Proteins. - :func:composition, :func:molecular_weight, :func:gravy, :func:aromaticity, :func:length — sequence properties. - :class:MultipleSequenceAlignment / :class:AlignedSequence, :func:read_a3m / :func:write_a3m — the many-to-one alignment an AF3-class folding model actually runs on, with the per-hit source and pairing metadata a complex prediction needs.

For sequence I/O see :mod:molforge.io (FASTA). a3m lives with the MSA type rather than in :mod:molforge.io, because its lower-case insertion convention has to survive parsing verbatim and the generic FASTA reader normalises it away.

Alignment dataclass

Alignment(
    aligned_a: str,
    aligned_b: str,
    score: float,
    identity: float,
    coverage_a: float,
    coverage_b: float,
    start_a: int,
    end_a: int,
    start_b: int,
    end_b: int,
)

Result of a pairwise sequence alignment.

Attributes:

Name Type Description
aligned_a str

Top sequence with gaps inserted (- for gap).

aligned_b str

Bottom sequence with gaps inserted.

score float

Total alignment score.

identity float

Fraction of aligned positions where residues match (excluding gap-vs-gap and gap-vs-residue positions).

coverage_a float

Fraction of sequence A covered by the alignment.

coverage_b float

Fraction of sequence B covered by the alignment.

start_a int

Start index in original sequence A (inclusive).

end_a int

End index in original sequence A (exclusive).

start_b int

Start index in original sequence B (inclusive).

end_b int

End index in original sequence B (exclusive).

length property

length: int

Alignment length including gaps.

format

format(width: int = 60) -> str

Format the alignment as a human-readable block.

Parameters:

Name Type Description Default
width int

Characters per row.

60

Returns:

Type Description
str

Multi-line string showing aligned_a / matches / aligned_b

str

in stacked blocks of width columns.

AlignedSequence dataclass

AlignedSequence(
    sequence: str,
    identifier: str = "",
    source_database: str = "",
    pairing_key: str = "",
    comment: str = "",
)

One row of an alignment: a homologue, lined up against the query.

Attributes:

Name Type Description
sequence str

The aligned sequence in a3m convention — upper-case and - are alignment columns, lower-case are insertions.

identifier str

The accession or header text, without the >.

source_database str

Which database the hit came from (:data:KNOWN_SOURCE_DATABASES lists the ones that round-trip cleanly). "" when unrecorded.

pairing_key str

Token used to pair this hit against another chain's hits — conventionally the source organism. "" when the hit can't be paired, which is the honest default: an unpairable hit is still a perfectly good monomer hit.

comment str

Free text, carried for readability and ignored otherwise.

columns property

columns: str

The alignment columns only, with insertions removed.

This is the row as the model sees it: every :class:AlignedSequence in an alignment has the same columns length.

ungapped property

ungapped: str

The plain sequence — no gaps, no insertion casing.

to_dict

to_dict() -> dict[str, Any]

A JSON-safe row, shaped like Chai-1's .aligned.pqt columns.

MultipleSequenceAlignment dataclass

MultipleSequenceAlignment(
    query: AlignedSequence,
    hits: tuple[AlignedSequence, ...] = (),
)

A query sequence and the homologues aligned to it.

Attributes:

Name Type Description
query AlignedSequence

The sequence being folded. Always row 0 of the a3m, and the row every other is aligned against.

hits tuple[AlignedSequence, ...]

The homologues, in search order (best first, by convention).

Example

from molforge.sequence import MultipleSequenceAlignment msa = MultipleSequenceAlignment.from_a3m( ... ">query\nMKQ\n>hit1 OX=9606\nMKA\n" ... ) msa.depth 2 msa.n_columns 3 msa.hits[0].pairing_key '9606'

depth property

depth: int

Number of sequences, including the query.

This is the quantity :attr:Boltz.msa_depth and Chai-1's recycle_msa_subsample cap.

n_columns property

n_columns: int

Alignment width, insertions excluded.

query_sequence property

query_sequence: str

The query with gaps and insertion casing removed.

subsample

subsample(
    depth: int, *, seed: int | None = None
) -> MultipleSequenceAlignment

Return an alignment of at most depth sequences.

The query is always kept — an alignment without it has nothing to be aligned to — so depth=8 means the query plus seven hits.

Hits are sampled rather than truncated when seed is given, because the top of a search output is usually the most redundant part of it; truncation gives a shallow alignment of near-identical sequences, which is not the same experiment as a shallow alignment of diverse ones. Without a seed the first depth - 1 hits are kept, which is deterministic and matches what the engines do with their own depth caps.

Parameters:

Name Type Description Default
depth int

Total sequences to keep, query included. Must be >= 1.

required
seed int | None

Seed for random sampling. None truncates instead.

None

Returns:

Type Description
MultipleSequenceAlignment

A new alignment; this one is unchanged.

Raises:

Type Description
ValueError

If depth is below 1.

deduplicate

deduplicate() -> MultipleSequenceAlignment

Drop hits whose alignment columns repeat an earlier row.

Search outputs routinely contain the same alignment several times over; depth that is really redundancy inflates every depth-based decision made downstream.

with_source_database

with_source_database(
    name: str,
) -> MultipleSequenceAlignment

Label every hit with the database it came from.

Search wrappers know this; the a3m they parse usually doesn't say.

pairable

pairable() -> tuple[AlignedSequence, ...]

Hits carrying a pairing key, i.e. those usable for a complex.

from_a3m classmethod

from_a3m(
    text: str, *, source_database: str = ""
) -> MultipleSequenceAlignment

Parse a3m text. The first record is the query.

Parameters:

Name Type Description Default
text str

The a3m document.

required
source_database str

Label applied to every hit, since a3m has nowhere to record it.

''

Returns:

Type Description
MultipleSequenceAlignment

The parsed alignment.

Raises:

Type Description
ValueError

If the text has no records, or a row's column count disagrees with the query's.

to_a3m

to_a3m() -> str

Serialise to a3m text, query first.

The format Boltz reads directly and Chai-1 converts from, so this is the handover point to both.

to_pairing_rows

to_pairing_rows() -> list[dict[str, Any]]

Rows shaped for Chai-1's .aligned.pqt / Boltz's paired CSV.

The query is included and marked source_database="query", which is what Chai-1 expects to find.

Mutation dataclass

Mutation(
    wild_type: str,
    position: int,
    mutant: str,
    chain_id: str | None = None,
)

A single point mutation.

Attributes:

Name Type Description
wild_type str

One-letter code of the original residue.

position int

1-indexed residue number (matches PDB seq_id).

mutant str

One-letter code of the new residue.

chain_id str | None

Optional chain identifier (some workflows need it).

parse classmethod

parse(spec: str) -> Mutation

Parse a mutation string like "A123V".

Supports chain prefix syntax "A:K42N" for chain-aware mutations.

Raises:

Type Description
ValueError

If the spec doesn't match the expected format.

align

align(
    a: str,
    b: str,
    *,
    mode: str = "global",
    matrix: str | None = "BLOSUM62",
    match: int = 2,
    mismatch: int = -1,
    gap_open: int = -10,
    gap_extend: int = -1,
) -> Alignment

Pairwise alignment entry point.

Parameters:

Name Type Description Default
mode str

"global" (Needleman-Wunsch) or "local" (Smith-Waterman).

'global'

See :func:needleman_wunsch / :func:smith_waterman for the rest.

identity

identity(a: str, b: str, *, mode: str = 'global') -> float

Convenience: align and return only the identity score.

needleman_wunsch

needleman_wunsch(
    a: str,
    b: str,
    *,
    matrix: str | None = "BLOSUM62",
    match: int = 2,
    mismatch: int = -1,
    gap_open: int = -10,
    gap_extend: int = -1,
) -> Alignment

Global pairwise alignment (Needleman-Wunsch with affine gaps).

Parameters:

Name Type Description Default
a str

First sequence to align.

required
b str

Second sequence to align.

required
matrix str | None

Substitution matrix name (e.g. "BLOSUM62", "PAM250") or None to use match / mismatch instead.

'BLOSUM62'
match int

Per-position match score when matrix=None.

2
mismatch int

Per-position mismatch score when matrix=None.

-1
gap_open int

Penalty for opening a new gap (added at gap start).

-10
gap_extend int

Penalty for each additional gap position.

-1

Returns:

Name Type Description
An Alignment

class:Alignment covering the full length of both sequences.

Notes

Implements affine gap penalties via the standard three-matrix formulation (M for match/mismatch, X for gap in A, Y for gap in B). Penalties are added to the score, so they should be negative.

smith_waterman

smith_waterman(
    a: str,
    b: str,
    *,
    matrix: str | None = "BLOSUM62",
    match: int = 2,
    mismatch: int = -1,
    gap_open: int = -10,
    gap_extend: int = -1,
) -> Alignment

Local pairwise alignment (Smith-Waterman with affine gaps).

Finds the highest-scoring local subsequence pair.

Parameters:

Name Type Description Default
a str

First sequence to align.

required
b str

Second sequence to align.

required
matrix str | None

see :func:needleman_wunsch.

'BLOSUM62'
match int

see :func:needleman_wunsch.

2
mismatch int

see :func:needleman_wunsch.

-1
gap_open int

see :func:needleman_wunsch.

-10
gap_extend int

see :func:needleman_wunsch.

-1

Returns:

Name Type Description
An Alignment

class:Alignment covering only the best-scoring local region;

Alignment

start_* / end_* give the bounds in the original sequences.

aromaticity

aromaticity(sequence: str) -> float

Fraction of aromatic residues (F, W, Y).

A quick proxy for things like protein UV absorption.

gravy

gravy(sequence: str) -> float

Grand average of hydropathy (GRAVY) — Kyte-Doolittle 1982.

Higher = more hydrophobic. Soluble globular proteins typically have GRAVY between -2 and +2.

length

length(sequence: str) -> int

Number of standard amino acids in a sequence.

molecular_weight

molecular_weight(sequence: str) -> float

Approximate molecular weight in Da.

Sums per-residue monoisotopic masses plus one water for the terminal H/OH. Non-standard residues are ignored.

available_matrices

available_matrices() -> list[str]

List the named substitution matrices that ship with molforge.

get_matrix

get_matrix(
    name: str,
) -> tuple[NDArray[np.int_], dict[str, int]]

Return (matrix, index_map) for a named substitution matrix.

Raises:

Type Description
KeyError

If name is not a known matrix.

read_a3m

read_a3m(
    path: str | PathLike[str], *, source_database: str = ""
) -> MultipleSequenceAlignment

Read an a3m file.

Parameters:

Name Type Description Default
path str | PathLike[str]

The .a3m file.

required
source_database str

Label applied to every hit.

''

Returns:

Type Description
MultipleSequenceAlignment

The parsed alignment.

write_a3m

write_a3m(
    msa: MultipleSequenceAlignment,
    path: str | PathLike[str],
) -> None

Write an alignment as a3m — the format both AF3-class engines accept.

apply_mutation

apply_mutation(
    sequence: str, mutation: Mutation | str
) -> str

Apply a single point mutation to a sequence string.

Parameters:

Name Type Description Default
sequence str

One-letter amino-acid sequence.

required
mutation Mutation | str

A :class:Mutation object or mutation string (e.g. "A123V").

required

Returns:

Type Description
str

The mutated sequence.

Raises:

Type Description
ValueError

If the position is out of range, or the wild-type residue doesn't match what's actually at that position.

apply_mutations

apply_mutations(
    sequence: str, mutations: str | Iterable[Mutation | str]
) -> str

Apply multiple mutations to a sequence.

Accepts a slash-delimited string or an iterable of mutations.

mutate_protein

mutate_protein(
    protein: Protein,
    mutation: Mutation | str,
    *,
    chain_id: str | None = None,
) -> Protein

Apply a sequence-level mutation to a :class:Protein.

Updates the residue_name field of the affected residue. Side chain atoms are not rebuilt — for full structural mutation, route through Rosetta, OpenMM, or a side-chain repacker. This function is for sequence-bookkeeping and downstream tools that re-fold or repack on their own.

Parameters:

Name Type Description Default
protein Protein

The structure to mutate (a copy is returned; original is unmodified).

required
mutation Mutation | str

A :class:Mutation or mutation string.

required
chain_id str | None

Which chain to mutate. If omitted, uses the chain embedded in the mutation string ("A:K42N") or the first chain in the protein.

None

Returns:

Type Description
Protein

A new :class:Protein with the residue name updated.

Raises:

Type Description
ValueError

If the position doesn't exist, or the wild-type doesn't match the residue actually present.

parse_mutations

parse_mutations(spec: str) -> list[Mutation]

Parse a slash- (or comma- or whitespace-) delimited mutation string.

Example

parse_mutations("A123V/T56K") [Mutation('A', 123, 'V'), Mutation('T', 56, 'K')]