Submodules
biolm.pipeline.async_executor module
Async batch execution utilities with semaphore-based rate limiting.
- class biolm.pipeline.async_executor.AsyncBatchExecutor(max_concurrent: int = 10, batch_size: int | None = None, progress_desc: str = 'Processing', show_progress: bool = True)
Bases:
objectExecute async tasks in batches with rate limiting.
Features: - Semaphore-based concurrency control - Progress tracking with tqdm - Error handling per item - Dynamic batch sizing
- Args:
max_concurrent: Maximum number of concurrent tasks batch_size: Size of each batch (None for no batching) progress_desc: Description for progress bar show_progress: Whether to show progress bar
- Example:
-
default
>>> executor = AsyncBatchExecutor(max_concurrent=10) >>> results = await executor.execute(items, process_func)
- async execute(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) → list[R]
Execute async function on all items with rate limiting.
- Args:
items: List of items to process func: Async function to apply to each item return_exceptions: If True, return exceptions instead of raising
- Returns:
List of results (same order as input)
- async execute_batched(items: list[T], batch_func: Callable[[list[T]], Coroutine[Any, Any, list[R]]], batch_size: int | None = None, return_exceptions: bool = False) → list[R]
Execute async function on batches of items.
- Args:
items: List of items to process batch_func: Async function that processes a batch and returns results batch_size: Size of each batch (uses self.batch_size if None) return_exceptions: If True, return exceptions instead of raising
- Returns:
Flattened list of results
- class biolm.pipeline.async_executor.CachingExecutor(executor: AsyncBatchExecutor, cache_check_func: Callable[[T], R | None] | None = None, cache_store_func: Callable[[T, R], None] | None = None)
Bases:
objectExecutor with built-in caching support.
Checks cache before execution and updates cache after completion.
- Args:
executor: AsyncBatchExecutor instance cache_check_func: Function to check if result is cached (returns cached result or None) cache_store_func: Function to store result in cache
- Example:
-
default
>>> def cache_check(item): ... return datastore.get_prediction(item['sequence_id'], 'stability') >>> >>> def cache_store(item, result): ... datastore.add_prediction(item['sequence_id'], 'stability', result) >>> >>> executor = CachingExecutor( ... AsyncBatchExecutor(max_concurrent=10), ... cache_check, cache_store ... ) >>> results = await executor.execute_with_cache(items, process_func)
- async execute_with_cache(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) → tuple[list[R], int, int]
Execute with caching.
- Returns:
Tuple of (results, cached_count, computed_count)
- class biolm.pipeline.async_executor.StreamingExecutor(executor: AsyncBatchExecutor, result_callback: Callable[[T, R], Coroutine[Any, Any, None]] | None = None)
Bases:
objectExecutor that streams results as they complete.
Useful for pipelines where downstream stages can start before all results are ready.
- Args:
executor: AsyncBatchExecutor instance result_callback: Optional callback for each completed result
- Example:
-
default
>>> async def on_result(item, result): ... print(f"Completed: {item}") ... downstream_queue.put(result) >>> >>> executor = StreamingExecutor( ... AsyncBatchExecutor(max_concurrent=10), ... result_callback=on_result ... ) >>> await executor.execute_streaming(items, process_func)
- async execute_streaming(items: list[T], func: Callable[[T], Coroutine[Any, Any, R]], return_exceptions: bool = False) → list[R]
Execute with streaming results.
Results are passed to callback as they complete.
- Returns:
List of results (order may differ from input)
- async biolm.pipeline.async_executor.process_dataframe_async(df: DataFrame, process_func: Callable[[Series], Coroutine[Any, Any, Any]], result_column: str, max_concurrent: int = 10, show_progress: bool = True, progress_desc: str = 'Processing') → DataFrame
Process DataFrame rows asynchronously and add results as a new column.
- Args:
df: Input DataFrame process_func: Async function to process each row (receives a Series) result_column: Name of column to store results max_concurrent: Maximum concurrent tasks show_progress: Whether to show progress bar progress_desc: Description for progress bar
- Returns:
DataFrame with new result column
- Example:
-
default
>>> async def predict_stability(row): ... return await model.predict(row['sequence']) >>> >>> df = await process_dataframe_async( ... df, predict_stability, 'stability_score' ... )
- async biolm.pipeline.async_executor.process_sequences_batched(sequences: list[str], batch_func: Callable[[list[str]], Coroutine[Any, Any, list[Any]]], batch_size: int = 32, max_concurrent: int = 5, show_progress: bool = True, progress_desc: str = 'Processing sequences') → list[Any]
Process sequences in batches.
- Args:
sequences: List of sequences batch_func: Async function that processes a batch of sequences batch_size: Size of each batch max_concurrent: Maximum concurrent batches show_progress: Whether to show progress bar progress_desc: Description for progress bar
- Returns:
List of results (one per sequence)
- Example:
-
default
>>> async def predict_batch(seqs): ... return await model.predict_batch(seqs) >>> >>> results = await process_sequences_batched( ... sequences, predict_batch, batch_size=32 ... )
biolm.pipeline.base module
Base Pipeline classes for stage management and execution.
- class biolm.pipeline.base.BasePipeline(datastore: DuckDBDataStore | str | Path | None = None, run_id: str | None = None, output_dir: str | Path = 'pipeline_outputs', resume: bool = False, verbose: bool = True, input_schema: InputSchema | None = None)
Bases:
ABCBase class for all pipeline types.
Provides: - Stage management and dependency resolution - Async execution with progress tracking - Caching and resumability - Export and visualization
When no
datastoreis provided, the pipeline automatically creates a DuckDB cache under.biolm/pipelines/. The/ pipeline_id(and full cache path) is exposed viametadataso users can reconnect to the same cache in later sessions.- Args:
datastore: DataStore instance or path to a DuckDB file. Required. run_id: Unique run identifier (auto-generated if not provided). output_dir: Directory for CSV/Parquet exports (default
pipeline_outputs). resume: Whether to resume from a previous run. verbose: Enable verbose output.
- add_stage(stage: Stage)
Add a stage to the pipeline.
- close()
Close the pipeline’s datastore connection.
Safe to call multiple times. Called automatically by
__exit__and__aexit__. Also called by__del__for auto-created datastores only (user-provided datastores are not closed on GC so the caller can continue using them after the pipeline is discarded).
- export_to_csv(output_path: str | Path | None = None)
Export final results to CSV.
- classmethod from_db(db_path: str | Path, definition_id: str | None = None, run_id: str | None = None, verbose: bool = True) → BasePipeline
Reconstruct a pipeline from an existing DuckDB database.
Useful for recovering after a kernel death without re-running already-completed stages.
- Args:
db_path: Path to the DuckDB database file. definition_id: Specific definition to load (
None= latest). run_id: Run ID for the reconstructed pipeline (None= generate new). verbose: Enable verbose output.- Returns:
Reconstructed
BasePipelinesubclass instance.
Example:
defaultpipeline = DataPipeline.from_db("my_pipeline.duckdb") pipeline.run(resume=True)
- get_final_data() → DataFrame
Get the final output DataFrame.
Materializes from the merged final WorkingSet via DuckDB. In a branched DAG the last-added stage is not necessarily the last executed sink, so we prefer
_final_ws(set at end of run()).The returned DataFrame always contains at minimum:
sequence_id,sequence,length,hashsource_label— label from the generation config;Nonewhen unset. Always present regardless of whether any labels were set.One column per prediction type from upstream prediction stages.
- property metadata: PipelineMetadata
Return metadata for reconnecting to this pipeline’s cache later.
- query(sql: str, params=None) → DataFrame
Execute arbitrary SQL against the pipeline’s DuckDB datastore.
- results() → DataFrame
Return the final output DataFrame. Alias for
get_final_data().The returned DataFrame always contains at minimum:
sequence_id,sequence,length,hashsource_label— the label set on the generation config (e.g.DirectGenerationConfig.label).Nonewhen no label was set. Always present — do not guard on"source_label" in df.columns.One column per prediction type computed by any prediction stages.
- run(enable_streaming: bool = True, **kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the pipeline synchronously.
This is a convenience wrapper around run_async(). Works in both script/notebook environments (detects running event loops).
- async run_async(enable_streaming: bool = True, **kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the pipeline asynchronously.
- Args:
- enable_streaming: Stream prediction results through per-sequence
filters for better parallelism and lower latency (default True).
- Returns:
Dict mapping stage names to StageResults
- summary() → DataFrame
Get pipeline summary statistics.
- class biolm.pipeline.base.InputSchema(columns: list[str])
Bases:
objectDescribes the input columns for a pipeline.
When set, these columns are the primary data —
sequenceis not required. Columns are stored directly on thesequencestable viaALTER TABLE ADD COLUMNso thatmaterialize_working_set(), SQL filters, anditem_columnsall work via direct JOINs.Hashing uses all columns (sorted alphabetically) joined with
\x00separators so that identical rows produce the same hash regardless of column order.- Args:
columns: List of column names that comprise the primary input.
- columns: list[str]
- hash_row(row: dict[str, str]) → str
SHA-256 hash of the row values across all input columns (sorted).
- class biolm.pipeline.base.PipelineContext(datastore: DuckDBDataStore, run_id: str)
Bases:
objectShared key-value store backed by DuckDB for inter-stage communication.
Stages can read/write arbitrary data through the context. Common use case: stage 1 predicts structures (stored in the
structurestable), stage 2 reads them for structure-conditioned generation.- Args:
datastore: The pipeline’s DuckDB datastore. run_id: Current pipeline run ID.
- get(key: str, default: Any = None) → Any
Retrieve a value from the pipeline context table.
- get_structure(sequence_id: int, model_name: str | None = None) → dict | None
Convenience: fetch a structure from the datastore’s structures table.
- get_structures_for_ws(ws: WorkingSet, model_name: str | None = None) → DataFrame
Fetch structures for all sequences in a WorkingSet.
- set(key: str, value: Any)
Store a value in the pipeline context table.
- class biolm.pipeline.base.PipelineMetadata(pipeline_id: str, cache_dir: Path, db_path: Path, run_id: str)
Bases:
objectMetadata for a pipeline run — lets users retrieve and reuse cached results.
- Attributes:
pipeline_id: Unique identifier for the pipeline’s cache directory. cache_dir: Path to the
.biolm/pipelines/cache directory. db_path: Path to the DuckDB database file inside the cache directory. run_id: The run ID for this execution (there can be multiple runssharing the same cache).
Example:
defaultpipeline = DataPipeline(sequences=[...]) pipeline.run() meta = pipeline.metadata print(meta.pipeline_id) # "20260302_143022_a1b2c3d4" print(meta.cache_dir) # ".biolm/pipelines/20260302_143022_a1b2c3d4" # Later — reuse the same cache: pipeline2 = DataPipeline( sequences=new_seqs, datastore=meta.db_path, # or str(meta.cache_dir) resume=True, )- cache_dir: Path
- db_path: Path
- pipeline_id: str
- run_id: str
- class biolm.pipeline.base.Stage(name: str, cache_key: str | None = None, depends_on: list[str] | None = None, model_name: str | None = None, max_concurrent: int = 10)
Bases:
ABCAbstract base class for pipeline stages.
A stage represents a single processing step in the pipeline. It can filter data, compute predictions, or transform sequences.
- Args:
name: Stage name (must be unique within pipeline) cache_key: Unused collision-dedup key (auto-derived by PredictionStage) depends_on: List of stage names this stage depends on model_name: Model name for predictions/structures max_concurrent: Maximum concurrent API calls (for rate limiting)
- Class Attributes:
- merge_mode: How this stage’s output is merged when it runs in parallel
with other stages.
"intersect"(default) = only keep sequences that pass all parallel stages (correct for filters)."union"= keep sequences that appear in any parallel stage output (correct for independent prediction stages that may skip sequences on error but should not drop others).
- merge_mode: str = 'intersect'
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Legacy DataFrame interface — used by streaming mode and GenerationStage.
Subclasses may override this for backward compatibility or for cases where a DataFrame is the natural input (e.g. generation with an empty df).
- abstract async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Process data using WorkingSet (DuckDB-native).
All stages must implement this method. Stages that need actual sequence data (e.g. ClusteringStage) should call
datastore.materialize_working_set(ws)internally — that is the stage’s responsibility, not the pipeline’s.- Args:
ws: Input WorkingSet (set of sequence IDs). datastore: DataStore for reading/writing data. **kwargs: Additional arguments (e.g.
run_id).- Returns:
Tuple of (output WorkingSet, StageResult).
- to_spec() → dict
Return a serializable dict describing this stage.
Used by BasePipeline.run_async() to persist pipeline definitions to DuckDB (enabling DataPipeline.from_db() recovery after kernel death).
Subclasses must override this. Raises NotImplementedError by default.
- class biolm.pipeline.base.StageResult(stage_name: str, input_count: int, output_count: int, filtered_count: int = 0, cached_count: int = 0, computed_count: int = 0, elapsed_time: float = 0.0, metadata: dict[str, typing.Any] = <factory>)
Bases:
objectResult from a pipeline stage.
- cached_count: int = 0
- computed_count: int = 0
- elapsed_time: float = 0.0
- filtered_count: int = 0
- input_count: int
- metadata: dict[str, Any]
- output_count: int
- stage_name: str
- class biolm.pipeline.base.WorkingSet(sequence_ids: frozenset[int])
Bases:
objectLightweight set of sequence IDs — replaces DataFrame as inter-stage transport.
Stages operate on DuckDB directly and pass only the set of surviving sequence IDs to the next stage. Materialization to DataFrame happens once at
get_final_data()time.Memory: 1M IDs ≈ 28 MB (frozenset[int]) vs 500 MB+ DataFrame.
- difference(other: WorkingSet) → WorkingSet
Return IDs in self but not in other.
- classmethod from_ids(ids) → WorkingSet
Create from any iterable of ints.
- intersect(other: WorkingSet) → WorkingSet
Return a new WorkingSet containing only IDs present in both sets.
- sequence_ids: frozenset[int]
- to_list() → list[int]
Return sorted list (useful for DuckDB queries).
- union(other: WorkingSet) → WorkingSet
Return a new WorkingSet containing IDs from either set.
biolm.pipeline.clustering module
Sequence clustering and diversity analysis.
Provides tools for grouping similar sequences and measuring sequence space coverage.
Performance Notes: - For large datasets (>10k sequences), use sampling or embedding-based methods - Pairwise distance computations are O(n²) - use max_sample parameter - Embedding-based clustering scales much better than Hamming distance
- class biolm.pipeline.clustering.ClusteringResult(cluster_ids: ndarray, centroids: list[str], centroid_indices: ndarray, n_clusters: int, silhouette_score: float | None = None, davies_bouldin_score: float | None = None, cluster_sizes: dict[int, int] | None = None)
Bases:
objectResults from sequence clustering.
- centroid_indices: ndarray
- centroids: list[str]
- cluster_ids: ndarray
- cluster_sizes: dict[int, int] | None = None
- davies_bouldin_score: float | None = None
- n_clusters: int
- silhouette_score: float | None = None
- class biolm.pipeline.clustering.DiversityAnalyzer
Bases:
objectAnalyze sequence diversity and coverage.
Provides metrics for understanding sequence space exploration.
- Performance Notes:
Shannon entropy: O(n*L) where L is sequence length
Pairwise distances: O(n²*L) - use max_sample for large n
All metrics scale linearly except pairwise distances
- Example:
-
default
>>> analyzer = DiversityAnalyzer() >>> metrics = analyzer.compute_all_metrics(sequences, max_sample=10000) >>> print(f"Shannon entropy: {metrics['shannon_entropy']:.2f}")
- classmethod compute_all_metrics(sequences: list[str], max_sample: int | None = 10000) → dict[str, Union[float, dict]]
Compute all diversity metrics at once.
- Args:
sequences: List of protein sequences max_sample: Unused, reserved for future embedding-based pairwise stats
- Returns:
Dictionary with all diversity metrics
- static motif_diversity(sequences: list[str], k: int = 3) → dict[str, Union[int, float]]
Analyze k-mer (motif) diversity.
- Args:
sequences: List of protein sequences k: Length of k-mers to analyze
- Returns:
Dictionary with k-mer statistics
- static shannon_entropy(sequences: list[str], normalize: bool = True) → float
Calculate Shannon entropy of amino acid distribution.
Measures positional diversity across all sequences. Optimized for large datasets using vectorized operations.
- Args:
sequences: List of protein sequences normalize: Normalize by log(20) for 0-1 range
- Returns:
Shannon entropy (higher = more diverse)
- exception biolm.pipeline.clustering.PerformanceWarning
Bases:
UserWarningWarning for potentially slow operations on large datasets.
- class biolm.pipeline.clustering.SequenceClusterer(method: Literal['kmeans', 'dbscan', 'hierarchical'] = 'kmeans', n_clusters: int | None = None, similarity_metric: Literal['embedding'] = 'embedding', eps: float = 0.5, min_samples: int = 5, random_state: int = 42, mini_batch: bool = False, max_sample: int | None = None)
Bases:
objectCluster sequences by similarity.
Supports multiple clustering algorithms and similarity metrics.
- Performance Notes:
For >10k sequences with Hamming distance, consider using max_sample
Embedding-based clustering scales much better (O(n) with MiniBatch K-means)
Use mini_batch=True for very large datasets (>50k sequences)
- Args:
method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure sequence similarity eps: DBSCAN epsilon parameter min_samples: DBSCAN minimum samples per cluster mini_batch: Use MiniBatchKMeans for large datasets (faster, approximate) max_sample: Maximum sequences to use for distance matrix (None = all)
- Example:
-
default
>>> # For large datasets, use sampling or mini-batch >>> clusterer = SequenceClusterer( ... method='kmeans', ... n_clusters=100, ... mini_batch=True # Much faster for large N ... ) >>> result = clusterer.cluster(sequences)
- cluster(sequences: list[str], embeddings: ndarray | None = None) → ClusteringResult
Cluster sequences and return assignments.
- Args:
sequences: List of protein sequences embeddings: Pre-computed embeddings (if using embedding metric)
- Returns:
ClusteringResult with cluster assignments and metrics
- biolm.pipeline.clustering.analyze_diversity(sequences: list[str], max_sample: int | None = 10000) → dict
Convenience function for analyzing sequence diversity.
- Args:
sequences: List of protein sequences max_sample: Reserved for future embedding-based pairwise stats
- Returns:
Dictionary of diversity metrics (shannon_entropy, motif diversity, uniqueness)
- Example:
-
default
>>> metrics = analyze_diversity(sequences) >>> print(f"Entropy: {metrics['shannon_entropy']:.2f}")
- biolm.pipeline.clustering.cluster_sequences(sequences: list[str], method: str = 'kmeans', n_clusters: int = 10, embeddings: ndarray | None = None, mini_batch: bool = False, max_sample: int | None = None, **kwargs) → ClusteringResult
Convenience function for clustering sequences using embeddings.
- Performance Tips:
For >50k sequences, use mini_batch=True
Requires pre-computed embeddings (similarity_metric=’embedding’)
- Args:
sequences: List of protein sequences method: Clustering algorithm n_clusters: Number of clusters embeddings: Pre-computed embeddings (required) mini_batch: Use MiniBatchKMeans for faster (approximate) clustering max_sample: Reserved; not yet used **kwargs: Additional arguments for SequenceClusterer
- Returns:
ClusteringResult
biolm.pipeline.data module
Data-driven pipeline implementations.
DataPipeline: Load sequences from files/lists and run predictions SingleStepPipeline: Simplified single-step prediction pipeline
- class biolm.pipeline.data.ClusteringStage(name: str, method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, max_sample: int | None = None, **kwargs)
Bases:
StageSequence clustering stage.
- Args:
name: Stage name method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure similarity (‘hamming’, ‘embedding’) embedding_model: Model to use for embeddings (if similarity_metric=’embedding’)
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Cluster sequences, add cluster columns, and return the enriched DataFrame.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Cluster sequences — materializes internally for scikit-learn.
Clustering doesn’t filter rows (all sequences survive), so the returned WorkingSet is the same as the input. Cluster assignments are stored as predictions (prediction_type
'cluster_id') so thatmaterialize_working_set()includes them inget_final_data().
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.data.CofoldingPredictionStage(name: str, model_name: str, action: str = 'predict', prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, params: dict | None = None, batch_size: int = 1, item_columns: dict[str, str] | None = None, **kwargs)
Bases:
StagePrediction stage for co-folding models (Boltz2, Chai-1).
Each sequence in the pipeline DataFrame is used as the primary entity (chain) in a multi-molecule folding request. Additional static entities — ligands, cofactors, DNA/RNA, or other protein chains — are injected via
static_entitiesand held constant for every sequence.The caller is responsible for providing the correct molecule field names and params for the target model. Boltz2 and Chai-1 both expect an item shaped like
{'molecules': [{'id': ..., 'type': ..., 'sequence': ...}, ...]}.- Args:
name: Stage name. model_name: BioLM model slug (
'boltz2','chai1'). action: API action (default'predict'). prediction_type: Column name written for the confidence score(default
'structure').- sequence_chain_id: Chain ID / name assigned to the pipeline’s primary
sequence in the molecules list.
- sequence_entity_type: Molecule type for the primary sequence
(
'protein','dna','rna').- static_entities: List of
FoldingEntity objects appended to every molecules list after the primary chain.
- params: Model-specific params dict passed directly to the API
(e.g.
{'recycling_steps': 3, 'sampling_steps': 20}for Boltz).- batch_size: Sequences per API call (default 1; co-folding models are
typically limited to batch size 1).
Example:
defaultfrom biolm.pipeline import FoldingEntity pipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), ], params={'recycling_steps': 3, 'sampling_steps': 20}, depends_on=['filter_top50'], )- merge_mode: str = 'union'
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Run co-folding prediction for each sequence in df.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Run co-folding prediction using WorkingSet — no DataFrame transport.
- to_spec() → dict
Serialize to a dict for pipeline definition persistence.
Note:
static_entities(FoldingEntity objects) are not serializable and are omitted. A reconstructed pipeline will not have static_entities and must have them re-attached manually afterfrom_db().
- class biolm.pipeline.data.DataPipeline(sequences: list[str] | DataFrame | str | Path = None, diff_mode: bool = False, input_columns: list[str] | None = None, **kwargs)
Bases:
BasePipelinePipeline for processing existing sequences from files or lists.
Load sequences from CSV/FASTA/lists and run predictions/filtering.
- Args:
sequences: Input sequences (list of strings, DataFrame, or file path) datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output diff_mode: If True, merge new sequences with existing cached results.
Only computes predictions for uncached sequences. Use get_merged_results() or query_results() to efficiently access combined data without loading millions of rows into memory (SQL-based queries).
- Example:
-
default
>>> # Standard mode >>> pipeline = DataPipeline(sequences='sequences.csv') >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> pipeline.add_filter(ThresholdFilter('plddt', min_value=70)) >>> results = pipeline.run()default>>> # Diff mode - add new sequences to existing pipeline (SQL-based, efficient) >>> pipeline = DataPipeline(sequences='new_sequences.csv', diff_mode=True) >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> results = pipeline.run() >>> # Efficiently query specific data (doesn't load all millions of rows!) >>> high_quality = pipeline.query_results("s.length > 100 AND p.value > 70") >>> # Or get merged results with filters >>> merged = pipeline.get_merged_results(prediction_types=['plddt', 'tm'])
- add_clustering(method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a sequence clustering stage.
Clusters sequences by similarity and adds cluster_id column to DataFrame.
- Args:
method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (required for kmeans/hierarchical) similarity_metric: ‘hamming’ or ‘embedding’ embedding_model: Model name if using embedding similarity stage_name: Optional custom stage name depends_on: Optional list of stage names this stage depends on **kwargs: Additional arguments for clustering (eps, min_samples, etc.)
- Example:
-
default
>>> # Cluster by sequence similarity >>> pipeline.add_clustering(method='kmeans', n_clusters=10) >>> >>> # Cluster by embeddings >>> pipeline.add_prediction('esm2-650m', action='encode', stage_name='embed') >>> pipeline.add_clustering( ... method='kmeans', ... n_clusters=5, ... similarity_metric='embedding', ... embedding_model='esm2-650m', ... depends_on=['embed'] ... )
- add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)
Add a co-folding prediction stage (Boltz2, Chai-1).
Each pipeline sequence becomes the primary entity in a multi-molecule folding request.
static_entitiesinjects ligands, cofactors, DNA/RNA strands, or additional protein chains that are constant across all designs.The caller is responsible for providing the correct molecule field names via
static_entitiesand the rightparamsfor the model.- Args:
model_name: BioLM model slug (
'boltz2','chai1'). action: API action (default'predict'). stage_name: Optional stage name (defaults tomodel_name). prediction_type: Column name for the confidence score. sequence_chain_id: Chain ID assigned to the primary sequence(e.g.
'A'for Boltz, moleculenamefor Chai-1).- sequence_entity_type: Entity type for the primary sequence
(
'protein','dna','rna').- static_entities: List of
FoldingEntityobjects to include in every request alongside the primary sequence.
depends_on: Upstream stage names. params: Model-specific params (e.g.
{'recycling_steps': 3}). batch_size: Sequences per API call (default 1).
Example:
defaultfrom biolm.pipeline import FoldingEntity pipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), ], params={'recycling_steps': 3, 'sampling_steps': 20}, depends_on=['filter_top50'], )
- add_filter(filter_func: BaseFilter | Callable[[...], Any], stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a filter stage.
- Args:
filter_func: Filter function or BaseFilter instance stage_name: Custom stage name depends_on: List of stage names this depends on. When
None(default), auto-depends on the last added stage. Pass
depends_on=[]for earliest-level execution.
- add_prediction(model_name: str, action: str = 'predict', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage.
- Args:
model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) extractions: API response key(s) to extract. Required for
predict/score actions. Can be a string for a single key or a list of strings / ExtractionSpec objects.
- columns: Output column name(s). A string renames a single
extraction; a dict maps response keys to column names (unmapped keys keep their name).
params: Optional API parameters stage_name: Custom stage name (defaults to
predict_{first_column}) depends_on: List of stage names this depends on. WhenNone(default), the stage auto-depends on the last added stage, creating a sequential chain. Pass
depends_on=[]to run at the earliest possible level (parallel with other level-0 stages).
Example:
defaultpipeline.add_prediction( "temberture-regression", extractions="prediction", columns="tm", )
- add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)
Add multiple prediction stages at the same level (run in parallel).
- Args:
models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages
- Returns:
self for chaining
- Example:
-
default
>>> pipeline.add_predictions([ ... {'model_name': 'temberture-regression', 'extractions': 'prediction', 'columns': 'tm'}, ... {'model_name': 'biolmsol', 'extractions': 'solubility_score', 'columns': 'solubility'}, ... ])
- add_structure_prediction(model_name: str, structure_key: str = 'pdb', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, plddt_key: str | None = None, structure_format: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage that stores the structure from the response.
Convenience wrapper around
add_prediction()with aStructureSpec.- Args:
model_name: BioLM model name (e.g. ‘esmfold’, ‘alphafold2’). structure_key: Response key containing the structure string. extractions: Optional scalar extractions (e.g. ‘mean_plddt’). columns: Output column name(s) for scalar extractions. plddt_key: Optional response key for confidence score. structure_format: ‘pdb’ or ‘cif’ (auto-detected from key if None). stage_name: Custom stage name. depends_on: Stage dependencies.
Example:
defaultpipeline.add_structure_prediction( "esmfold", extractions="mean_plddt", columns="plddt", plddt_key="mean_plddt", )
- explore() → dict[str, Any]
Return summary stats for the pipeline’s datastore (all via SQL).
- Returns:
Dict with keys: sequences, embeddings, generated, completed_stages, predictions (dict of prediction_type → count).
- get_merged_results(prediction_types: list[str] | None = None, sequence_filter: str | None = None) → DataFrame
Get results merged with existing cached data (for diff mode).
This method is SQL-based and efficient - it doesn’t load millions of rows. Instead, it queries only the data you need using DuckDB’s columnar engine.
NOTE (Bug #5):
sequence_filterandsql_wherein query_results() are interpolated directly into SQL. These parameters are intended for internal/ trusted caller use only — never pass untrusted user input to them.- Args:
prediction_types: List of prediction types to include (None = all) sequence_filter: SQL WHERE clause to filter sequences (e.g., “length > 50”).
TRUSTED CALLERS ONLY — not safe for untrusted user input.
- Returns:
DataFrame with requested sequences and predictions
- Example:
-
default
>>> # Get all results (efficient - DuckDB only loads what's needed) >>> df = pipeline.get_merged_results()default>>> # Get only specific predictions (columnar - even faster!) >>> df = pipeline.get_merged_results(prediction_types=['tm', 'plddt'])default>>> # Get sequences matching criteria (predicate pushdown!) >>> df = pipeline.get_merged_results(sequence_filter="length > 100")
- plot(kind: str = 'funnel', **kwargs)
Convenience wrapper around PipelinePlotter.
- Args:
- kind: One of ‘funnel’, ‘predictions’, ‘distributions’, ‘scatter’,
‘correlation’, ‘diversity’, ‘temperature’.
- **kwargs: Forwarded to the underlying plotter method.
scatter requires x_col and y_col. diversity accepts reference_sequence. temperature requires metric_col.
- query(sql: str, params=None) → DataFrame
Execute arbitrary SQL against the pipeline’s DuckDB datastore.
- Args:
sql: DuckDB SQL query string. params: Optional list of query parameters.
- Returns:
DataFrame with results.
- Example:
-
default
>>> pipeline.query("SELECT * FROM sequences WHERE length > 100")
- query_results(sql_where: str, columns: list[str] | None = None) → DataFrame
Query results using SQL WHERE clause (for diff mode with large datasets).
This leverages DuckDB’s vectorized engine for maximum performance.
NOTE (Bug #5):
sql_whereis interpolated directly into SQL. TRUSTED CALLERS ONLY — never pass untrusted user input to this parameter.- Args:
- sql_where: SQL WHERE clause using table aliases:
s.* for sequences table (e.g., “s.length > 100”)
Column names directly (no p. prefix needed)
TRUSTED CALLERS ONLY — not safe for untrusted user input.
columns: Columns to include (None = all available)
- Returns:
DataFrame with matching sequences (only loads what matches!)
- Example:
-
default
>>> # Find long sequences (columnar scan - fast!) >>> df = pipeline.query_results("s.length > 200")default>>> # Complex filter with predictions >>> df = pipeline.query_results( ... "s.length > 100", ... columns=['tm', 'plddt'] ... )
- stats(stage_name: str | None = None) → DataFrame
Return per-stage counts from stage_completions.
- Args:
stage_name: If provided, filter to that stage only.
- Returns:
DataFrame with columns: stage_name, status, input_count, output_count, completed_at.
- biolm.pipeline.data.Embed(model_name: str, sequences: list[str] | DataFrame | str | Path, layer: int | None = None, key: str | None = None, **kwargs) → DataFrame
Convenience function for generating embeddings.
- Args:
- model_name: BioLM model name (e.g.,
'esm2-8m','esm2-650m', 'ablang2'). Seebiolm.list_models()for the full slugged list — the family name alone ('esm2') is not a valid endpoint.
sequences: Input sequences layer: Optional layer number key: Response dict key containing embeddings. Auto-detected if None:
ESM2 models use “embeddings”, AbLang2 uses “seqcoding”, others default to “embedding”.
**kwargs: Additional arguments
- model_name: BioLM model name (e.g.,
- Returns:
DataFrame with ‘sequence’, ‘sequence_id’, and ‘embedding’ columns
- Example:
-
default
>>> df = Embed('esm2-8m', sequences=['MKTAYIAKQRQ', 'MKLAVID'])
- class biolm.pipeline.data.EmbeddingSpec(key: str, layer: int | None = None, reduction: str | None = None)
Bases:
objectDeclarative specification for extracting embeddings from API responses.
Covers common response formats without writing a custom function.
- Args:
- key: Response dict key containing the embedding data (e.g.
"embedding","seqcoding","embeddings").- layer: Which layer to extract when the response contains multiple
layers (list of
{layer: int, embedding: [...]}dicts).Nonestores all layers; anintstores only that layer.- reduction: Reduce per-token 2-D embeddings to a single vector:
"mean","first","last","sum".Nonestores the full array as-is.
Examples:
default# ablang2 returns {"seqcoding": [float, ...]} EmbeddingSpec(key="seqcoding") # esm2-8m returns {"embeddings": [{embedding: [...], layer: 33}]} # Store only layer 33: EmbeddingSpec(key="embeddings", layer=33) # Per-residue → mean-pool: EmbeddingSpec(key="embedding", reduction="mean")- key: str
- layer: int | None = None
- reduction: str | None = None
- class biolm.pipeline.data.ExtractionSpec(response_key: str, reduction: str | None = None)
Bases:
objectSpecification for extracting a value from an API response (with reduction).
Use when you need to apply a reduction (mean, max, min, sum) to an array-valued response key. For simple scalar extractions, pass a plain string to
extractionsinstead.- Args:
response_key: Key in API response dict, e.g. “plddt” reduction: Optional reduction for array values: “mean”, “max”, “min”, “sum”
- reduction: str | None = None
- response_key: str
- class biolm.pipeline.data.FilterStage(name: str, filter_func: BaseFilter | Callable[[...], Any], **kwargs)
Bases:
StageGeneric filtering stage.
- Args:
name: Stage name filter_func: Filter function or BaseFilter instance
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Apply filter to DataFrame and return the filtered result.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Apply filter using WorkingSet.
Two execution paths — chosen at call time, not as a “fallback”:
SQL-native (zero materialization): filters that implement
to_sql()return a complete SELECT scoped to the working set. DuckDB executes it directly; no DataFrame is ever created.DataFrame-based: filters that cannot be expressed in SQL (e.g. HammingDistanceFilter, CustomFilter) must materialize a DataFrame. This is the correct path for those filters, not a fallback.
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.data.MatrixExtractionSpec(prefix: str = 'ddg', values_key: str = 'ddG_matrix.values', row_labels_key: str = 'ddG_matrix.residue_axis', col_labels_key: str = 'ddG_matrix.amino_acid_axis', mutation_key: str | None = None, value_key: str | None = None)
Bases:
objectFlattens a per-mutation response into individual prediction rows.
Each mutation becomes a separate prediction row with prediction_type formatted as ‘{prefix}_{label}’ (e.g., ‘ddg_M1A’).
- Two modes:
Matrix mode (SPURS): 2D array + row/col labels.
List mode (ThermoMPNN): list of dicts with mutation name + value.
- Args:
prefix: Prediction type prefix (e.g. “ddg”). values_key: Dot-path to 2D array in response (matrix mode). row_labels_key: Dot-path to row labels (position labels). col_labels_key: Dot-path to column labels (amino acid labels). mutation_key: Dict key for mutation name (list mode). If set, uses list mode. value_key: Dict key for the numeric value (list mode).
- col_labels_key: str = 'ddG_matrix.amino_acid_axis'
- mutation_key: str | None = None
- prefix: str = 'ddg'
- row_labels_key: str = 'ddG_matrix.residue_axis'
- value_key: str | None = None
- values_key: str = 'ddG_matrix.values'
- exception biolm.pipeline.data.PipelineAPIAuthError(status_code: int, payload: Any, model_name: str)
Bases:
RuntimeErrorRaised when the BioLM API returns 401/402 (auth/billing) during a stage.
These errors are not retriable per-item — every batch will hit the same failure — so the pipeline fails fast instead of producing a silent empty result.
skip_on_error=Truedoes NOT swallow this; callers always see an unambiguous failure with the upstream error payload.
- biolm.pipeline.data.Predict(model_name: str, sequences: list[str] | DataFrame | str | Path, extractions: str | list | None = None, params: dict | None = None, **kwargs) → DataFrame
Convenience function for single-step prediction.
- Args:
model_name: BioLM model name sequences: Input sequences extractions: API response key(s) to extract. Required — pass the response
key for your model (e.g.
extractions='prediction'for temberture,extractions='mean_plddt'for esmfold). Use a list or ExtractionSpec for multiple extractions.params: Optional API parameters **kwargs: Additional arguments
- Returns:
DataFrame with predictions
- Example:
-
default
>>> df = Predict('temberture-regression', sequences=['MKTAYIAKQRQ'], extractions='prediction')
- class biolm.pipeline.data.PredictionStage(name: str, model_name: str, action: str = 'predict', params: dict | None = None, batch_size: int = 32, max_concurrent: int = 5, max_connections: int = 10, item_columns: dict[str, str] | None = None, extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, embedding_extractor: EmbeddingSpec | Callable[[dict], Any] | None = None, structure_output: StructureSpec | None = None, structure_input: dict[str, str] | None = None, matrix_extraction: MatrixExtractionSpec | None = None, **kwargs)
Bases:
StageGeneric prediction stage using BioLM API.
Uses
merge_mode = "union"so that when multiple prediction stages run in parallel, sequences processed by any stage survive the merge — i.e., independent predictions do not cancel each other out.- Args:
name: Stage name model_name: BioLM model name (e.g., ‘esmfold’, ‘esm2’, ‘temberture-regression’) action: API action (‘predict’, ‘encode’, ‘score’) prediction_type: Type of prediction for caching (e.g., ‘structure’, ‘stability’, ‘embedding’) params: Optional parameters for the API call batch_size: Number of sequences per pipeline batch (default 32).
Each pipeline batch becomes one SDK call, which the SDK may further split by the model’s
maxItemsschema limit.- max_concurrent: Maximum pipeline batches in flight at once (default 5).
Controls how many batches are dispatched concurrently at the pipeline level. Higher values keep the API more saturated but use more memory (up to
max_concurrent * batch_sizeitems plus their responses in memory at once).- max_connections: Maximum concurrent HTTP connections to the API
(default 10). This is the SDK-level semaphore that throttles the actual HTTP requests. Each pipeline batch may be split into multiple sub-requests by the model’s
maxItemslimit;max_connectionscaps how many of those sub-requests run simultaneously across all in-flight batches.
structure_output: Store the structure from this model’s response. structure_input: Inject structures from upstream models into API items.
Maps API field name → source model name (e.g.
{"pdb": "esmfold"}).- matrix_extraction: Flatten a per-mutation response into individual
prediction rows (e.g. for DMS heatmaps).
- merge_mode: str = 'union'
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Process sequences through prediction model (legacy DataFrame path).
Uses the same bounded-concurrency batching as
process_ws— see that method’s docstring for the design rationale.Performance: single DuckDB anti-join for cache detection, bounded- concurrency API dispatch, single JOIN query to merge predictions back.
- async process_streaming(df: DataFrame, datastore: DuckDBDataStore, **kwargs)
Process sequences and yield results as batches complete (streaming).
Yields DataFrames as API batches complete instead of waiting for all results. This allows downstream stages to start processing immediately.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Process sequences using WorkingSet with bounded-concurrency batching.
Batching strategy (two levels):
This method splits uncached sequences into pipeline-level batches of
batch_sizeitems (default 32) and keeps up tomax_concurrentbatches in flight at once. As each batch completes, its results are written to DuckDB immediately and a new batch is dispatched.Each pipeline batch is itself an SDK call that may be further split by the model’s
maxItemsschema limit — the SDK handles that internally viaasyncio.gatherthrottled by a semaphore.Why bounded concurrency instead of all-at-once or sequential:
Sequential (old behavior) leaves the API idle between batches. With 300 sequences and 200ms API latency, that’s 10 idle round-trips.
All-at-once (
process_streamingstyle) has no backpressure — 10k sequences creates 300+ in-flight tasks with all items and response payloads in memory simultaneously.Bounded concurrency keeps the API saturated (
max_concurrentbatches in flight) while bounding memory to at mostmax_concurrent * batch_sizeitems plus their results. DuckDB writes happen as each batch lands, providing natural backpressure: a slow datastore slows down new batch dispatch.
- Steps:
Cache check via anti-join (DuckDB)
Fetch only uncached (sequence_id, sequence) pairs
Dispatch batches with bounded concurrency
Write results to DuckDB as each batch completes
Return WorkingSet of IDs that have predictions
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.data.SingleStepPipeline(model_name: str, action: str = 'predict', sequences: list[str] | DataFrame | str | Path = None, params: dict | None = None, extractions=None, columns=None, embedding_extractor=None, **kwargs)
Bases:
DataPipelineSimplified pipeline for single-step predictions.
Convenience class for running a single prediction model on sequences.
- Args:
model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) sequences: Input sequences params: Optional API parameters **kwargs: Additional arguments passed to DataPipeline
- Example:
-
default
>>> pipeline = SingleStepPipeline( ... model_name='esmfold', ... sequences=['MKTAYIAKQRQ', 'MKLAVID'] ... ) >>> results = pipeline.run() >>> df = pipeline.get_final_data()
- class biolm.pipeline.data.StructureSpec(key: str, format: str | None = None, plddt_key: str | None = None, index: int | None = 0)
Bases:
objectSpecification for extracting and storing a structure from an API response.
- Args:
key: Response dict key containing the structure string (e.g. “pdb”, “cif”, “pdbs”). format: Structure format — “pdb” or “cif”. Auto-detected from key if None. plddt_key: Optional response key for a confidence score to store alongside. index: For list-valued keys (e.g. AF2 “pdbs”), which element to store (default 0).
- detect_format() → str
- format: str | None = None
- index: int | None = 0
- key: str
- plddt_key: str | None = None
biolm.pipeline.datastore module
Backward-compatibility shim.
Old code imported from biolm.pipeline.datastore; the implementation has
moved to biolm.pipeline.datastore_duckdb. Import DataStore from
either location — they resolve to the same class.
- biolm.pipeline.datastore.DataStore
alias of
DuckDBDataStore
biolm.pipeline.datastore_duckdb module
DuckDB-based DataStore with Parquet backend for efficient large-scale data management.
Key features: - Columnar storage (Parquet) - Out-of-core queries (bigger than RAM) - Vectorized anti-join deduplication - 5-50× faster than pandas for aggregations - Diff-mode friendly batch inserts
- class biolm.pipeline.datastore_duckdb.DuckDBDataStore(db_path: str | Path | None = None, data_dir: str | Path | None = None)
Bases:
objectDuckDB + Parquet based datastore for efficient sequence management.
Optimized for: - Millions of sequences - Complex queries (joins, filters, aggregations) - Out-of-core operations (bigger than RAM) - Diff mode with efficient deduplication
- Args:
db_path: Path to DuckDB database file data_dir: Directory for Parquet files and large data
- Example:
-
default
>>> ds = DuckDBDataStore("pipeline.db", "data/") >>> seq_id = ds.add_sequence("MKLLIV") >>> ds.add_prediction(seq_id, "tm", "temberture-regression", 65.5) >>> >>> # Efficient query - no memory explosion >>> high_tm = ds.query("SELECT * FROM predictions WHERE value > 60")
- add_embedding(sequence_id: int, model_name: str, embedding: ndarray, layer: int | None = None)
Add embedding stored inline in DuckDB as FLOAT[] (no per-file Parquet overhead).
- Args:
sequence_id: Sequence ID model_name: Model name embedding: Numpy array layer: Optional layer number
- add_embeddings_batch(data: list[dict])
Batch-insert embeddings in a single DuckDB statement.
Each dict must have: sequence_id (int), model_name (str), embedding (np.ndarray). Optional: layer (int or None).
Significantly faster than N individual add_embedding() calls — one INSERT…SELECT vs. N individual row inserts.
- add_generation_metadata(sequence_id: int, model_name: str, run_id: str = '', temperature: float | None = None, top_k: int | None = None, top_p: float | None = None, num_return_sequences: int | None = None, do_sample: bool | None = None, repetition_penalty: float | None = None, max_length: int | None = None, sampling_params: dict | None = None) → int
Store generation parameters for a sequence.
- Returns:
metadata_id of the inserted row.
- add_generation_metadata_batch(rows: list[dict]) → None
Batch-insert generation metadata — one DuckDB round-trip.
Each dict in rows must have at minimum
sequence_idandmodel_name. Optional fields:temperature,top_k,top_p,num_return_sequences,do_sample,repetition_penalty,max_length.
- add_prediction(sequence_id: int, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None)
Add single prediction (convenience wrapper).
- add_prediction_by_sequence(sequence: str, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None) → int
Add a prediction, creating the sequence if it doesn’t exist.
- Returns:
prediction_id of the inserted row.
- add_predictions_batch(data: list[dict[str, Any]])
Batch add predictions efficiently.
- Args:
- data: List of dicts with keys: sequence_id, prediction_type,
model_name, value, metadata (optional)
- Example:
-
default
>>> ds.add_predictions_batch([ ... {'sequence_id': 1, 'prediction_type': 'tm', ... 'model_name': 'temberture-regression', 'value': 65.5}, ... {'sequence_id': 2, 'prediction_type': 'tm', ... 'model_name': 'temberture-regression', 'value': 70.2}, ... ])
- add_sequence(sequence: str) → int
Add single sequence (convenience wrapper).
- add_sequences_batch(sequences: list[str] | None = None, deduplicate: bool = True, input_df: DataFrame | None = None, input_columns: list[str] | None = None) → list[int]
Add multiple sequences efficiently using anti-join deduplication.
This is the RECOMMENDED way to add sequences - vectorized and fast!
Two calling conventions:
Legacy (sequence-only):
add_sequences_batch(["MKLLIV", ...])Multi-column (arbitrary input columns):
add_sequences_batch(input_df=df, input_columns=["heavy_chain", "light_chain"])In this mode, the hash is computed across all input columns, and the column values are stored directly on thesequencestable. Asequencecolumn is still written (concatenation of all input columns joined with:) so downstream code has a fallback.
- Args:
sequences: List of sequence strings (legacy path). deduplicate: Use anti-join to skip existing sequences. input_df: DataFrame with input columns (multi-column path). input_columns: Column names in input_df to use as primary data.
- Returns:
List of sequence_ids (new and existing), preserving input order.
- add_structure(sequence_id: int, model_name: str, structure_str: str | None = None, format: str = 'pdb', plddt_mean: float | None = None, plddt: float | None = None) → int
Store a structure gzip-compressed as BLOB (~8-12x smaller than plain TEXT).
- Args:
sequence_id: Sequence ID. model_name: Model that produced the structure (e.g. ‘esmfold’). structure_str: Full structure file content as a string. format: ‘pdb’ or ‘cif’ (default ‘pdb’). plddt_mean: Mean pLDDT score (optional). plddt: Alias for plddt_mean.
- Returns:
structure_id of the inserted row.
- close()
Close the DuckDB connection and release the file lock.
Safe to call multiple times — subsequent calls are no-ops. Called automatically by
__exit__and__del__.
- count_matching_sequences(sequences: list[str]) → int
Count how many of the given sequences already exist in the datastore.
Uses a single vectorized hash join instead of N individual lookups. Safe to call from any context — registers DataFrame explicitly.
- create_pipeline_run(run_id: str, pipeline_type: str, config: dict, status: str = 'running')
Create or update a pipeline run record (safe for resume runs).
- ensure_input_columns(columns: list[str])
Ensure the
sequencestable has all the given columns.Uses
ALTER TABLE ADD COLUMNfor any that don’t already exist. This is idempotent — safe to call on every pipeline run.- Raises:
- duckdb.CatalogException: If column creation fails for an unexpected
reason (e.g. type conflict). The “column already exists” case is silently ignored as it is the normal idempotent path.
- execute_filter_sql(sequence_ids: list[int], sql_query: str, ws_table_name: str = '_filter_ws') → list[int]
Execute a filter SQL query and return surviving sequence IDs.
The sql_query must be a complete
SELECTstatement that returnssequence_idvalues and JOINs against ws_table_name (which contains the input sequence_ids) to scope to the working set.Pass a unique ws_table_name (use
make_filter_ws_name()) when running multiple SQL-native filters concurrently — the default_filter_wsname races otherwise.- Args:
sequence_ids: Input sequence IDs. sql_query: Complete SQL SELECT returning
sequence_idvalues. ws_table_name: Name to register the working-set IDs under. Mustmatch the
ws_table=argument passed to the filter’sto_sql().- Returns:
List of sequence_ids that survive the filter.
- Raises:
ValueError: If sql_query is not a single SELECT statement.
- export_to_csv(path: str | Path, **kwargs) → None
Export all data to CSV (convenience wrapper around export_to_dataframe).
- export_to_dataframe(include_sequences: bool = True, include_predictions: bool = True, include_generation_metadata: bool = False, prediction_types: list[str] | None = None, run_id: str | None = None) → DataFrame
Export data to a flat DataFrame using a single DuckDB SQL query.
Uses conditional aggregation (CASE WHEN pivot) — no per-type queries, no pandas merges, no full table loads.
- Args:
include_sequences: Always True; includes sequence_id, sequence, length. include_predictions: Pivot prediction_type values into columns. include_generation_metadata: Join generation_metadata columns. prediction_types: Limit to specific prediction types (None = all).
- Returns:
Wide-format DataFrame: one row per sequence, one column per prediction type.
- export_to_parquet(table_name: str, output_path: str | Path)
Export table to Parquet file (for sharing/archiving).
- Args:
table_name: Table to export (sequences, predictions, etc.) output_path: Path to output Parquet file
- Example:
-
default
>>> ds.export_to_parquet('sequences', 'sequences_backup.parquet')
- get_all_sequences() → DataFrame
Return all sequences as a DataFrame with sequence_id, sequence, length columns.
- get_column_registry_entry(column_name: str) → dict | None
Return the registry entry for a column, or None if not registered.
- get_context(run_id: str, key: str) → Any | None
Retrieve a value from the pipeline context table.
- get_embedding(embedding_id: int) → tuple | None
Return (metadata_dict, embedding_array) for a given embedding_id, or None.
- get_embeddings_bulk(sequence_ids: list[int], model_name: str | None = None) → dict[int, numpy.ndarray]
Fetch embeddings for multiple sequences in a single JOIN query.
Replaces N individual get_embeddings_by_sequence() calls (O(n) queries → O(1)).
- Args:
sequence_ids: List of sequence IDs to fetch. model_name: Optional model filter.
- Returns:
Dict mapping sequence_id → numpy embedding array for sequences that have an embedding.
- get_embeddings_by_sequence(sequence: str, model_name: str | None = None, load_data: bool = False) → list[dict]
Get embeddings for a sequence.
- get_embeddings_concat(sequence_ids: list[int], model_names: list[str]) → dict[int, numpy.ndarray]
Fetch and concatenate embeddings from multiple models per sequence.
For each sequence_id, retrieves the embedding from each model in
model_namesorder and horizontally concatenates them into a single vector. Sequences missing an embedding from any requested model are omitted from the result.- Args:
sequence_ids: Sequence IDs to fetch. model_names: Ordered list of model names whose embeddings will be
concatenated (e.g.
["esm2-8m", "esmc-300m"]).- Returns:
Dict mapping
sequence_id→ concatenated numpy array.
- get_existing_input_columns() → list[str]
Return extra columns on the sequences table (beyond the base schema).
Used for input-schema validation when connecting to an existing DB. Returns an empty list if only the base columns are present.
- get_filter_results(run_id: str, stage_name: str) → list[int]
Return sequence_ids that passed a given filter stage in a run.
- Args:
run_id: Pipeline run ID. stage_name: Filter stage name.
- Returns:
List of sequence_ids that passed the filter, or empty list if no data.
- get_generation_metadata(sequence_id: int) → list[dict]
Return generation metadata records for a sequence_id.
- get_latest_definition_id() → str | None
Return the definition_id of the most recently created pipeline definition.
- get_latest_run_id(definition_id: str | None = None) → str | None
Return the run_id of the most recent pipeline run.
from_db()must reuse the existing run_id so that resume can find already-completed stages. A new run_id means no stages are marked complete, causing everything to re-run.- Args:
- definition_id: If provided, restrict to runs that used this definition.
If None, return the most recent run across all definitions.
- Returns:
The run_id string, or None if no runs exist.
- get_pipeline_metadata(key: str) → Any | None
Retrieve a value from pipeline_metadata by key.
- get_pipeline_run(run_id: str) → dict | None
Return pipeline run record as a dict, or None if not found.
- get_predictions(sequence_id: int, prediction_type: str | None = None, model_name: str | None = None) → DataFrame
Return predictions for a sequence_id as a DataFrame.
- get_predictions_bulk(sequence_ids: list[int], prediction_type: str, model_name: str) → DataFrame
Fetch predictions for multiple sequences in a single JOIN query.
Replaces N individual get_predictions_by_sequence() calls.
- Returns:
DataFrame with columns: sequence_id, value, metadata
- get_predictions_by_sequence(sequence: str, prediction_type: str | None = None, model_name: str | None = None) → DataFrame
Get predictions for a sequence.
- get_sequence(sequence_id: int) → str | None
Return the sequence string for a given sequence_id, or None if not found.
- get_sequence_attributes_for_ids(sequence_ids: list[int], attr_names: list[str]) → dict[int, dict[str, str]]
Retrieve per-sequence attributes, returning {seq_id: {attr: value}}.
- Args:
sequence_ids: Sequence IDs to look up. attr_names: Attribute names to retrieve.
- Returns:
Nested dict:
{sequence_id: {attr_name: attr_value}}.
- get_sequence_id(sequence: str) → int | None
Get sequence_id for a sequence.
- get_sequence_ids_with_prediction(sequence_ids: list[int], prediction_type: str, model_name: str) → list[int]
Return sequence_ids that DO have a given prediction (inverse of uncached check).
- Args:
sequence_ids: Candidate sequence IDs. prediction_type: Prediction type key. model_name: Model name.
- Returns:
List of sequence_ids that have a cached prediction.
- get_sequences_for_ids(sequence_ids: list[int]) → list[tuple[int, str]]
Fetch (sequence_id, sequence) pairs for the given IDs.
Lightweight fetch for building API request items without materializing a full DataFrame.
- Args:
sequence_ids: List of sequence IDs to look up.
- Returns:
List of (sequence_id, sequence_string) tuples.
- get_sequences_for_ids_with_columns(sequence_ids: list[int], columns: list[str]) → dict[int, dict[str, str]]
Fetch column values from the sequences table for given IDs.
This reads columns stored directly on the
sequencestable (viaensure_input_columns), NOT fromsequence_attributes.- Args:
sequence_ids: Sequence IDs to look up. columns: Column names to fetch (must exist on the sequences table).
- Returns:
{sequence_id: {col: value, ...}}
- get_stats() → dict[str, int]
Return row counts for the main tables.
- get_structure(sequence_id: int, model_name: str | None = None) → dict | None
Fetch the most recent structure for a sequence, decompressing on read.
Returns a dict with ‘structure_str’ key (always decompressed string) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.
- Args:
sequence_id: Sequence ID. model_name: Optional model filter.
- Returns:
Dict with structure record, or None if not found.
- get_structure_by_id(structure_id: int) → dict | None
Return a structure record by its structure_id (primary key).
- get_structures_bulk(sequence_ids: list[int]) → DataFrame
Fetch structures for multiple sequences, decompressing structure content.
Returns a DataFrame with a ‘structure_str’ column (always plain text) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.
- Args:
sequence_ids: List of sequence IDs to look up.
- Returns:
DataFrame with one row per structure record.
- get_structures_by_sequence(sequence: str, model_name: str | None = None) → list[dict]
Return structure records for a sequence string (decompressed).
- get_structures_for_ids(sequence_ids: list[int], model_name: str) → dict[int, dict]
Fetch the most recent structure for each sequence_id in one query.
Returns {sequence_id: record} where record has a ‘structure_str’ key. Replaces N individual get_structure() calls for batch structure injection.
- get_uncached_sequence_ids(sequence_ids: list[int], prediction_type: str, model_name: str) → list[int]
Return sequence_ids that do NOT yet have a given prediction (vectorized anti-join).
Replaces N individual has_prediction() calls with a single SQL query.
- Args:
sequence_ids: Candidate sequence IDs to check. prediction_type: Prediction type key. model_name: Model name.
- Returns:
List of sequence_ids with no cached prediction.
- has_prediction(sequence: str, prediction_type: str, model_name: str) → bool
Check if prediction exists for sequence.
- is_stage_complete(stage_id: str) → bool
Check if stage is complete.
- load_blob(blob_id: str) → str | None
Retrieve a stored blob by its blob_id. Returns None if not found.
- load_pipeline_definition(definition_id: str | None = None) → dict | None
Load a pipeline definition by ID, or the latest one if ID is None.
- make_filter_ws_name() → str
Mint a unique
_filter_ws_table name for one filter execution.Required when SQL-native filters can run in parallel at the same DAG level — sharing the canonical
_filter_wsregistration would let two stages clobber each other’s working set.
- mark_stage_complete(run_id: str, stage_name: str, stage_id: str, input_count: int, output_count: int, status: str = 'completed')
Mark stage as complete (or failed/skipped).
uses INSERT … ON CONFLICT DO NOTHING so that a stage that was already marked ‘completed’ does not get its completed_at timestamp overwritten on resume. Only truly new rows are inserted.
- materialize_working_set(ws: WorkingSet, include_predictions: bool = True, prediction_types: list[str] | None = None) → pd.DataFrame
Materialize a WorkingSet into a DataFrame via a single DuckDB pivot query.
- Args:
ws: WorkingSet containing the sequence IDs to materialize. include_predictions: If True, pivot prediction values into columns. prediction_types: Limit to specific types (None = all available).
- Returns:
Wide-format DataFrame: one row per sequence, one column per prediction type. Always includes the following columns regardless of pipeline configuration:
sequence_id,sequence,length,hash— core sequence data.source_label— label set on the generation config (e.g.DirectGenerationConfig.label,SaturationMutagenesisConfig.label).NULL/Nonewhen no label was supplied. This column is always present even when no labels have been set, so downstream code should not branch on"source_label" in df.columns— it is always there.
- query(sql: str, params: list | None = None) → DataFrame
Execute arbitrary SQL query and return DataFrame.
This is the POWER feature - query directly without loading everything!
- Args:
sql: DuckDB SQL query params: Optional query parameters
- Returns:
DataFrame with results (only loads what matches query!)
- Example:
-
default
>>> # Find high-quality long sequences >>> df = ds.query(''' ... SELECT s.sequence, p.value as plddt ... FROM sequences s ... JOIN predictions p ON s.sequence_id = p.sequence_id ... WHERE s.length > 200 ... AND p.prediction_type = 'plddt' ... AND p.value > 80 ... ''')
- register_column(column_name: str, model_name: str, action: str, definition_id: str, stage_name: str)
Register an output column in the prediction_column_registry. Idempotent.
- save_filter_results(run_id: str, stage_name: str, passed_sequence_ids: list[int])
Record which sequence_ids passed a filter stage (for resume support).
- Args:
run_id: Pipeline run ID. stage_name: Filter stage name. passed_sequence_ids: IDs of sequences that passed the filter.
- save_pipeline_definition(definition_id: str, pipeline_type: str, input_schema_json: str | None, stages_json: str)
Persist a pipeline definition. Updates stages/schema if definition_id already exists.
- set_context(run_id: str, key: str, value: Any)
Store a key-value pair in the pipeline context table.
- set_pipeline_metadata(key: str, value: Any)
Upsert a key/value pair in the pipeline_metadata table.
- store_blob(content: str) → str
Store a large string value, returning its blob_id (SHA-256[:32]).
Content-addressed: identical content always maps to the same blob_id. Safe to call multiple times with the same content (INSERT OR IGNORE).
- store_sequence_attributes(seq_ids: list[int], attr_name: str, attr_values: list[str])
Persist a per-sequence attribute column (e.g. heavy_chain, light_chain).
- Args:
seq_ids: Sequence IDs. attr_name: Attribute name (column name from the input DataFrame). attr_values: Corresponding values (one per sequence_id).
- update_pipeline_run_status(run_id: str, status: str)
Update pipeline run status.
biolm.pipeline.filters module
Filter implementations for pipeline stages.
Filters can be: - Per-sequence: Operate on individual sequences independently (streaming-compatible) - Aggregate: Require all data before filtering (must batch)
- class biolm.pipeline.filters.BaseFilter
Bases:
ABCBase class for filters.
- Attributes:
- requires_complete_data: If True, filter needs all data before filtering.
If False, can filter sequences as they arrive (streaming).
- requires_complete_data: bool = False
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.filters.CompositeFilter(*filters: BaseFilter)
Bases:
BaseFilterA serializable filter that chains sub-filters sequentially.
Unlike
CustomFilter,CompositeFilteris fully serializable viato_spec()/filter_from_spec()as long as every sub-filter is serializable. It also supports SQL fast-path evaluation when all sub-filters implementto_sql()and exactly one SQL filter is present.- Args:
*filters: Sub-filters applied left to right.
- Example:
-
default
>>> combined = CompositeFilter( ... ThresholdFilter('tm', min_value=60), ... SequenceLengthFilter(min_length=100), ... )
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', **kwargs) → str | None
Return SQL only when a single sub-filter supports it.
Full CTE-chaining for multiple SQL filters is complex to implement correctly (each stage’s output must feed the next as a temporary table). For now we support the single-SQL-filter case and fall back to DataFrame materialization for all other combinations.
- class biolm.pipeline.filters.ConservedResidueFilter(conserved_positions: dict[int, list[str]], reference_length: int | None = None)
Bases:
BaseFilterFilter sequences that have specific residues at specific positions.
- Args:
- conserved_positions: Dict mapping position (0-indexed) to allowed residues
e.g., {5: [‘M’, ‘L’], 10: [‘K’]}
reference_length: Expected sequence length (optional)
- Example:
-
default
>>> filter = ConservedResidueFilter({107: ['H'], 109: ['H'], 126: ['H']}) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.filters.CustomFilter(func: Callable[[DataFrame], DataFrame], name: str | None = None)
Bases:
BaseFilterApply a custom filter function.
- Args:
func: Function that takes a DataFrame and returns a filtered DataFrame name: Optional name for the filter (for repr)
- Example:
-
default
>>> def my_filter(df): ... return df[df['sequence'].str.contains('M')] >>> filter = CustomFilter(my_filter, name='contains_M') >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.filters.DiversitySamplingFilter(n_samples: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42, resample: bool = True)
Bases:
BaseFilterSample diverse sequences using clustering or random sampling.
This filter REQUIRES complete data to assess diversity.
- Args:
n_samples: Number of sequences to sample method: Sampling method (‘random’, ‘spread’, ‘top’) score_column: Column to use for ‘top’ method random_seed: Random seed for reproducibility resample: If False, only sample if not already sampled (default: True)
- Example:
-
default
>>> filter = DiversitySamplingFilter(n_samples=1000, method='random') >>> df_sampled = filter(df)
- requires_complete_data: bool = True
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.filters.HammingDistanceFilter(reference_sequence: str, max_distance: float | None = None, min_distance: float | None = None, normalize: bool = False)
Bases:
BaseFilterFilter by Hamming distance to a reference sequence.
- Args:
reference_sequence: Reference sequence max_distance: Maximum Hamming distance (inclusive) min_distance: Minimum Hamming distance (inclusive) normalize: If True, use normalized distance (0-1)
- Example:
-
default
>>> filter = HammingDistanceFilter('MKTAYIAKQ', max_distance=5) >>> df_filtered = filter(df)
- static hamming_distance(seq1: str, seq2: str, normalize: bool = False) → float
Calculate Hamming distance between two sequences.
For equal-length sequences this is the standard Hamming distance (count of positions where characters differ).
For sequences of different lengths (F09 — edge-case note): the distance is computed as the number of mismatches in the overlapping prefix PLUS the absolute difference in lengths (each extra character in the longer sequence counts as one mismatch). When
normalize=Truethe result is divided bymax(len(seq1), len(seq2)). This is a non-standard extension; callers comparing variable-length sequences should be aware that the normalized value is NOT equivalent to edit distance / Levenshtein normalized distance.
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.filters.RankingFilter(column: str, n: int | None = None, ascending: bool = False, method: str = 'top', percentile: float | None = None)
Bases:
BaseFilterFilter by ranking - select top N or bottom N by a column value.
This filter REQUIRES complete data to rank all sequences.
- Args:
column: Column name to rank by n: Number of sequences to select ascending: If True, select lowest values; if False, select highest (default) method: Ranking method (‘top’ for top N, ‘bottom’ for bottom N, ‘percentile’ for top/bottom %) percentile: If method=’percentile’, the percentile threshold (0-100)
- Example:
-
default
>>> # Top 100 by Tm >>> filter = RankingFilter('tm', n=100, ascending=False) >>> df_filtered = filter(df) >>> >>> # Bottom 50 by hamming distance >>> filter = RankingFilter('hamming_distance', n=50, ascending=True) >>> df_filtered = filter(df) >>> >>> # Top 10% by pLDDT >>> filter = RankingFilter('plddt', method='percentile', percentile=90) >>> df_filtered = filter(df)
- requires_complete_data: bool = True
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.filters.SequenceLengthFilter(min_length: int | None = None, max_length: int | None = None)
Bases:
BaseFilterFilter by sequence length.
- Args:
min_length: Minimum sequence length (inclusive) max_length: Maximum sequence length (inclusive)
- Example:
-
default
>>> filter = SequenceLengthFilter(min_length=50, max_length=500) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.filters.ThresholdFilter(column: str, min_value: float | None = None, max_value: float | None = None, keep_na: bool = False)
Bases:
BaseFilterFilter by column value threshold.
- Args:
column: Column name to filter on min_value: Minimum value (inclusive) max_value: Maximum value (inclusive) keep_na: Whether to keep rows with NaN values
- Example:
-
default
>>> filter = ThresholdFilter('tm', min_value=60.0) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.filters.ValidAminoAcidFilter(alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', verbose: bool = True, column: str = 'sequence')
Bases:
BaseFilterFilter sequences to only those composed of valid amino acid characters.
Uses vectorized regex matching via
str.match()(C-level regex engine), which is ~100x faster than.apply(lambda)at million-sequence scale.- Args:
alphabet: String of allowed characters (default: 20 standard amino acids) verbose: If True, print count of removed sequences
- Example:
-
default
>>> filter = ValidAminoAcidFilter() >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- biolm.pipeline.filters.combine_filters(*filters: BaseFilter) → BaseFilter
Combine multiple filters into a single filter (applied sequentially).
Returns a
CompositeFilterwhich is serializable (unlike the previousCustomFilter-based implementation) and supports SQL fast-path evaluation when all sub-filters implementto_sql().- Args:
*filters: Variable number of filter objects
- Returns:
Combined filter
- Example:
-
default
>>> filter = combine_filters( ... ThresholdFilter('tm', min_value=60), ... SequenceLengthFilter(min_length=100) ... )
biolm.pipeline.generative module
Generative pipeline for sequence generation using language models.
Supports: - Masked language models (ESM, ESM-1v) with remasking - Inherently generative models (ProteinMPNN, ProGen2, etc.) - Temperature and sampling parameter scanning - Multi-model generation in parallel - DMS-style scanning: SaturationMutagenesisConfig (single-mutant library + scoring)
and IterativeMaskingDMSConfig (greedy MLM argmax 2-point DMS)
Structured config hierarchy: ScoringProtocolConfig / GenerativeProtocolConfig base classes
- class biolm.pipeline.generative.DirectGenerationConfig(model_name: str, structure_path: str | None = None, structure_column: str | None = None, sequence: str | None = None, item_field: str = 'pdb', params: dict[str, typing.Any] = <factory>, num_sequences: int = 100, temperature: float = 1.0, structure_from_stage: str | None = None, structure_from_model: str | None = None, n_runs: int = 1, label: str | None = None)
Bases:
GenerativeProtocolConfigConfiguration for structure- or sequence-conditioned generation.
Use with models such as ProteinMPNN, AntiFold, HyperMPNN, LigandMPNN, DSM.
The caller is responsible for providing the correct
item_fieldandparamsfor the target model — these vary per model and are documented in the BioLM API schema (/schema/). Common values:/generate/ - Args:
- model_name: BioLM model slug (e.g.
'protein-mpnn','antifold', 'dsm-150m-base').
structure_path: Path to a PDB or CIF file. structure_column: DataFrame column holding PDB strings (for chained
pipelines where structure was predicted upstream).
sequence: Parent sequence string for sequence-conditioned models (DSM). item_field: The item dict key expected by the model API —
'pdb'forstructure-conditioned models,
'sequence'for DSM. Defaults to'pdb'.- params: Model-specific params dict (required for non-trivial calls).
Keys must exactly match the model’s API param names. When empty the stage sends
{'num_sequences': num_sequences, 'temperature': temperature}as a simple fallback — useful only for models that accept these exact param names.
num_sequences: Fallback when
paramsis empty (default 100). temperature: Fallback whenparamsis empty (default 1.0).- model_name: BioLM model slug (e.g.
Example:
defaultfrom biolm.pipeline.generative import DirectGenerationConfig, GenerativePipeline # Structure-conditioned design with ProteinMPNN cfg_mpnn = DirectGenerationConfig( model_name="protein-mpnn", structure_path="/path/to/protein.pdb", item_field="pdb", params={"num_sequences": 50, "temperature": 0.1}, ) # Sequence-conditioned design with DSM cfg_dsm = DirectGenerationConfig( model_name="dsm-150m-base", sequence="MKTAYIAKQRQ", item_field="sequence", params={ "num_sequences": 100, "temperature": 1.0, "remasking": "low_confidence", "step_divisor": 8, }, ) pipeline = GenerativePipeline(configs=[cfg_mpnn]) results = pipeline.run()- item_field: str = 'pdb'
- label: str | None = None
- model_name: str
- n_runs: int = 1
- num_sequences: int = 100
- params: dict[str, Any]
- sequence: str | None = None
- structure_column: str | None = None
- structure_from_model: str | None = None
- structure_from_stage: str | None = None
- structure_path: str | None = None
- temperature: float = 1.0
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.generative.FoldingEntity(id: str, entity_type: str, sequence: str | None = None, smiles: str | None = None, ccd: str | None = None)
Bases:
objectA molecular entity for co-folding models (Boltz2, Chai-1).
Used with
DataPipeline.add_cofolding_prediction()andGenerativePipeline.add_cofolding_prediction()to inject static entities — ligands, cofactors, DNA/RNA strands, or additional protein chains — alongside the pipeline’s primary sequences.- Args:
id: Chain identifier (Boltz uses single letter(s); Chai-1 uses a name). entity_type: Molecule type —
'protein','dna','rna', or'ligand'.sequence: Amino-acid / nucleotide sequence for protein / DNA / RNA entities. smiles: SMILES string for small-molecule ligands. ccd: CCD code for ligands defined in the Chemical Component Dictionary
(e.g.
'ATP','HEM').
- ccd: str | None = None
- entity_type: str
- id: str
- sequence: str | None = None
- smiles: str | None = None
- biolm.pipeline.generative.Generate(model_name: str, num_sequences: int = 100, temperature: float | list[float] = 1.0, parent_sequence: str | None = None, **kwargs) → DataFrame
Convenience function for quick sequence generation.
- Args:
model_name: BioLM model name num_sequences: Number of sequences to generate temperature: Temperature (float) or list of temperatures for a temperature scan.
When a list is given, one DirectGenerationConfig is created per temperature.
parent_sequence: Optional parent sequence for sequence-conditioned models **kwargs: Additional arguments forwarded to GenerativePipeline
- Returns:
DataFrame with generated sequences
- Example:
-
default
>>> df = Generate('dsm-150m-base', num_sequences=100, parent_sequence='MKTAY')
- class biolm.pipeline.generative.GenerationConfig(model_name: str, num_sequences: int = 100, temperature: float | list[float] = 1.0, sampling_params: dict[str, typing.Any] = <factory>, generation_method: str = 'generate', parent_sequence: str | None = None, mask_positions: str | list[int] = 'auto', mask_fraction: float = 0.15, batch_size: int = 32)
Bases:
objectConfiguration for sequence generation.
Deprecated since version Use:
RemaskingConfigfor MLM-based generation orDirectGenerationConfigfor structure-conditioned models.- Args:
model_name: BioLM model name num_sequences: Number of sequences to generate temperature: Temperature or list of temperatures to scan sampling_params: Additional sampling parameters generation_method: ‘generate’ or ‘remask’ (for MLMs) parent_sequence: Parent sequence (for remasking or conditioning) mask_positions: Positions to mask (for remasking), or ‘auto’ for automatic mask_fraction: Fraction of positions to mask (if mask_positions=’auto’) batch_size: Batch size for generation
- batch_size: int = 32
- generation_method: str = 'generate'
- mask_fraction: float = 0.15
- mask_positions: str | list[int] = 'auto'
- model_name: str
- num_sequences: int = 100
- parent_sequence: str | None = None
- sampling_params: dict[str, Any]
- temperature: float | list[float] = 1.0
- class biolm.pipeline.generative.GenerationStage(name: str = 'generation', config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig | None = None, configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, **kwargs)
Bases:
StageStage for generating sequences using generative models.
Accepts either the new typed configs (RemaskingConfig / DirectGenerationConfig) or the legacy GenerationConfig list.
For
DirectGenerationConfigthe caller supplies the correctitem_field(e.g.'pdb'or'sequence') andparamsdict with model-specific param names — no auto-detection is performed.The stage handles the three main response formats returned by BioLM generation models:
Flat list (MPNN):
[{sequence, pdb, ...}, ...]Nested samples (AntiFold):
[{sequences: [{heavy, light, ...}]}]Double-nested (DSM):
[[{sequence, log_prob, ...}, ...]]
- Args:
name: Stage name. config: Single RemaskingConfig or DirectGenerationConfig (new API). configs: List of config objects — GenerationConfig, RemaskingConfig, or
DirectGenerationConfig (old / multi-model API).
deduplicate: Whether to deduplicate generated sequences.
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Generate sequences using configured models.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Generate sequences and return a WorkingSet of the new IDs.
The input WorkingSet’s IDs are forwarded as
ws_idsso thatDirectGenerationConfigcan scope structure lookups to sequences already in the current pipeline run (GEN-05 consistency: no cross-run contamination whenstructure_from_stageis set).
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.generative.GenerativePipeline(generation_configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, configs: list[Union[biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, filters=None, data_store=None, **kwargs)
Bases:
BasePipelinePipeline for generating sequences and running predictions.
Supports: - Multiple generative models in parallel - Temperature scanning - Masked language model remasking - Downstream predictions and filtering
- Args:
generation_configs: List of GenerationConfig objects deduplicate: Whether to deduplicate generated sequences datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output
- Example:
-
default
>>> # Generate with MPNN at multiple temperatures >>> config1 = GenerationConfig( ... model_name='proteinmpnn', ... num_sequences=1000, ... temperature=[0.5, 1.0, 1.5], ... parent_sequence='MKTAYIAKQRQ' ... ) >>> >>> # Also generate with ESM using remasking >>> config2 = GenerationConfig( ... model_name='esm2', ... num_sequences=500, ... generation_method='remask', ... parent_sequence='MKTAYIAKQRQ', ... mask_fraction=0.15 ... ) >>> >>> pipeline = GenerativePipeline( ... generation_configs=[config1, config2] ... ) >>> pipeline.add_filter(ThresholdFilter('length', min_value=50)) >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> results = pipeline.run()
- add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities: list[biolm.pipeline.generative.FoldingEntity] | None = None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)
Add a co-folding prediction stage (Boltz2, Chai-1).
Each sequence in the pipeline is treated as the primary protein chain.
static_entitieslets you inject additional molecules — ligands, cofactors, DNA/RNA strands, or extra protein chains — that are held constant across all designs.- Args:
model_name: BioLM model slug, e.g.
'boltz2'or'chai1'. action: API action (default'predict'). stage_name: Stage name (defaults tomodel_name). prediction_type: Column name for the confidence score (default'structure').- sequence_chain_id: Chain ID assigned to the pipeline’s primary
sequence in the multi-molecule request (e.g.
'A').- sequence_entity_type: Molecule type for the primary sequence:
'protein','dna', or'rna'(default'protein').- static_entities: List of
FoldingEntityobjects — ligands, cofactors, extra proteins/DNA/RNA — included in every request.
depends_on: Upstream stage names this stage waits for. params: Model-specific params dict (e.g.
{'recycling_steps': 3, 'sampling_steps': 20}for Boltz).- batch_size: Sequences per API request (default 1; co-folding models
are typically batch-size-1).
Example:
defaultpipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), FoldingEntity(id='B', entity_type='protein', sequence='MKTAYIAKQRQ'), ], depends_on=['filter_top50'], )
- add_filter(filter_func, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a filter stage (same as DataPipeline).
- add_generation_config(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig) → GenerativePipeline
Append a config to the existing generation slot.
Use this to add a second model or temperature variant alongside the current generation config rather than replacing it. If there is no generation slot yet, one is created.
- Args:
config: Config to add.
- Returns:
Self, for method chaining.
- add_prediction(model_name: str, action: str = 'predict', extractions=None, columns=None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage (same as DataPipeline).
- add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)
Add multiple prediction stages at the same level (run in parallel).
- Args:
models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages
- Returns:
self for chaining
- Example:
-
default
>>> pipeline.add_predictions(['temberture-regression', 'proteinmpnn', 'esm2'])
- add_stage(stage: Stage) → None
Add a stage to the pipeline.
If stage is a
GenerationStageit replaces the current generation slot (there is always exactly one, at position 0) rather than appending. All other stage types are appended normally.
- replace_generation(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str | None = None, deduplicate: bool = True) → GenerativePipeline
Swap the generation slot with a single new config.
Equivalent to
set_generation(config, stage_name=stage_name). Kept for backwards compatibility and single-config convenience.
- async run_async(**kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the generative pipeline.
All GenerationStages (regardless of position) are extracted and run first as sources — their outputs are unioned into the initial WorkingSet so generated sequences trickle through every downstream prediction/filter stage (the “funnel”). Idempotent: self.stages is always restored after execution.
- set_generation(*configs: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str = 'generation', deduplicate: bool = True) → GenerativePipeline
Set (or replace) the generation slot with one or more configs.
Multiple configs run in parallel — use this for multi-model generation or temperature scanning. Every call to
.run()re-runs generation from scratch; downstream stages use their prediction cache so only truly new sequences are computed.- Args:
- *configs: One or more
RemaskingConfigor DirectGenerationConfigobjects.
stage_name: Name for the generation stage (default
"generation"). deduplicate: Deduplicate across all configs (default True).- *configs: One or more
- Returns:
Self, for method chaining.
Example:
default# Single model pipeline.set_generation( DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=200) ).run() # Two models in parallel — sequences from both trickle through the funnel pipeline.set_generation( DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=100), RemaskingConfig("esm-150m", mask_fraction=0.15), ).run()
- use_sequences(sequences=None, column: str = 'sequence', stage_name: str = 'data_source', from_db: bool = False) → GenerativePipeline
Use existing sequences as the pipeline source instead of generating.
Replaces the generation slot with a
SequenceSourceConfig. Downstream prediction and filter stages run on the provided sequences, using the normal DuckDB prediction cache for anything already computed.- Args:
sequences: One of:
list[str]— plain amino-acid stringspd.DataFrame— must contain column (default"sequence")str/Path— CSV or FASTA fileNone— reload all sequences already in the DuckDB
column: Column name when sequences is a DataFrame or CSV. stage_name: Name for the source stage (default
"data_source"). from_db: Pull all sequences already present in this pipeline’s DuckDB.- Returns:
Self, for method chaining.
Example:
default# Inject a list pipeline.use_sequences(["MKTAY", "MKLLIV"]).run() # Use all sequences already in the DB (e.g. recover + rerun) pipeline.use_sequences(from_db=True).run() # Load from CSV and run through the existing filter/predict stages pipeline.use_sequences("candidates.csv").run()
- class biolm.pipeline.generative.GenerativeProtocolConfig
Bases:
objectMarker base class for stages that produce new amino-acid sequences.
Subclasses drive generative or masked-language models to emit novel sequences — either via autoregressive sampling (ProteinMPNN, DSM, AntiFold) or by greedy-argmax masking over an MLM (ESM2, ESMC). Unlike
ScoringProtocolConfigsubclasses, these stages expand the candidate pool rather than filtering it; they are the source stage in a generative design pipeline.Use
isinstance(config, GenerativeProtocolConfig)()to branch on config type in pipeline dispatch functions.Example:
defaultfrom biolm.pipeline.generative import ( GenerativeProtocolConfig, DirectGenerationConfig, IterativeMaskingDMSConfig, ) def dispatch(config): if isinstance(config, GenerativeProtocolConfig): return run_generative_stage(config) # emits new sequences return run_scoring_stage(config) # returns ranked variants cfg_direct = DirectGenerationConfig( "dsm-150m-base", sequence="MKTAYIAKQRQ", num_sequences=50 ) cfg_dms = IterativeMaskingDMSConfig( parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m" ) assert isinstance(cfg_direct, GenerativeProtocolConfig) # True assert isinstance(cfg_dms, GenerativeProtocolConfig) # True
- class biolm.pipeline.generative.IterativeMaskingDMSConfig(parent_sequence: str, model_name: str, positions: list[int] | None = None, rounds: int = 2, mask_token: str = '<mask>', alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', exclude_synonymous: bool = True, batch_size: int = 32, label: str | None = None, action: str = 'predict')
Bases:
GenerativeProtocolConfigSource config that builds multi-point variants via sequential greedy masking.
Implements an iterative argmax masking procedure using a masked language model:
For each target position, mask it in the parent sequence and query the model for the highest-probability residue (greedy argmax — not sampled).
If
rounds > 1, apply the round-1 preferred substitution at each position and repeat for round 2: mask each other target position in the round-1 sequence and collect the argmax residue.Yield all resulting sequences as pipeline outputs.
This matches the ESM2 two-round DMS design pattern in the EGF generation notebook.
- Args:
parent_sequence: Starting sequence. model_name: MLM model slug (e.g.
'esm2-650m','esmc-300m'). positions: 0-indexed positions to probe. Defaults to all positions. rounds: Number of sequential masking rounds (default 2). Round N usesthe variant produced by round N-1 as its starting sequence.
mask_token: Token inserted at masked positions (default
'). alphabet: Vocabulary used to identify valid AA positions in logits.' Defaults to the 20 canonical amino acids.
- exclude_synonymous: Skip round-1 variants where the argmax matches WT
(default
True).
batch_size: Sequences per API request (default 32). label: Optional label stored as
source_labelin results. action: API action for the model (default'predict'; the model mustreturn logits). Only
'predict'is allowed because the greedy-argmax procedure requires per-token logit arrays.
Example:
defaultfrom biolm.pipeline.generative import IterativeMaskingDMSConfig, GenerativePipeline # Two-round greedy DMS at three positions using ESM2-650M config = IterativeMaskingDMSConfig( parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m", positions=[2, 5, 8], # 0-indexed; None → all positions rounds=2, # round 1: per-position; round 2: 2-point combos exclude_synonymous=True, ) pipeline = GenerativePipeline(configs=[config]) results = pipeline.run() # results DataFrame has columns: # sequence, dms_round, dms_pos1, dms_aa1, dms_pos2, dms_aa2- action: str = 'predict'
- alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
- batch_size: int = 32
- exclude_synonymous: bool = True
- label: str | None = None
- mask_token: str = '<mask>'
- model_name: str
- parent_sequence: str
- positions: list[int] | None = None
- rounds: int = 2
- to_spec() → dict
- class biolm.pipeline.generative.SaturationMutagenesisConfig(parent_sequence: str, scoring_model: str, positions: list[int] | None = None, alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', scoring_action: str = 'predict', scoring_params: dict[str, typing.Any] = <factory>, score_field: str = 'ddg', top_n: int | None = 50, ascending: bool = True, exclude_synonymous: bool = True, batch_size: int = 8, label: str | None = None, pdb_str: str | None = None, chain: str = 'A')
Bases:
ScoringProtocolConfigSource config that generates a single-mutant library and filters by a prediction model.
Enumerates every single amino-acid substitution at the specified positions, scores each variant with scoring_model using scoring_action, then returns the top-top_n variants ranked by score_field.
Typical use: ThermoMPNN-D or ESM2StabP-guided design — predict ΔΔG for all single-point mutants, keep the most stabilising ones.
- Args:
parent_sequence: Wild-type sequence to mutate. scoring_model: BioLM model slug used to score variants (e.g.
'thermompnn-d'). positions: 0-indexed positions to enumerate. IfNone, all positionsin the sequence are enumerated.
alphabet: Amino acids to substitute. Defaults to the 20 canonical AAs. scoring_action: API action for the scoring model (default
'predict'). scoring_params: Extra params forwarded to the scoring model API. score_field: Key inside each model response that holds the numeric score(default
'ddg'). Supports nested access with'.'separator, e.g.'result.ddg'.- top_n: Number of top-scoring variants to retain (default 50).
None keeps all variants that receive a valid score.
- ascending: If
True, lower scores are better (e.g. negative ΔΔG means stabilising). Defaults to
True.- exclude_synonymous: If
True(default), skip substitutions that are identical to the wild-type residue.
batch_size: Sequences per API request when scoring (default 8). label: Optional label stored as
source_labelin results. pdb_str: Raw PDB file contents as a string (not a file path). Whenprovided, each scoring item is built as
{"pdb": pdb_str, "mutations": ["instead of"], "chain": chain} {"sequence": mutant_sequence}. Required by structure-aware models such as ThermoMPNN-D. PassNone(default) for sequence-only models like ESM2StabP.- chain: Chain identifier forwarded in structure-aware scoring items
(default
'A').
- top_n: Number of top-scoring variants to retain (default 50).
Example:
defaultfrom biolm.pipeline.generative import SaturationMutagenesisConfig, GenerativePipeline # Score all single-mutant variants at three positions with ThermoMPNN-D config = SaturationMutagenesisConfig( parent_sequence="MKTAYIAKQRQ", scoring_model="thermompnn-d", positions=[3, 7, 10], # 0-indexed; None → all positions score_field="ddg", # key in API response holding ΔΔG top_n=25, # keep top-25 most stabilising variants ascending=True, # lower ΔΔG = more stabilising pdb_str=open("protein.pdb").read(), # required by structure-aware models ) pipeline = GenerativePipeline(configs=[config]) pipeline.add_prediction("esmfold", extractions="mean_plddt", columns="plddt") results = pipeline.run()- alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
- ascending: bool = True
- batch_size: int = 8
- chain: str = 'A'
- exclude_synonymous: bool = True
- label: str | None = None
- parent_sequence: str
- pdb_str: str | None = None
- positions: list[int] | None = None
- score_field: str = 'ddg'
- scoring_action: str = 'predict'
- scoring_model: str
- scoring_params: dict[str, Any]
- to_spec() → dict
- top_n: int | None = 50
- class biolm.pipeline.generative.ScoringProtocolConfig
Bases:
objectMarker base class for stages that score sequences and return a ranked subset.
Subclasses call a BioLM prediction model to assign a numeric score to each variant sequence enumerated by the config (e.g. all single-point mutants), then sort and return the top-N results. They do not produce wholly new sequences — the output is a scored, ranked subset of the candidate library built internally by the config (no separate generation step is needed).
The
scoring_actionfield on concrete subclasses selects the client method:'predict'callsBioLMApiClient.predict()and'score'callsBioLMApiClient.score(). Both are allowlisted ('predict','score') to prevent arbitrary method dispatch.Use
isinstance(config, ScoringProtocolConfig)()to branch on config type in pipeline dispatch functions.Example:
defaultfrom biolm.pipeline.generative import ( ScoringProtocolConfig, SaturationMutagenesisConfig, ) def dispatch(config): if isinstance(config, ScoringProtocolConfig): return run_scoring_stage(config) # returns ranked variant library return run_generative_stage(config) # returns novel sequences config = SaturationMutagenesisConfig( parent_sequence="MKTAYIAKQRQ", scoring_model="thermompnn-d", score_field="ddg", ) assert isinstance(config, ScoringProtocolConfig) # True
- class biolm.pipeline.generative.SequenceSourceConfig(sequences: list | DataFrame | str | Path | None = None, column: str = 'sequence', from_db: bool = False)
Bases:
objectUse existing sequences as the generation-slot source — no API calls made.
Plug into
set_generation()(or usepipeline.use_sequences()) to feed existing data through prediction/filter stages without generating new sequences. The provided sequences are added to the DuckDB via the normal dedup path, so sequences already present just return their existing IDs.- Args:
sequences: Source of sequences — one of:
list[str]: plain amino-acid stringspd.DataFrame: must contain column (default"sequence")str/Path: path to a CSV or FASTA (.fasta/.fa) fileNone: reload all sequences already in the DuckDB (requiresfrom_db=TrueOR leaving sequences as None)
- column: Column name when sequences is a DataFrame or CSV
(default
"sequence").- from_db: Pull all sequences already present in the pipeline’s DuckDB
instead of loading new ones. Equivalent to
sequences=None.
Example:
default# Inject a list pipeline.use_sequences(["MKTAY", "MKLLIV"]).run() # Use all sequences already in the DB (e.g. after from_db() recovery) pipeline.use_sequences(from_db=True).run() # Load from CSV pipeline.use_sequences("candidates.csv").run()- column: str = 'sequence'
- from_db: bool = False
- sequences: list | DataFrame | str | Path | None = None
- to_spec() → dict
Serialize for pipeline definition persistence.
Live DataFrames are not serializable — on reconstruct we fall back to
from_db=Trueso the existing DB sequences are reused. Plainlist[str]sequences are included directly so from_db() reconstruction can replay the same input without requiring an existing DB (GEN-09 fix).
biolm.pipeline.mlm_remasking module
Masked Language Model (MLM) remasking utilities.
Provides functionality for iterative remasking and prediction using masked language models like ESM, ESM-1v, ESM-2.
- class biolm.pipeline.mlm_remasking.MLMRemasker(config: RemaskingConfig, api_client=None, model_name: str | None = None)
Bases:
objectMasked Language Model remasking utility.
Handles iterative masking and prediction for generating sequence variants using masked language models.
- Args:
config: RemaskingConfig instance api_client: BioLM API client (optional, for actual predictions) model_name: Model name (e.g., ‘esm2’, ‘esm1v’)
- Example:
-
default
>>> config = RemaskingConfig(mask_fraction=0.15, num_iterations=5) >>> remasker = MLMRemasker(config, model_name='esm2') >>> variants = remasker.generate_variants('MKTAYIAKQRQ', num_variants=10)
- create_masked_sequence(sequence: str, positions: list[int]) → str
Create masked sequence with mask token at specified positions.
- Args:
sequence: Original sequence positions: Positions to mask
- Returns:
Masked sequence
- async generate_variant(parent_sequence: str, iteration: int = 0) → tuple[str, dict[str, Any]]
Generate a single variant through remasking (async).
- Args:
parent_sequence: Starting sequence iteration: Iteration number (for seeding)
- Returns:
Tuple of (variant_sequence, metadata_dict)
- async generate_variants(parent_sequence: str, num_variants: int = 100, deduplicate: bool = True) → list[tuple[str, dict[str, Any]]]
Generate multiple variants through remasking (async, concurrent).
Uses asyncio.gather with a semaphore to generate variants concurrently rather than sequentially, significantly reducing wall-clock time when the API client supports concurrent calls.
- Args:
parent_sequence: Starting sequence num_variants: Number of variants to generate deduplicate: Remove duplicate sequences (parent excluded when True)
- Returns:
List of (variant_sequence, metadata) tuples
- async iterative_refinement(sequence: str, fitness_function: callable, num_iterations: int = 10, population_size: int = 20, keep_top_k: int = 5) → list[tuple[str, float, dict[str, Any]]]
Perform iterative refinement using remasking and a fitness function (async).
- Args:
sequence: Starting sequence fitness_function: Function that scores sequences (higher is better) num_iterations: Number of refinement iterations population_size: Number of variants per iteration keep_top_k: Number of top sequences to keep per iteration
- Returns:
List of (sequence, fitness, metadata) tuples for final population
- async predict_masked_positions(sequence: str, mask_positions: list[int]) → tuple[str, dict[int, float]]
Predict amino acids at masked positions.
Builds the masked sequence client-side (inserting
config.mask_tokenat each position), sends it to the model’s predict endpoint, and decodes the returned logits with temperature/top-k/top-p sampling.Falls back to reading a
"sequence"key from the response if the model returns a filled sequence directly instead of logits.- Args:
sequence: Original (unmasked) sequence. mask_positions: 0-indexed positions to replace.
- Returns:
Tuple of (predicted_sequence, confidences_dict)
- select_mask_positions(sequence: str, confidences: ndarray | None = None) → list[int]
Select positions to mask based on strategy.
- Args:
sequence: Input sequence confidences: Optional confidence scores per position (for low_confidence strategy)
- Returns:
List of positions to mask (0-indexed)
- class biolm.pipeline.mlm_remasking.RemaskingConfig(model_name: str = 'esm-150m', action: str = 'predict', mask_fraction: float = 0.15, mask_positions: str | list[int] = 'auto', num_iterations: int = 1, temperature: float = 1.0, top_k: int | None = None, top_p: float | None = None, mask_token: str = '<mask>', conserved_positions: list[int] | None = None, mask_strategy: str = 'random', block_size: int = 3, confidence_threshold: float = 0.8, parent_sequence: str | None = None, num_variants: int = 100)
Bases:
objectConfiguration for MLM remasking.
- Args:
model_name: MLM model to use (e.g., ‘esm-150m’, ‘esm-650m’, ‘esm-3b’, ‘esm3’, ‘esmc’) mask_fraction: Fraction of positions to mask (default: 0.15) mask_positions: Specific positions to mask, or ‘auto’ for random num_iterations: Number of remasking iterations (default: 1) temperature: Sampling temperature (default: 1.0) top_k: Top-k sampling (default: None) top_p: Nucleus sampling (default: None) mask_token: Token to use for masking (default: ‘<mask>’) conserved_positions: Positions that should never be masked mask_strategy: Strategy for selecting positions (‘random’, ‘low_confidence’, ‘blocks’) block_size: Size of blocks for block masking (default: 3) confidence_threshold: Threshold for low-confidence masking (default: 0.8)
- Example:
-
default
>>> # ESM2 150M remasking >>> config = RemaskingConfig( ... model_name='esm-150m', ... mask_fraction=0.15, ... num_iterations=5, ... temperature=1.0 ... ) >>> >>> # ESM3 with higher temperature >>> config = RemaskingConfig( ... model_name='esm3', ... mask_fraction=0.20, ... temperature=1.5 ... )
- action: str = 'predict'
- block_size: int = 3
- confidence_threshold: float = 0.8
- conserved_positions: list[int] | None = None
- mask_fraction: float = 0.15
- mask_positions: str | list[int] = 'auto'
- mask_strategy: str = 'random'
- mask_token: str = '<mask>'
- model_name: str = 'esm-150m'
- num_iterations: int = 1
- num_variants: int = 100
- parent_sequence: str | None = None
- temperature: float = 1.0
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- top_k: int | None = None
- top_p: float | None = None
- biolm.pipeline.mlm_remasking.create_remasker_from_dict(config_dict: dict[str, Any], **kwargs) → MLMRemasker
Create MLMRemasker from a configuration dictionary.
- Args:
config_dict: Dictionary with configuration parameters **kwargs: Additional arguments for MLMRemasker
- Returns:
MLMRemasker instance
- Example:
-
default
>>> config = { ... 'mask_fraction': 0.2, ... 'num_iterations': 5, ... 'temperature': 1.0 ... } >>> remasker = create_remasker_from_dict(config, model_name='esm2')
biolm.pipeline.pipeline_def module
Pipeline definition persistence and reconstruction.
Provides factory functions for serializing pipeline definitions to DuckDB and reconstructing pipeline objects from stored definitions (for kernel-death recovery via DataPipeline.from_db() / GenerativePipeline.from_db()).
- biolm.pipeline.pipeline_def.filter_from_spec(spec: dict) → BaseFilter
Reconstruct a
BaseFilterfrom itsto_spec()dict.- Raises:
NotImplementedError: For
CustomFilter(func is not serializable). ValueError: For unknown filter types.
- biolm.pipeline.pipeline_def.pipeline_from_definition(defn: dict, datastore: DuckDBDataStore, run_id: str | None = None, verbose: bool = True) → BasePipeline
Reconstruct a pipeline from a stored definition dict.
- Args:
defn: Dict as returned by
DuckDBDataStore.load_pipeline_definition(). datastore: Open DuckDB datastore to attach to the pipeline. run_id: Run ID to resume (None= look up the most recent run for thisdefinition, or generate a fresh one if no prior runs exist). when resuming via
from_db(), we must reuse the existing run_id so thatstage_id = f"{run_id}_{stage_name}"matches the stage_completions rows written by the previous run. Passing a new run_id causes all stages to appear incomplete and re-run.verbose: Enable verbose output.
- Returns:
A fully configured
DataPipelineorGenerativePipelineready to call.run(resume=True)on.- Raises:
- NotImplementedError: If any stage cannot be auto-reconstructed
(e.g. CustomFilter, custom callable embedding_extractor).
ValueError: For unknown pipeline types.
- biolm.pipeline.pipeline_def.stage_from_spec(spec: dict) → Stage
Reconstruct a
Stagefrom itsto_spec()dict.- Raises:
NotImplementedError: For stage types that cannot be auto-reconstructed. ValueError: For unknown stage types.
biolm.pipeline.utils module
Utility functions for pipeline operations.
Also includes structure file conversion utilities (CIF ↔ PDB) for use with structure-conditioned generative models (AntiFold, HyperMPNN, etc.).
- biolm.pipeline.utils.cif_to_pdb(cif_path: str, output_path: str | None = None) → str
Convert a CIF structure file to PDB format.
Uses gemmi if available, falls back to biopython.
- Args:
cif_path: Path to input CIF (.cif / .mmcif) file. output_path: Destination PDB path. Defaults to same name with .pdb extension.
- Returns:
Path to written PDB file.
- Raises:
ImportError: If neither gemmi nor biopython is installed.
- biolm.pipeline.utils.compute_hamming_distance(seq1: str, seq2: str, normalize: bool = False) → float
Compute Hamming distance between two sequences.
- Args:
seq1: First sequence seq2: Second sequence normalize: If True, return normalized distance (0-1)
- Returns:
Hamming distance
- biolm.pipeline.utils.compute_sequence_identity(seq1: str, seq2: str) → float
Compute sequence identity (fraction of identical positions).
- Args:
seq1: First sequence seq2: Second sequence
- Returns:
Sequence identity (0.0 to 1.0)
- biolm.pipeline.utils.create_run_summary(pipeline) → dict[str, Any]
Create a summary dict of pipeline run.
- Args:
pipeline: Pipeline instance
- Returns:
Summary dict
- biolm.pipeline.utils.deduplicate_sequences(sequences: list[str] | DataFrame, sequence_column: str = 'sequence') → list[str] | DataFrame
Remove duplicate sequences.
- Args:
sequences: List of sequences or DataFrame sequence_column: Column name if DataFrame
- Returns:
Deduplicated sequences (same type as input)
- biolm.pipeline.utils.export_run_summary(pipeline, output_path: str | Path)
Export pipeline run summary to JSON.
- Args:
pipeline: Pipeline instance output_path: Output file path
- biolm.pipeline.utils.hash_sequence(sequence: str) → str
Generate SHA256 hash of a sequence.
- biolm.pipeline.utils.load_fasta(file_path: str | Path) → list[str]
Load sequences from FASTA file.
- Args:
file_path: Path to FASTA file
- Returns:
List of sequences
- biolm.pipeline.utils.load_sequences_from_file(file_path: str | Path, format: str | None = None) → list[str]
Load sequences from a file.
Supports: - FASTA (.fasta, .fa, .faa) - CSV (.csv) with ‘sequence’ column - Plain text (one sequence per line)
- Args:
file_path: Path to file format: Optional format override (‘fasta’, ‘csv’, ‘txt’)
- Returns:
List of sequences
- Example:
-
default
>>> sequences = load_sequences_from_file('sequences.fasta')
- biolm.pipeline.utils.load_structure_string(path: str) → tuple[str, str]
Load a structure file and return (format, content_string).
- Args:
path: Path to a .pdb, .ent, .cif, or .mmcif file.
- Returns:
Tuple of (format_str, content_str) where format_str is ‘pdb’ or ‘cif’.
- Raises:
ValueError: If file extension is not recognised. FileNotFoundError: If file does not exist.
- biolm.pipeline.utils.merge_prediction_results(df: DataFrame, predictions: dict[str, list[float]], prediction_names: list[str] | None = None) → DataFrame
Merge prediction results into DataFrame.
- Args:
df: Base DataFrame predictions: Dict mapping prediction names to value lists prediction_names: Optional list of column names
- Returns:
DataFrame with predictions added
- biolm.pipeline.utils.pdb_to_cif(pdb_path: str, output_path: str | None = None) → str
Convert a PDB structure file to CIF format.
Uses gemmi if available, falls back to biopython.
- Args:
pdb_path: Path to input PDB (.pdb / .ent) file. output_path: Destination CIF path. Defaults to same name with .cif extension.
- Returns:
Path to written CIF file.
- Raises:
ImportError: If neither gemmi nor biopython is installed.
- biolm.pipeline.utils.sample_sequences(sequences: list[str] | DataFrame, n: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42) → list[str] | DataFrame
Sample n sequences from a collection.
- Args:
sequences: List or DataFrame n: Number to sample method: ‘random’, ‘top’, or ‘spread’ score_column: Column for ‘top’ or ‘spread’ methods (DataFrame only) random_seed: Random seed
- Returns:
Sampled sequences (same type as input)
- biolm.pipeline.utils.split_sequences_by_length(sequences: list[str], boundaries: list[int]) → dict[str, list[str]]
Split sequences into bins by length.
- Args:
sequences: List of sequences boundaries: List of length boundaries [b1, b2, …]
Creates bins: <b1, b1-b2, b2-b3, …, >bn
- Returns:
Dict mapping bin names to sequence lists
- Example:
-
default
>>> bins = split_sequences_by_length(seqs, [100, 200, 300]) >>> # Returns: {'300': [...]}
- biolm.pipeline.utils.summarize_dataframe(df: DataFrame) → DataFrame
Create a summary of DataFrame columns.
- Returns:
Summary DataFrame with statistics
- biolm.pipeline.utils.validate_sequence(sequence: str, alphabet: str = 'protein') → bool
Validate that a sequence contains only valid amino acids.
- Args:
sequence: Sequence to validate alphabet: ‘protein’ or ‘dna’
- Returns:
True if valid, False otherwise
- biolm.pipeline.utils.write_fasta(sequences: list[str] | DataFrame, file_path: str | Path, headers: list[str] | None = None, sequence_column: str = 'sequence', header_column: str | None = None)
Write sequences to FASTA file.
- Args:
sequences: List of sequences or DataFrame file_path: Output file path headers: Optional list of headers (one per sequence) sequence_column: Column name for sequences (if DataFrame) header_column: Column name for headers (if DataFrame)
- Example:
-
default
>>> write_fasta(sequences, 'output.fasta') >>> write_fasta(df, 'output.fasta', header_column='id')
biolm.pipeline.visualization module
Visualization utilities for pipeline results.
Provides plotting functions for: - Pipeline funnel diagrams - Prediction distributions - Embedding visualizations (PCA, UMAP) - Correlation matrices - Temperature scan results
- class biolm.pipeline.visualization.PipelinePlotter(pipeline, df: DataFrame | None = None)
Bases:
objectConvenience class for plotting pipeline results.
- Args:
pipeline: Pipeline instance df: Optional DataFrame (uses pipeline.get_final_data() if not provided)
- Example:
-
default
>>> plotter = PipelinePlotter(pipeline) >>> plotter.plot_funnel() >>> plotter.plot_distribution('tm')
- plot_correlation_matrix(**kwargs)
Plot correlation matrix.
- plot_distribution(column: str, **kwargs)
Plot distribution of a column.
- plot_distributions(columns: list[str] | None = None, **kwargs)
Alias for plot_predictions — plot distributions of prediction columns.
- plot_diversity(reference_sequence: str | None = None, **kwargs)
Plot sequence diversity.
- plot_funnel(**kwargs)
Plot pipeline funnel.
- plot_predictions(columns: list[str] | None = None, **kwargs)
Plot distributions of all numeric prediction columns in a multi-panel layout.
- Args:
- columns: Specific columns to plot. If None, auto-detects all numeric
columns except sequence_id, length, and hash.
**kwargs: Forwarded to matplotlib (e.g. figsize, bins).
- plot_scatter(x_col: str, y_col: str, **kwargs)
Plot scatter plot.
- plot_temperature_scan(metric_col: str, **kwargs)
Plot temperature scan results.
- biolm.pipeline.visualization.plot_correlation_matrix(df: DataFrame, columns: list[str] | None = None, figsize: tuple[int, int] = (10, 8), save_path: Path | None = None)
Plot correlation matrix heatmap.
- Args:
df: DataFrame columns: Optional list of columns to include (defaults to all numeric) figsize: Figure size save_path: Optional path to save figure
- Example:
-
default
>>> plot_correlation_matrix(df, columns=['tm', 'plddt', 'solubility'])
- biolm.pipeline.visualization.plot_distribution(df: DataFrame, column: str, bins: int = 50, figsize: tuple[int, int] = (10, 6), title: str | None = None, xlabel: str | None = None, save_path: Path | None = None)
Plot distribution of a column.
- Args:
df: DataFrame column: Column name to plot bins: Number of bins figsize: Figure size title: Optional title xlabel: Optional x-axis label save_path: Optional path to save figure
- Example:
-
default
>>> plot_distribution(df, 'tm', title='Tm Distribution')
- biolm.pipeline.visualization.plot_embedding_pca(embeddings: ndarray, labels: ndarray | None = None, n_components: int = 2, figsize: tuple[int, int] = (10, 8), title: str = 'PCA of Embeddings', save_path: Path | None = None)
Plot PCA of embeddings.
- Args:
embeddings: Array of embeddings (n_samples, n_features) labels: Optional array of labels for coloring n_components: Number of PCA components (2 or 3) figsize: Figure size title: Plot title save_path: Optional path to save figure
- Example:
-
default
>>> embeddings = np.array([...]) # Load from datastore >>> plot_embedding_pca(embeddings, labels=df['temperature'])
- biolm.pipeline.visualization.plot_embedding_umap(embeddings: ndarray, labels: ndarray | None = None, n_neighbors: int = 15, min_dist: float = 0.1, figsize: tuple[int, int] = (10, 8), title: str = 'UMAP of Embeddings', save_path: Path | None = None)
Plot UMAP of embeddings.
- Args:
embeddings: Array of embeddings (n_samples, n_features) labels: Optional array of labels for coloring n_neighbors: UMAP n_neighbors parameter min_dist: UMAP min_dist parameter figsize: Figure size title: Plot title save_path: Optional path to save figure
- Example:
-
default
>>> embeddings = np.array([...]) # Load from datastore >>> plot_embedding_umap(embeddings, labels=df['temperature'])
- biolm.pipeline.visualization.plot_pipeline_funnel(stage_results: dict[str, Any], figsize: tuple[int, int] = (10, 6), save_path: Path | None = None)
Plot pipeline funnel showing sequence counts through stages.
- Args:
stage_results: Dict mapping stage names to StageResult objects figsize: Figure size save_path: Optional path to save figure
- Example:
-
default
>>> plot_pipeline_funnel(pipeline.stage_results)
- biolm.pipeline.visualization.plot_scatter(df: DataFrame, x_col: str, y_col: str, color_col: str | None = None, figsize: tuple[int, int] = (10, 8), title: str | None = None, save_path: Path | None = None, alpha: float = 0.6)
Plot scatter plot of two columns.
- Args:
df: DataFrame x_col: X-axis column y_col: Y-axis column color_col: Optional column for coloring points figsize: Figure size title: Optional title save_path: Optional path to save figure alpha: Point transparency
- Example:
-
default
>>> plot_scatter(df, 'tm', 'plddt', color_col='temperature')
- biolm.pipeline.visualization.plot_sequence_diversity(df: DataFrame, reference_sequence: str | None = None, figsize: tuple[int, int] = (12, 5), save_path: Path | None = None)
Plot sequence diversity metrics.
- Args:
df: DataFrame with ‘sequence’ column reference_sequence: Optional reference sequence for Hamming distance figsize: Figure size save_path: Optional path to save figure
- Example:
-
default
>>> plot_sequence_diversity(df, reference_sequence='MKTAYIAKQRQ')
- biolm.pipeline.visualization.plot_temperature_scan(df: DataFrame, metric_col: str, temperature_col: str = 'temperature', figsize: tuple[int, int] = (10, 6), save_path: Path | None = None)
Plot results of temperature scanning.
- Args:
df: DataFrame with temperature and metric columns metric_col: Column with metric to plot temperature_col: Column with temperature values figsize: Figure size save_path: Optional path to save figure
- Example:
-
default
>>> plot_temperature_scan(df, 'tm', temperature_col='temperature')
Module contents
BioLM Pipeline System
A comprehensive pipeline framework for biological sequence generation, prediction, and analysis.
Requires optional dependencies — install with:
pip install "biolm-sdk[pipeline]"
- class biolm.pipeline.BasePipeline(datastore: DuckDBDataStore | str | Path | None = None, run_id: str | None = None, output_dir: str | Path = 'pipeline_outputs', resume: bool = False, verbose: bool = True, input_schema: InputSchema | None = None)
Bases:
ABCBase class for all pipeline types.
Provides: - Stage management and dependency resolution - Async execution with progress tracking - Caching and resumability - Export and visualization
When no
datastoreis provided, the pipeline automatically creates a DuckDB cache under.biolm/pipelines/. The/ pipeline_id(and full cache path) is exposed viametadataso users can reconnect to the same cache in later sessions.- Args:
datastore: DataStore instance or path to a DuckDB file. Required. run_id: Unique run identifier (auto-generated if not provided). output_dir: Directory for CSV/Parquet exports (default
pipeline_outputs). resume: Whether to resume from a previous run. verbose: Enable verbose output.
- add_stage(stage: Stage)
Add a stage to the pipeline.
- close()
Close the pipeline’s datastore connection.
Safe to call multiple times. Called automatically by
__exit__and__aexit__. Also called by__del__for auto-created datastores only (user-provided datastores are not closed on GC so the caller can continue using them after the pipeline is discarded).
- export_to_csv(output_path: str | Path | None = None)
Export final results to CSV.
- classmethod from_db(db_path: str | Path, definition_id: str | None = None, run_id: str | None = None, verbose: bool = True) → BasePipeline
Reconstruct a pipeline from an existing DuckDB database.
Useful for recovering after a kernel death without re-running already-completed stages.
- Args:
db_path: Path to the DuckDB database file. definition_id: Specific definition to load (
None= latest). run_id: Run ID for the reconstructed pipeline (None= generate new). verbose: Enable verbose output.- Returns:
Reconstructed
BasePipelinesubclass instance.
Example:
defaultpipeline = DataPipeline.from_db("my_pipeline.duckdb") pipeline.run(resume=True)
- get_final_data() → DataFrame
Get the final output DataFrame.
Materializes from the merged final WorkingSet via DuckDB. In a branched DAG the last-added stage is not necessarily the last executed sink, so we prefer
_final_ws(set at end of run()).The returned DataFrame always contains at minimum:
sequence_id,sequence,length,hashsource_label— label from the generation config;Nonewhen unset. Always present regardless of whether any labels were set.One column per prediction type from upstream prediction stages.
- property metadata: PipelineMetadata
Return metadata for reconnecting to this pipeline’s cache later.
- query(sql: str, params=None) → DataFrame
Execute arbitrary SQL against the pipeline’s DuckDB datastore.
- results() → DataFrame
Return the final output DataFrame. Alias for
get_final_data().The returned DataFrame always contains at minimum:
sequence_id,sequence,length,hashsource_label— the label set on the generation config (e.g.DirectGenerationConfig.label).Nonewhen no label was set. Always present — do not guard on"source_label" in df.columns.One column per prediction type computed by any prediction stages.
- run(enable_streaming: bool = True, **kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the pipeline synchronously.
This is a convenience wrapper around run_async(). Works in both script/notebook environments (detects running event loops).
- async run_async(enable_streaming: bool = True, **kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the pipeline asynchronously.
- Args:
- enable_streaming: Stream prediction results through per-sequence
filters for better parallelism and lower latency (default True).
- Returns:
Dict mapping stage names to StageResults
- summary() → DataFrame
Get pipeline summary statistics.
- class biolm.pipeline.ClusteringResult(cluster_ids: ndarray, centroids: list[str], centroid_indices: ndarray, n_clusters: int, silhouette_score: float | None = None, davies_bouldin_score: float | None = None, cluster_sizes: dict[int, int] | None = None)
Bases:
objectResults from sequence clustering.
- centroid_indices: ndarray
- centroids: list[str]
- cluster_ids: ndarray
- cluster_sizes: dict[int, int] | None = None
- davies_bouldin_score: float | None = None
- n_clusters: int
- silhouette_score: float | None = None
- class biolm.pipeline.CofoldingPredictionStage(name: str, model_name: str, action: str = 'predict', prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, params: dict | None = None, batch_size: int = 1, item_columns: dict[str, str] | None = None, **kwargs)
Bases:
StagePrediction stage for co-folding models (Boltz2, Chai-1).
Each sequence in the pipeline DataFrame is used as the primary entity (chain) in a multi-molecule folding request. Additional static entities — ligands, cofactors, DNA/RNA, or other protein chains — are injected via
static_entitiesand held constant for every sequence.The caller is responsible for providing the correct molecule field names and params for the target model. Boltz2 and Chai-1 both expect an item shaped like
{'molecules': [{'id': ..., 'type': ..., 'sequence': ...}, ...]}.- Args:
name: Stage name. model_name: BioLM model slug (
'boltz2','chai1'). action: API action (default'predict'). prediction_type: Column name written for the confidence score(default
'structure').- sequence_chain_id: Chain ID / name assigned to the pipeline’s primary
sequence in the molecules list.
- sequence_entity_type: Molecule type for the primary sequence
(
'protein','dna','rna').- static_entities: List of
FoldingEntity objects appended to every molecules list after the primary chain.
- params: Model-specific params dict passed directly to the API
(e.g.
{'recycling_steps': 3, 'sampling_steps': 20}for Boltz).- batch_size: Sequences per API call (default 1; co-folding models are
typically limited to batch size 1).
Example:
defaultfrom biolm.pipeline import FoldingEntity pipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), ], params={'recycling_steps': 3, 'sampling_steps': 20}, depends_on=['filter_top50'], )- merge_mode: str = 'union'
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Run co-folding prediction for each sequence in df.
- async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Run co-folding prediction using WorkingSet — no DataFrame transport.
- to_spec() → dict
Serialize to a dict for pipeline definition persistence.
Note:
static_entities(FoldingEntity objects) are not serializable and are omitted. A reconstructed pipeline will not have static_entities and must have them re-attached manually afterfrom_db().
- class biolm.pipeline.CompositeFilter(*filters: BaseFilter)
Bases:
BaseFilterA serializable filter that chains sub-filters sequentially.
Unlike
CustomFilter,CompositeFilteris fully serializable viato_spec()/filter_from_spec()as long as every sub-filter is serializable. It also supports SQL fast-path evaluation when all sub-filters implementto_sql()and exactly one SQL filter is present.- Args:
*filters: Sub-filters applied left to right.
- Example:
-
default
>>> combined = CompositeFilter( ... ThresholdFilter('tm', min_value=60), ... SequenceLengthFilter(min_length=100), ... )
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', **kwargs) → str | None
Return SQL only when a single sub-filter supports it.
Full CTE-chaining for multiple SQL filters is complex to implement correctly (each stage’s output must feed the next as a temporary table). For now we support the single-SQL-filter case and fall back to DataFrame materialization for all other combinations.
- class biolm.pipeline.ConservedResidueFilter(conserved_positions: dict[int, list[str]], reference_length: int | None = None)
Bases:
BaseFilterFilter sequences that have specific residues at specific positions.
- Args:
- conserved_positions: Dict mapping position (0-indexed) to allowed residues
e.g., {5: [‘M’, ‘L’], 10: [‘K’]}
reference_length: Expected sequence length (optional)
- Example:
-
default
>>> filter = ConservedResidueFilter({107: ['H'], 109: ['H'], 126: ['H']}) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.CustomFilter(func: Callable[[DataFrame], DataFrame], name: str | None = None)
Bases:
BaseFilterApply a custom filter function.
- Args:
func: Function that takes a DataFrame and returns a filtered DataFrame name: Optional name for the filter (for repr)
- Example:
-
default
>>> def my_filter(df): ... return df[df['sequence'].str.contains('M')] >>> filter = CustomFilter(my_filter, name='contains_M') >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.DataPipeline(sequences: list[str] | DataFrame | str | Path = None, diff_mode: bool = False, input_columns: list[str] | None = None, **kwargs)
Bases:
BasePipelinePipeline for processing existing sequences from files or lists.
Load sequences from CSV/FASTA/lists and run predictions/filtering.
- Args:
sequences: Input sequences (list of strings, DataFrame, or file path) datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output diff_mode: If True, merge new sequences with existing cached results.
Only computes predictions for uncached sequences. Use get_merged_results() or query_results() to efficiently access combined data without loading millions of rows into memory (SQL-based queries).
- Example:
-
default
>>> # Standard mode >>> pipeline = DataPipeline(sequences='sequences.csv') >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> pipeline.add_filter(ThresholdFilter('plddt', min_value=70)) >>> results = pipeline.run()default>>> # Diff mode - add new sequences to existing pipeline (SQL-based, efficient) >>> pipeline = DataPipeline(sequences='new_sequences.csv', diff_mode=True) >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> results = pipeline.run() >>> # Efficiently query specific data (doesn't load all millions of rows!) >>> high_quality = pipeline.query_results("s.length > 100 AND p.value > 70") >>> # Or get merged results with filters >>> merged = pipeline.get_merged_results(prediction_types=['plddt', 'tm'])
- add_clustering(method: str = 'kmeans', n_clusters: int | None = None, similarity_metric: str = 'hamming', embedding_model: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a sequence clustering stage.
Clusters sequences by similarity and adds cluster_id column to DataFrame.
- Args:
method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (required for kmeans/hierarchical) similarity_metric: ‘hamming’ or ‘embedding’ embedding_model: Model name if using embedding similarity stage_name: Optional custom stage name depends_on: Optional list of stage names this stage depends on **kwargs: Additional arguments for clustering (eps, min_samples, etc.)
- Example:
-
default
>>> # Cluster by sequence similarity >>> pipeline.add_clustering(method='kmeans', n_clusters=10) >>> >>> # Cluster by embeddings >>> pipeline.add_prediction('esm2-650m', action='encode', stage_name='embed') >>> pipeline.add_clustering( ... method='kmeans', ... n_clusters=5, ... similarity_metric='embedding', ... embedding_model='esm2-650m', ... depends_on=['embed'] ... )
- add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities=None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)
Add a co-folding prediction stage (Boltz2, Chai-1).
Each pipeline sequence becomes the primary entity in a multi-molecule folding request.
static_entitiesinjects ligands, cofactors, DNA/RNA strands, or additional protein chains that are constant across all designs.The caller is responsible for providing the correct molecule field names via
static_entitiesand the rightparamsfor the model.- Args:
model_name: BioLM model slug (
'boltz2','chai1'). action: API action (default'predict'). stage_name: Optional stage name (defaults tomodel_name). prediction_type: Column name for the confidence score. sequence_chain_id: Chain ID assigned to the primary sequence(e.g.
'A'for Boltz, moleculenamefor Chai-1).- sequence_entity_type: Entity type for the primary sequence
(
'protein','dna','rna').- static_entities: List of
FoldingEntityobjects to include in every request alongside the primary sequence.
depends_on: Upstream stage names. params: Model-specific params (e.g.
{'recycling_steps': 3}). batch_size: Sequences per API call (default 1).
Example:
defaultfrom biolm.pipeline import FoldingEntity pipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), ], params={'recycling_steps': 3, 'sampling_steps': 20}, depends_on=['filter_top50'], )
- add_filter(filter_func: BaseFilter | Callable[[...], Any], stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a filter stage.
- Args:
filter_func: Filter function or BaseFilter instance stage_name: Custom stage name depends_on: List of stage names this depends on. When
None(default), auto-depends on the last added stage. Pass
depends_on=[]for earliest-level execution.
- add_prediction(model_name: str, action: str = 'predict', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage.
- Args:
model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) extractions: API response key(s) to extract. Required for
predict/score actions. Can be a string for a single key or a list of strings / ExtractionSpec objects.
- columns: Output column name(s). A string renames a single
extraction; a dict maps response keys to column names (unmapped keys keep their name).
params: Optional API parameters stage_name: Custom stage name (defaults to
predict_{first_column}) depends_on: List of stage names this depends on. WhenNone(default), the stage auto-depends on the last added stage, creating a sequential chain. Pass
depends_on=[]to run at the earliest possible level (parallel with other level-0 stages).
Example:
defaultpipeline.add_prediction( "temberture-regression", extractions="prediction", columns="tm", )
- add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)
Add multiple prediction stages at the same level (run in parallel).
- Args:
models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages
- Returns:
self for chaining
- Example:
-
default
>>> pipeline.add_predictions([ ... {'model_name': 'temberture-regression', 'extractions': 'prediction', 'columns': 'tm'}, ... {'model_name': 'biolmsol', 'extractions': 'solubility_score', 'columns': 'solubility'}, ... ])
- add_structure_prediction(model_name: str, structure_key: str = 'pdb', extractions: str | list[Union[str, biolm.pipeline.data.ExtractionSpec]] | None = None, columns: str | dict[str, str] | None = None, plddt_key: str | None = None, structure_format: str | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage that stores the structure from the response.
Convenience wrapper around
add_prediction()with aStructureSpec.- Args:
model_name: BioLM model name (e.g. ‘esmfold’, ‘alphafold2’). structure_key: Response key containing the structure string. extractions: Optional scalar extractions (e.g. ‘mean_plddt’). columns: Output column name(s) for scalar extractions. plddt_key: Optional response key for confidence score. structure_format: ‘pdb’ or ‘cif’ (auto-detected from key if None). stage_name: Custom stage name. depends_on: Stage dependencies.
Example:
defaultpipeline.add_structure_prediction( "esmfold", extractions="mean_plddt", columns="plddt", plddt_key="mean_plddt", )
- explore() → dict[str, Any]
Return summary stats for the pipeline’s datastore (all via SQL).
- Returns:
Dict with keys: sequences, embeddings, generated, completed_stages, predictions (dict of prediction_type → count).
- get_merged_results(prediction_types: list[str] | None = None, sequence_filter: str | None = None) → DataFrame
Get results merged with existing cached data (for diff mode).
This method is SQL-based and efficient - it doesn’t load millions of rows. Instead, it queries only the data you need using DuckDB’s columnar engine.
NOTE (Bug #5):
sequence_filterandsql_wherein query_results() are interpolated directly into SQL. These parameters are intended for internal/ trusted caller use only — never pass untrusted user input to them.- Args:
prediction_types: List of prediction types to include (None = all) sequence_filter: SQL WHERE clause to filter sequences (e.g., “length > 50”).
TRUSTED CALLERS ONLY — not safe for untrusted user input.
- Returns:
DataFrame with requested sequences and predictions
- Example:
-
default
>>> # Get all results (efficient - DuckDB only loads what's needed) >>> df = pipeline.get_merged_results()default>>> # Get only specific predictions (columnar - even faster!) >>> df = pipeline.get_merged_results(prediction_types=['tm', 'plddt'])default>>> # Get sequences matching criteria (predicate pushdown!) >>> df = pipeline.get_merged_results(sequence_filter="length > 100")
- plot(kind: str = 'funnel', **kwargs)
Convenience wrapper around PipelinePlotter.
- Args:
- kind: One of ‘funnel’, ‘predictions’, ‘distributions’, ‘scatter’,
‘correlation’, ‘diversity’, ‘temperature’.
- **kwargs: Forwarded to the underlying plotter method.
scatter requires x_col and y_col. diversity accepts reference_sequence. temperature requires metric_col.
- query(sql: str, params=None) → DataFrame
Execute arbitrary SQL against the pipeline’s DuckDB datastore.
- Args:
sql: DuckDB SQL query string. params: Optional list of query parameters.
- Returns:
DataFrame with results.
- Example:
-
default
>>> pipeline.query("SELECT * FROM sequences WHERE length > 100")
- query_results(sql_where: str, columns: list[str] | None = None) → DataFrame
Query results using SQL WHERE clause (for diff mode with large datasets).
This leverages DuckDB’s vectorized engine for maximum performance.
NOTE (Bug #5):
sql_whereis interpolated directly into SQL. TRUSTED CALLERS ONLY — never pass untrusted user input to this parameter.- Args:
- sql_where: SQL WHERE clause using table aliases:
s.* for sequences table (e.g., “s.length > 100”)
Column names directly (no p. prefix needed)
TRUSTED CALLERS ONLY — not safe for untrusted user input.
columns: Columns to include (None = all available)
- Returns:
DataFrame with matching sequences (only loads what matches!)
- Example:
-
default
>>> # Find long sequences (columnar scan - fast!) >>> df = pipeline.query_results("s.length > 200")default>>> # Complex filter with predictions >>> df = pipeline.query_results( ... "s.length > 100", ... columns=['tm', 'plddt'] ... )
- stats(stage_name: str | None = None) → DataFrame
Return per-stage counts from stage_completions.
- Args:
stage_name: If provided, filter to that stage only.
- Returns:
DataFrame with columns: stage_name, status, input_count, output_count, completed_at.
- biolm.pipeline.DataStore
alias of
DuckDBDataStore
- class biolm.pipeline.DirectGenerationConfig(model_name: str, structure_path: str | None = None, structure_column: str | None = None, sequence: str | None = None, item_field: str = 'pdb', params: dict[str, typing.Any] = <factory>, num_sequences: int = 100, temperature: float = 1.0, structure_from_stage: str | None = None, structure_from_model: str | None = None, n_runs: int = 1, label: str | None = None)
Bases:
GenerativeProtocolConfigConfiguration for structure- or sequence-conditioned generation.
Use with models such as ProteinMPNN, AntiFold, HyperMPNN, LigandMPNN, DSM.
The caller is responsible for providing the correct
item_fieldandparamsfor the target model — these vary per model and are documented in the BioLM API schema (/schema/). Common values:/generate/ - Args:
- model_name: BioLM model slug (e.g.
'protein-mpnn','antifold', 'dsm-150m-base').
structure_path: Path to a PDB or CIF file. structure_column: DataFrame column holding PDB strings (for chained
pipelines where structure was predicted upstream).
sequence: Parent sequence string for sequence-conditioned models (DSM). item_field: The item dict key expected by the model API —
'pdb'forstructure-conditioned models,
'sequence'for DSM. Defaults to'pdb'.- params: Model-specific params dict (required for non-trivial calls).
Keys must exactly match the model’s API param names. When empty the stage sends
{'num_sequences': num_sequences, 'temperature': temperature}as a simple fallback — useful only for models that accept these exact param names.
num_sequences: Fallback when
paramsis empty (default 100). temperature: Fallback whenparamsis empty (default 1.0).- model_name: BioLM model slug (e.g.
Example:
defaultfrom biolm.pipeline.generative import DirectGenerationConfig, GenerativePipeline # Structure-conditioned design with ProteinMPNN cfg_mpnn = DirectGenerationConfig( model_name="protein-mpnn", structure_path="/path/to/protein.pdb", item_field="pdb", params={"num_sequences": 50, "temperature": 0.1}, ) # Sequence-conditioned design with DSM cfg_dsm = DirectGenerationConfig( model_name="dsm-150m-base", sequence="MKTAYIAKQRQ", item_field="sequence", params={ "num_sequences": 100, "temperature": 1.0, "remasking": "low_confidence", "step_divisor": 8, }, ) pipeline = GenerativePipeline(configs=[cfg_mpnn]) results = pipeline.run()- item_field: str = 'pdb'
- label: str | None = None
- model_name: str
- n_runs: int = 1
- num_sequences: int = 100
- params: dict[str, Any]
- sequence: str | None = None
- structure_column: str | None = None
- structure_from_model: str | None = None
- structure_from_stage: str | None = None
- structure_path: str | None = None
- temperature: float = 1.0
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- class biolm.pipeline.DiversityAnalyzer
Bases:
objectAnalyze sequence diversity and coverage.
Provides metrics for understanding sequence space exploration.
- Performance Notes:
Shannon entropy: O(n*L) where L is sequence length
Pairwise distances: O(n²*L) - use max_sample for large n
All metrics scale linearly except pairwise distances
- Example:
-
default
>>> analyzer = DiversityAnalyzer() >>> metrics = analyzer.compute_all_metrics(sequences, max_sample=10000) >>> print(f"Shannon entropy: {metrics['shannon_entropy']:.2f}")
- classmethod compute_all_metrics(sequences: list[str], max_sample: int | None = 10000) → dict[str, Union[float, dict]]
Compute all diversity metrics at once.
- Args:
sequences: List of protein sequences max_sample: Unused, reserved for future embedding-based pairwise stats
- Returns:
Dictionary with all diversity metrics
- static motif_diversity(sequences: list[str], k: int = 3) → dict[str, Union[int, float]]
Analyze k-mer (motif) diversity.
- Args:
sequences: List of protein sequences k: Length of k-mers to analyze
- Returns:
Dictionary with k-mer statistics
- static shannon_entropy(sequences: list[str], normalize: bool = True) → float
Calculate Shannon entropy of amino acid distribution.
Measures positional diversity across all sequences. Optimized for large datasets using vectorized operations.
- Args:
sequences: List of protein sequences normalize: Normalize by log(20) for 0-1 range
- Returns:
Shannon entropy (higher = more diverse)
- class biolm.pipeline.DiversitySamplingFilter(n_samples: int, method: str = 'random', score_column: str | None = None, random_seed: int | None = 42, resample: bool = True)
Bases:
BaseFilterSample diverse sequences using clustering or random sampling.
This filter REQUIRES complete data to assess diversity.
- Args:
n_samples: Number of sequences to sample method: Sampling method (‘random’, ‘spread’, ‘top’) score_column: Column to use for ‘top’ method random_seed: Random seed for reproducibility resample: If False, only sample if not already sampled (default: True)
- Example:
-
default
>>> filter = DiversitySamplingFilter(n_samples=1000, method='random') >>> df_sampled = filter(df)
- requires_complete_data: bool = True
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.DuckDBDataStore(db_path: str | Path | None = None, data_dir: str | Path | None = None)
Bases:
objectDuckDB + Parquet based datastore for efficient sequence management.
Optimized for: - Millions of sequences - Complex queries (joins, filters, aggregations) - Out-of-core operations (bigger than RAM) - Diff mode with efficient deduplication
- Args:
db_path: Path to DuckDB database file data_dir: Directory for Parquet files and large data
- Example:
-
default
>>> ds = DuckDBDataStore("pipeline.db", "data/") >>> seq_id = ds.add_sequence("MKLLIV") >>> ds.add_prediction(seq_id, "tm", "temberture-regression", 65.5) >>> >>> # Efficient query - no memory explosion >>> high_tm = ds.query("SELECT * FROM predictions WHERE value > 60")
- add_embedding(sequence_id: int, model_name: str, embedding: ndarray, layer: int | None = None)
Add embedding stored inline in DuckDB as FLOAT[] (no per-file Parquet overhead).
- Args:
sequence_id: Sequence ID model_name: Model name embedding: Numpy array layer: Optional layer number
- add_embeddings_batch(data: list[dict])
Batch-insert embeddings in a single DuckDB statement.
Each dict must have: sequence_id (int), model_name (str), embedding (np.ndarray). Optional: layer (int or None).
Significantly faster than N individual add_embedding() calls — one INSERT…SELECT vs. N individual row inserts.
- add_generation_metadata(sequence_id: int, model_name: str, run_id: str = '', temperature: float | None = None, top_k: int | None = None, top_p: float | None = None, num_return_sequences: int | None = None, do_sample: bool | None = None, repetition_penalty: float | None = None, max_length: int | None = None, sampling_params: dict | None = None) → int
Store generation parameters for a sequence.
- Returns:
metadata_id of the inserted row.
- add_generation_metadata_batch(rows: list[dict]) → None
Batch-insert generation metadata — one DuckDB round-trip.
Each dict in rows must have at minimum
sequence_idandmodel_name. Optional fields:temperature,top_k,top_p,num_return_sequences,do_sample,repetition_penalty,max_length.
- add_prediction(sequence_id: int, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None)
Add single prediction (convenience wrapper).
- add_prediction_by_sequence(sequence: str, prediction_type: str, model_name: str, value: float | None, metadata: dict | None = None) → int
Add a prediction, creating the sequence if it doesn’t exist.
- Returns:
prediction_id of the inserted row.
- add_predictions_batch(data: list[dict[str, Any]])
Batch add predictions efficiently.
- Args:
- data: List of dicts with keys: sequence_id, prediction_type,
model_name, value, metadata (optional)
- Example:
-
default
>>> ds.add_predictions_batch([ ... {'sequence_id': 1, 'prediction_type': 'tm', ... 'model_name': 'temberture-regression', 'value': 65.5}, ... {'sequence_id': 2, 'prediction_type': 'tm', ... 'model_name': 'temberture-regression', 'value': 70.2}, ... ])
- add_sequence(sequence: str) → int
Add single sequence (convenience wrapper).
- add_sequences_batch(sequences: list[str] | None = None, deduplicate: bool = True, input_df: DataFrame | None = None, input_columns: list[str] | None = None) → list[int]
Add multiple sequences efficiently using anti-join deduplication.
This is the RECOMMENDED way to add sequences - vectorized and fast!
Two calling conventions:
Legacy (sequence-only):
add_sequences_batch(["MKLLIV", ...])Multi-column (arbitrary input columns):
add_sequences_batch(input_df=df, input_columns=["heavy_chain", "light_chain"])In this mode, the hash is computed across all input columns, and the column values are stored directly on thesequencestable. Asequencecolumn is still written (concatenation of all input columns joined with:) so downstream code has a fallback.
- Args:
sequences: List of sequence strings (legacy path). deduplicate: Use anti-join to skip existing sequences. input_df: DataFrame with input columns (multi-column path). input_columns: Column names in input_df to use as primary data.
- Returns:
List of sequence_ids (new and existing), preserving input order.
- add_structure(sequence_id: int, model_name: str, structure_str: str | None = None, format: str = 'pdb', plddt_mean: float | None = None, plddt: float | None = None) → int
Store a structure gzip-compressed as BLOB (~8-12x smaller than plain TEXT).
- Args:
sequence_id: Sequence ID. model_name: Model that produced the structure (e.g. ‘esmfold’). structure_str: Full structure file content as a string. format: ‘pdb’ or ‘cif’ (default ‘pdb’). plddt_mean: Mean pLDDT score (optional). plddt: Alias for plddt_mean.
- Returns:
structure_id of the inserted row.
- close()
Close the DuckDB connection and release the file lock.
Safe to call multiple times — subsequent calls are no-ops. Called automatically by
__exit__and__del__.
- count_matching_sequences(sequences: list[str]) → int
Count how many of the given sequences already exist in the datastore.
Uses a single vectorized hash join instead of N individual lookups. Safe to call from any context — registers DataFrame explicitly.
- create_pipeline_run(run_id: str, pipeline_type: str, config: dict, status: str = 'running')
Create or update a pipeline run record (safe for resume runs).
- ensure_input_columns(columns: list[str])
Ensure the
sequencestable has all the given columns.Uses
ALTER TABLE ADD COLUMNfor any that don’t already exist. This is idempotent — safe to call on every pipeline run.- Raises:
- duckdb.CatalogException: If column creation fails for an unexpected
reason (e.g. type conflict). The “column already exists” case is silently ignored as it is the normal idempotent path.
- execute_filter_sql(sequence_ids: list[int], sql_query: str, ws_table_name: str = '_filter_ws') → list[int]
Execute a filter SQL query and return surviving sequence IDs.
The sql_query must be a complete
SELECTstatement that returnssequence_idvalues and JOINs against ws_table_name (which contains the input sequence_ids) to scope to the working set.Pass a unique ws_table_name (use
make_filter_ws_name()) when running multiple SQL-native filters concurrently — the default_filter_wsname races otherwise.- Args:
sequence_ids: Input sequence IDs. sql_query: Complete SQL SELECT returning
sequence_idvalues. ws_table_name: Name to register the working-set IDs under. Mustmatch the
ws_table=argument passed to the filter’sto_sql().- Returns:
List of sequence_ids that survive the filter.
- Raises:
ValueError: If sql_query is not a single SELECT statement.
- export_to_csv(path: str | Path, **kwargs) → None
Export all data to CSV (convenience wrapper around export_to_dataframe).
- export_to_dataframe(include_sequences: bool = True, include_predictions: bool = True, include_generation_metadata: bool = False, prediction_types: list[str] | None = None, run_id: str | None = None) → DataFrame
Export data to a flat DataFrame using a single DuckDB SQL query.
Uses conditional aggregation (CASE WHEN pivot) — no per-type queries, no pandas merges, no full table loads.
- Args:
include_sequences: Always True; includes sequence_id, sequence, length. include_predictions: Pivot prediction_type values into columns. include_generation_metadata: Join generation_metadata columns. prediction_types: Limit to specific prediction types (None = all).
- Returns:
Wide-format DataFrame: one row per sequence, one column per prediction type.
- export_to_parquet(table_name: str, output_path: str | Path)
Export table to Parquet file (for sharing/archiving).
- Args:
table_name: Table to export (sequences, predictions, etc.) output_path: Path to output Parquet file
- Example:
-
default
>>> ds.export_to_parquet('sequences', 'sequences_backup.parquet')
- get_all_sequences() → DataFrame
Return all sequences as a DataFrame with sequence_id, sequence, length columns.
- get_column_registry_entry(column_name: str) → dict | None
Return the registry entry for a column, or None if not registered.
- get_context(run_id: str, key: str) → Any | None
Retrieve a value from the pipeline context table.
- get_embedding(embedding_id: int) → tuple | None
Return (metadata_dict, embedding_array) for a given embedding_id, or None.
- get_embeddings_bulk(sequence_ids: list[int], model_name: str | None = None) → dict[int, numpy.ndarray]
Fetch embeddings for multiple sequences in a single JOIN query.
Replaces N individual get_embeddings_by_sequence() calls (O(n) queries → O(1)).
- Args:
sequence_ids: List of sequence IDs to fetch. model_name: Optional model filter.
- Returns:
Dict mapping sequence_id → numpy embedding array for sequences that have an embedding.
- get_embeddings_by_sequence(sequence: str, model_name: str | None = None, load_data: bool = False) → list[dict]
Get embeddings for a sequence.
- get_embeddings_concat(sequence_ids: list[int], model_names: list[str]) → dict[int, numpy.ndarray]
Fetch and concatenate embeddings from multiple models per sequence.
For each sequence_id, retrieves the embedding from each model in
model_namesorder and horizontally concatenates them into a single vector. Sequences missing an embedding from any requested model are omitted from the result.- Args:
sequence_ids: Sequence IDs to fetch. model_names: Ordered list of model names whose embeddings will be
concatenated (e.g.
["esm2-8m", "esmc-300m"]).- Returns:
Dict mapping
sequence_id→ concatenated numpy array.
- get_existing_input_columns() → list[str]
Return extra columns on the sequences table (beyond the base schema).
Used for input-schema validation when connecting to an existing DB. Returns an empty list if only the base columns are present.
- get_filter_results(run_id: str, stage_name: str) → list[int]
Return sequence_ids that passed a given filter stage in a run.
- Args:
run_id: Pipeline run ID. stage_name: Filter stage name.
- Returns:
List of sequence_ids that passed the filter, or empty list if no data.
- get_generation_metadata(sequence_id: int) → list[dict]
Return generation metadata records for a sequence_id.
- get_latest_definition_id() → str | None
Return the definition_id of the most recently created pipeline definition.
- get_latest_run_id(definition_id: str | None = None) → str | None
Return the run_id of the most recent pipeline run.
from_db()must reuse the existing run_id so that resume can find already-completed stages. A new run_id means no stages are marked complete, causing everything to re-run.- Args:
- definition_id: If provided, restrict to runs that used this definition.
If None, return the most recent run across all definitions.
- Returns:
The run_id string, or None if no runs exist.
- get_pipeline_metadata(key: str) → Any | None
Retrieve a value from pipeline_metadata by key.
- get_pipeline_run(run_id: str) → dict | None
Return pipeline run record as a dict, or None if not found.
- get_predictions(sequence_id: int, prediction_type: str | None = None, model_name: str | None = None) → DataFrame
Return predictions for a sequence_id as a DataFrame.
- get_predictions_bulk(sequence_ids: list[int], prediction_type: str, model_name: str) → DataFrame
Fetch predictions for multiple sequences in a single JOIN query.
Replaces N individual get_predictions_by_sequence() calls.
- Returns:
DataFrame with columns: sequence_id, value, metadata
- get_predictions_by_sequence(sequence: str, prediction_type: str | None = None, model_name: str | None = None) → DataFrame
Get predictions for a sequence.
- get_sequence(sequence_id: int) → str | None
Return the sequence string for a given sequence_id, or None if not found.
- get_sequence_attributes_for_ids(sequence_ids: list[int], attr_names: list[str]) → dict[int, dict[str, str]]
Retrieve per-sequence attributes, returning {seq_id: {attr: value}}.
- Args:
sequence_ids: Sequence IDs to look up. attr_names: Attribute names to retrieve.
- Returns:
Nested dict:
{sequence_id: {attr_name: attr_value}}.
- get_sequence_id(sequence: str) → int | None
Get sequence_id for a sequence.
- get_sequence_ids_with_prediction(sequence_ids: list[int], prediction_type: str, model_name: str) → list[int]
Return sequence_ids that DO have a given prediction (inverse of uncached check).
- Args:
sequence_ids: Candidate sequence IDs. prediction_type: Prediction type key. model_name: Model name.
- Returns:
List of sequence_ids that have a cached prediction.
- get_sequences_for_ids(sequence_ids: list[int]) → list[tuple[int, str]]
Fetch (sequence_id, sequence) pairs for the given IDs.
Lightweight fetch for building API request items without materializing a full DataFrame.
- Args:
sequence_ids: List of sequence IDs to look up.
- Returns:
List of (sequence_id, sequence_string) tuples.
- get_sequences_for_ids_with_columns(sequence_ids: list[int], columns: list[str]) → dict[int, dict[str, str]]
Fetch column values from the sequences table for given IDs.
This reads columns stored directly on the
sequencestable (viaensure_input_columns), NOT fromsequence_attributes.- Args:
sequence_ids: Sequence IDs to look up. columns: Column names to fetch (must exist on the sequences table).
- Returns:
{sequence_id: {col: value, ...}}
- get_stats() → dict[str, int]
Return row counts for the main tables.
- get_structure(sequence_id: int, model_name: str | None = None) → dict | None
Fetch the most recent structure for a sequence, decompressing on read.
Returns a dict with ‘structure_str’ key (always decompressed string) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.
- Args:
sequence_id: Sequence ID. model_name: Optional model filter.
- Returns:
Dict with structure record, or None if not found.
- get_structure_by_id(structure_id: int) → dict | None
Return a structure record by its structure_id (primary key).
- get_structures_bulk(sequence_ids: list[int]) → DataFrame
Fetch structures for multiple sequences, decompressing structure content.
Returns a DataFrame with a ‘structure_str’ column (always plain text) regardless of whether data was stored compressed (structure_data BLOB) or as legacy plain TEXT.
- Args:
sequence_ids: List of sequence IDs to look up.
- Returns:
DataFrame with one row per structure record.
- get_structures_by_sequence(sequence: str, model_name: str | None = None) → list[dict]
Return structure records for a sequence string (decompressed).
- get_structures_for_ids(sequence_ids: list[int], model_name: str) → dict[int, dict]
Fetch the most recent structure for each sequence_id in one query.
Returns {sequence_id: record} where record has a ‘structure_str’ key. Replaces N individual get_structure() calls for batch structure injection.
- get_uncached_sequence_ids(sequence_ids: list[int], prediction_type: str, model_name: str) → list[int]
Return sequence_ids that do NOT yet have a given prediction (vectorized anti-join).
Replaces N individual has_prediction() calls with a single SQL query.
- Args:
sequence_ids: Candidate sequence IDs to check. prediction_type: Prediction type key. model_name: Model name.
- Returns:
List of sequence_ids with no cached prediction.
- has_prediction(sequence: str, prediction_type: str, model_name: str) → bool
Check if prediction exists for sequence.
- is_stage_complete(stage_id: str) → bool
Check if stage is complete.
- load_blob(blob_id: str) → str | None
Retrieve a stored blob by its blob_id. Returns None if not found.
- load_pipeline_definition(definition_id: str | None = None) → dict | None
Load a pipeline definition by ID, or the latest one if ID is None.
- make_filter_ws_name() → str
Mint a unique
_filter_ws_table name for one filter execution.Required when SQL-native filters can run in parallel at the same DAG level — sharing the canonical
_filter_wsregistration would let two stages clobber each other’s working set.
- mark_stage_complete(run_id: str, stage_name: str, stage_id: str, input_count: int, output_count: int, status: str = 'completed')
Mark stage as complete (or failed/skipped).
uses INSERT … ON CONFLICT DO NOTHING so that a stage that was already marked ‘completed’ does not get its completed_at timestamp overwritten on resume. Only truly new rows are inserted.
- materialize_working_set(ws: WorkingSet, include_predictions: bool = True, prediction_types: list[str] | None = None) → pd.DataFrame
Materialize a WorkingSet into a DataFrame via a single DuckDB pivot query.
- Args:
ws: WorkingSet containing the sequence IDs to materialize. include_predictions: If True, pivot prediction values into columns. prediction_types: Limit to specific types (None = all available).
- Returns:
Wide-format DataFrame: one row per sequence, one column per prediction type. Always includes the following columns regardless of pipeline configuration:
sequence_id,sequence,length,hash— core sequence data.source_label— label set on the generation config (e.g.DirectGenerationConfig.label,SaturationMutagenesisConfig.label).NULL/Nonewhen no label was supplied. This column is always present even when no labels have been set, so downstream code should not branch on"source_label" in df.columns— it is always there.
- query(sql: str, params: list | None = None) → DataFrame
Execute arbitrary SQL query and return DataFrame.
This is the POWER feature - query directly without loading everything!
- Args:
sql: DuckDB SQL query params: Optional query parameters
- Returns:
DataFrame with results (only loads what matches query!)
- Example:
-
default
>>> # Find high-quality long sequences >>> df = ds.query(''' ... SELECT s.sequence, p.value as plddt ... FROM sequences s ... JOIN predictions p ON s.sequence_id = p.sequence_id ... WHERE s.length > 200 ... AND p.prediction_type = 'plddt' ... AND p.value > 80 ... ''')
- register_column(column_name: str, model_name: str, action: str, definition_id: str, stage_name: str)
Register an output column in the prediction_column_registry. Idempotent.
- save_filter_results(run_id: str, stage_name: str, passed_sequence_ids: list[int])
Record which sequence_ids passed a filter stage (for resume support).
- Args:
run_id: Pipeline run ID. stage_name: Filter stage name. passed_sequence_ids: IDs of sequences that passed the filter.
- save_pipeline_definition(definition_id: str, pipeline_type: str, input_schema_json: str | None, stages_json: str)
Persist a pipeline definition. Updates stages/schema if definition_id already exists.
- set_context(run_id: str, key: str, value: Any)
Store a key-value pair in the pipeline context table.
- set_pipeline_metadata(key: str, value: Any)
Upsert a key/value pair in the pipeline_metadata table.
- store_blob(content: str) → str
Store a large string value, returning its blob_id (SHA-256[:32]).
Content-addressed: identical content always maps to the same blob_id. Safe to call multiple times with the same content (INSERT OR IGNORE).
- store_sequence_attributes(seq_ids: list[int], attr_name: str, attr_values: list[str])
Persist a per-sequence attribute column (e.g. heavy_chain, light_chain).
- Args:
seq_ids: Sequence IDs. attr_name: Attribute name (column name from the input DataFrame). attr_values: Corresponding values (one per sequence_id).
- update_pipeline_run_status(run_id: str, status: str)
Update pipeline run status.
- biolm.pipeline.Embed(model_name: str, sequences: list[str] | DataFrame | str | Path, layer: int | None = None, key: str | None = None, **kwargs) → DataFrame
Convenience function for generating embeddings.
- Args:
- model_name: BioLM model name (e.g.,
'esm2-8m','esm2-650m', 'ablang2'). Seebiolm.list_models()for the full slugged list — the family name alone ('esm2') is not a valid endpoint.
sequences: Input sequences layer: Optional layer number key: Response dict key containing embeddings. Auto-detected if None:
ESM2 models use “embeddings”, AbLang2 uses “seqcoding”, others default to “embedding”.
**kwargs: Additional arguments
- model_name: BioLM model name (e.g.,
- Returns:
DataFrame with ‘sequence’, ‘sequence_id’, and ‘embedding’ columns
- Example:
-
default
>>> df = Embed('esm2-8m', sequences=['MKTAYIAKQRQ', 'MKLAVID'])
- class biolm.pipeline.EmbeddingSpec(key: str, layer: int | None = None, reduction: str | None = None)
Bases:
objectDeclarative specification for extracting embeddings from API responses.
Covers common response formats without writing a custom function.
- Args:
- key: Response dict key containing the embedding data (e.g.
"embedding","seqcoding","embeddings").- layer: Which layer to extract when the response contains multiple
layers (list of
{layer: int, embedding: [...]}dicts).Nonestores all layers; anintstores only that layer.- reduction: Reduce per-token 2-D embeddings to a single vector:
"mean","first","last","sum".Nonestores the full array as-is.
Examples:
default# ablang2 returns {"seqcoding": [float, ...]} EmbeddingSpec(key="seqcoding") # esm2-8m returns {"embeddings": [{embedding: [...], layer: 33}]} # Store only layer 33: EmbeddingSpec(key="embeddings", layer=33) # Per-residue → mean-pool: EmbeddingSpec(key="embedding", reduction="mean")- key: str
- layer: int | None = None
- reduction: str | None = None
- class biolm.pipeline.ExtractionSpec(response_key: str, reduction: str | None = None)
Bases:
objectSpecification for extracting a value from an API response (with reduction).
Use when you need to apply a reduction (mean, max, min, sum) to an array-valued response key. For simple scalar extractions, pass a plain string to
extractionsinstead.- Args:
response_key: Key in API response dict, e.g. “plddt” reduction: Optional reduction for array values: “mean”, “max”, “min”, “sum”
- reduction: str | None = None
- response_key: str
- class biolm.pipeline.FoldingEntity(id: str, entity_type: str, sequence: str | None = None, smiles: str | None = None, ccd: str | None = None)
Bases:
objectA molecular entity for co-folding models (Boltz2, Chai-1).
Used with
DataPipeline.add_cofolding_prediction()andGenerativePipeline.add_cofolding_prediction()to inject static entities — ligands, cofactors, DNA/RNA strands, or additional protein chains — alongside the pipeline’s primary sequences.- Args:
id: Chain identifier (Boltz uses single letter(s); Chai-1 uses a name). entity_type: Molecule type —
'protein','dna','rna', or'ligand'.sequence: Amino-acid / nucleotide sequence for protein / DNA / RNA entities. smiles: SMILES string for small-molecule ligands. ccd: CCD code for ligands defined in the Chemical Component Dictionary
(e.g.
'ATP','HEM').
- ccd: str | None = None
- entity_type: str
- id: str
- sequence: str | None = None
- smiles: str | None = None
- class biolm.pipeline.GenerativePipeline(generation_configs: list[Union[biolm.pipeline.generative.GenerationConfig, biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, deduplicate: bool = True, configs: list[Union[biolm.pipeline.mlm_remasking.RemaskingConfig, biolm.pipeline.generative.DirectGenerationConfig, biolm.pipeline.generative.SaturationMutagenesisConfig, biolm.pipeline.generative.IterativeMaskingDMSConfig]] | None = None, filters=None, data_store=None, **kwargs)
Bases:
BasePipelinePipeline for generating sequences and running predictions.
Supports: - Multiple generative models in parallel - Temperature scanning - Masked language model remasking - Downstream predictions and filtering
- Args:
generation_configs: List of GenerationConfig objects deduplicate: Whether to deduplicate generated sequences datastore: DataStore instance or path run_id: Unique run ID output_dir: Output directory resume: Resume from previous run verbose: Enable verbose output
- Example:
-
default
>>> # Generate with MPNN at multiple temperatures >>> config1 = GenerationConfig( ... model_name='proteinmpnn', ... num_sequences=1000, ... temperature=[0.5, 1.0, 1.5], ... parent_sequence='MKTAYIAKQRQ' ... ) >>> >>> # Also generate with ESM using remasking >>> config2 = GenerationConfig( ... model_name='esm2', ... num_sequences=500, ... generation_method='remask', ... parent_sequence='MKTAYIAKQRQ', ... mask_fraction=0.15 ... ) >>> >>> pipeline = GenerativePipeline( ... generation_configs=[config1, config2] ... ) >>> pipeline.add_filter(ThresholdFilter('length', min_value=50)) >>> pipeline.add_prediction('esmfold', extractions='mean_plddt', columns='plddt') >>> results = pipeline.run()
- add_cofolding_prediction(model_name: str, action: str = 'predict', stage_name: str | None = None, prediction_type: str = 'structure', sequence_chain_id: str = 'A', sequence_entity_type: str = 'protein', static_entities: list[biolm.pipeline.generative.FoldingEntity] | None = None, depends_on: list[str] | None = None, params: dict | None = None, batch_size: int = 1)
Add a co-folding prediction stage (Boltz2, Chai-1).
Each sequence in the pipeline is treated as the primary protein chain.
static_entitieslets you inject additional molecules — ligands, cofactors, DNA/RNA strands, or extra protein chains — that are held constant across all designs.- Args:
model_name: BioLM model slug, e.g.
'boltz2'or'chai1'. action: API action (default'predict'). stage_name: Stage name (defaults tomodel_name). prediction_type: Column name for the confidence score (default'structure').- sequence_chain_id: Chain ID assigned to the pipeline’s primary
sequence in the multi-molecule request (e.g.
'A').- sequence_entity_type: Molecule type for the primary sequence:
'protein','dna', or'rna'(default'protein').- static_entities: List of
FoldingEntityobjects — ligands, cofactors, extra proteins/DNA/RNA — included in every request.
depends_on: Upstream stage names this stage waits for. params: Model-specific params dict (e.g.
{'recycling_steps': 3, 'sampling_steps': 20}for Boltz).- batch_size: Sequences per API request (default 1; co-folding models
are typically batch-size-1).
Example:
defaultpipeline.add_cofolding_prediction( model_name='boltz2', static_entities=[ FoldingEntity(id='L', entity_type='ligand', smiles='c1ccccc1'), FoldingEntity(id='B', entity_type='protein', sequence='MKTAYIAKQRQ'), ], depends_on=['filter_top50'], )
- add_filter(filter_func, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a filter stage (same as DataPipeline).
- add_generation_config(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig) → GenerativePipeline
Append a config to the existing generation slot.
Use this to add a second model or temperature variant alongside the current generation config rather than replacing it. If there is no generation slot yet, one is created.
- Args:
config: Config to add.
- Returns:
Self, for method chaining.
- add_prediction(model_name: str, action: str = 'predict', extractions=None, columns=None, params: dict | None = None, stage_name: str | None = None, depends_on: list[str] | None = None, **kwargs)
Add a prediction stage (same as DataPipeline).
- add_predictions(models: list[Union[str, dict]], action: str = 'predict', depends_on: list[str] | None = None, **kwargs)
Add multiple prediction stages at the same level (run in parallel).
- Args:
models: List of model names or dicts with model configs action: Default action if not specified in dict depends_on: List of stage names all these depend on **kwargs: Default kwargs for all stages
- Returns:
self for chaining
- Example:
-
default
>>> pipeline.add_predictions(['temberture-regression', 'proteinmpnn', 'esm2'])
- add_stage(stage: Stage) → None
Add a stage to the pipeline.
If stage is a
GenerationStageit replaces the current generation slot (there is always exactly one, at position 0) rather than appending. All other stage types are appended normally.
- replace_generation(config: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str | None = None, deduplicate: bool = True) → GenerativePipeline
Swap the generation slot with a single new config.
Equivalent to
set_generation(config, stage_name=stage_name). Kept for backwards compatibility and single-config convenience.
- async run_async(**kwargs) → dict[str, biolm.pipeline.base.StageResult]
Run the generative pipeline.
All GenerationStages (regardless of position) are extracted and run first as sources — their outputs are unioned into the initial WorkingSet so generated sequences trickle through every downstream prediction/filter stage (the “funnel”). Idempotent: self.stages is always restored after execution.
- set_generation(*configs: RemaskingConfig | DirectGenerationConfig | SaturationMutagenesisConfig | IterativeMaskingDMSConfig, stage_name: str = 'generation', deduplicate: bool = True) → GenerativePipeline
Set (or replace) the generation slot with one or more configs.
Multiple configs run in parallel — use this for multi-model generation or temperature scanning. Every call to
.run()re-runs generation from scratch; downstream stages use their prediction cache so only truly new sequences are computed.- Args:
- *configs: One or more
RemaskingConfigor DirectGenerationConfigobjects.
stage_name: Name for the generation stage (default
"generation"). deduplicate: Deduplicate across all configs (default True).- *configs: One or more
- Returns:
Self, for method chaining.
Example:
default# Single model pipeline.set_generation( DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=200) ).run() # Two models in parallel — sequences from both trickle through the funnel pipeline.set_generation( DirectGenerationConfig("dsm-150m-base", sequence=parent, num_sequences=100), RemaskingConfig("esm-150m", mask_fraction=0.15), ).run()
- use_sequences(sequences=None, column: str = 'sequence', stage_name: str = 'data_source', from_db: bool = False) → GenerativePipeline
Use existing sequences as the pipeline source instead of generating.
Replaces the generation slot with a
SequenceSourceConfig. Downstream prediction and filter stages run on the provided sequences, using the normal DuckDB prediction cache for anything already computed.- Args:
sequences: One of:
list[str]— plain amino-acid stringspd.DataFrame— must contain column (default"sequence")str/Path— CSV or FASTA fileNone— reload all sequences already in the DuckDB
column: Column name when sequences is a DataFrame or CSV. stage_name: Name for the source stage (default
"data_source"). from_db: Pull all sequences already present in this pipeline’s DuckDB.- Returns:
Self, for method chaining.
Example:
default# Inject a list pipeline.use_sequences(["MKTAY", "MKLLIV"]).run() # Use all sequences already in the DB (e.g. recover + rerun) pipeline.use_sequences(from_db=True).run() # Load from CSV and run through the existing filter/predict stages pipeline.use_sequences("candidates.csv").run()
- class biolm.pipeline.GenerativeProtocolConfig
Bases:
objectMarker base class for stages that produce new amino-acid sequences.
Subclasses drive generative or masked-language models to emit novel sequences — either via autoregressive sampling (ProteinMPNN, DSM, AntiFold) or by greedy-argmax masking over an MLM (ESM2, ESMC). Unlike
ScoringProtocolConfigsubclasses, these stages expand the candidate pool rather than filtering it; they are the source stage in a generative design pipeline.Use
isinstance(config, GenerativeProtocolConfig)()to branch on config type in pipeline dispatch functions.Example:
defaultfrom biolm.pipeline.generative import ( GenerativeProtocolConfig, DirectGenerationConfig, IterativeMaskingDMSConfig, ) def dispatch(config): if isinstance(config, GenerativeProtocolConfig): return run_generative_stage(config) # emits new sequences return run_scoring_stage(config) # returns ranked variants cfg_direct = DirectGenerationConfig( "dsm-150m-base", sequence="MKTAYIAKQRQ", num_sequences=50 ) cfg_dms = IterativeMaskingDMSConfig( parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m" ) assert isinstance(cfg_direct, GenerativeProtocolConfig) # True assert isinstance(cfg_dms, GenerativeProtocolConfig) # True
- class biolm.pipeline.HammingDistanceFilter(reference_sequence: str, max_distance: float | None = None, min_distance: float | None = None, normalize: bool = False)
Bases:
BaseFilterFilter by Hamming distance to a reference sequence.
- Args:
reference_sequence: Reference sequence max_distance: Maximum Hamming distance (inclusive) min_distance: Minimum Hamming distance (inclusive) normalize: If True, use normalized distance (0-1)
- Example:
-
default
>>> filter = HammingDistanceFilter('MKTAYIAKQ', max_distance=5) >>> df_filtered = filter(df)
- static hamming_distance(seq1: str, seq2: str, normalize: bool = False) → float
Calculate Hamming distance between two sequences.
For equal-length sequences this is the standard Hamming distance (count of positions where characters differ).
For sequences of different lengths (F09 — edge-case note): the distance is computed as the number of mismatches in the overlapping prefix PLUS the absolute difference in lengths (each extra character in the longer sequence counts as one mismatch). When
normalize=Truethe result is divided bymax(len(seq1), len(seq2)). This is a non-standard extension; callers comparing variable-length sequences should be aware that the normalized value is NOT equivalent to edit distance / Levenshtein normalized distance.
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- class biolm.pipeline.InputSchema(columns: list[str])
Bases:
objectDescribes the input columns for a pipeline.
When set, these columns are the primary data —
sequenceis not required. Columns are stored directly on thesequencestable viaALTER TABLE ADD COLUMNso thatmaterialize_working_set(), SQL filters, anditem_columnsall work via direct JOINs.Hashing uses all columns (sorted alphabetically) joined with
\x00separators so that identical rows produce the same hash regardless of column order.- Args:
columns: List of column names that comprise the primary input.
- columns: list[str]
- hash_row(row: dict[str, str]) → str
SHA-256 hash of the row values across all input columns (sorted).
- class biolm.pipeline.IterativeMaskingDMSConfig(parent_sequence: str, model_name: str, positions: list[int] | None = None, rounds: int = 2, mask_token: str = '<mask>', alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', exclude_synonymous: bool = True, batch_size: int = 32, label: str | None = None, action: str = 'predict')
Bases:
GenerativeProtocolConfigSource config that builds multi-point variants via sequential greedy masking.
Implements an iterative argmax masking procedure using a masked language model:
For each target position, mask it in the parent sequence and query the model for the highest-probability residue (greedy argmax — not sampled).
If
rounds > 1, apply the round-1 preferred substitution at each position and repeat for round 2: mask each other target position in the round-1 sequence and collect the argmax residue.Yield all resulting sequences as pipeline outputs.
This matches the ESM2 two-round DMS design pattern in the EGF generation notebook.
- Args:
parent_sequence: Starting sequence. model_name: MLM model slug (e.g.
'esm2-650m','esmc-300m'). positions: 0-indexed positions to probe. Defaults to all positions. rounds: Number of sequential masking rounds (default 2). Round N usesthe variant produced by round N-1 as its starting sequence.
mask_token: Token inserted at masked positions (default
'). alphabet: Vocabulary used to identify valid AA positions in logits.' Defaults to the 20 canonical amino acids.
- exclude_synonymous: Skip round-1 variants where the argmax matches WT
(default
True).
batch_size: Sequences per API request (default 32). label: Optional label stored as
source_labelin results. action: API action for the model (default'predict'; the model mustreturn logits). Only
'predict'is allowed because the greedy-argmax procedure requires per-token logit arrays.
Example:
defaultfrom biolm.pipeline.generative import IterativeMaskingDMSConfig, GenerativePipeline # Two-round greedy DMS at three positions using ESM2-650M config = IterativeMaskingDMSConfig( parent_sequence="MKTAYIAKQRQ", model_name="esm2-650m", positions=[2, 5, 8], # 0-indexed; None → all positions rounds=2, # round 1: per-position; round 2: 2-point combos exclude_synonymous=True, ) pipeline = GenerativePipeline(configs=[config]) results = pipeline.run() # results DataFrame has columns: # sequence, dms_round, dms_pos1, dms_aa1, dms_pos2, dms_aa2- action: str = 'predict'
- alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
- batch_size: int = 32
- exclude_synonymous: bool = True
- label: str | None = None
- mask_token: str = '<mask>'
- model_name: str
- parent_sequence: str
- positions: list[int] | None = None
- rounds: int = 2
- to_spec() → dict
- class biolm.pipeline.MLMRemasker(config: RemaskingConfig, api_client=None, model_name: str | None = None)
Bases:
objectMasked Language Model remasking utility.
Handles iterative masking and prediction for generating sequence variants using masked language models.
- Args:
config: RemaskingConfig instance api_client: BioLM API client (optional, for actual predictions) model_name: Model name (e.g., ‘esm2’, ‘esm1v’)
- Example:
-
default
>>> config = RemaskingConfig(mask_fraction=0.15, num_iterations=5) >>> remasker = MLMRemasker(config, model_name='esm2') >>> variants = remasker.generate_variants('MKTAYIAKQRQ', num_variants=10)
- create_masked_sequence(sequence: str, positions: list[int]) → str
Create masked sequence with mask token at specified positions.
- Args:
sequence: Original sequence positions: Positions to mask
- Returns:
Masked sequence
- async generate_variant(parent_sequence: str, iteration: int = 0) → tuple[str, dict[str, Any]]
Generate a single variant through remasking (async).
- Args:
parent_sequence: Starting sequence iteration: Iteration number (for seeding)
- Returns:
Tuple of (variant_sequence, metadata_dict)
- async generate_variants(parent_sequence: str, num_variants: int = 100, deduplicate: bool = True) → list[tuple[str, dict[str, Any]]]
Generate multiple variants through remasking (async, concurrent).
Uses asyncio.gather with a semaphore to generate variants concurrently rather than sequentially, significantly reducing wall-clock time when the API client supports concurrent calls.
- Args:
parent_sequence: Starting sequence num_variants: Number of variants to generate deduplicate: Remove duplicate sequences (parent excluded when True)
- Returns:
List of (variant_sequence, metadata) tuples
- async iterative_refinement(sequence: str, fitness_function: callable, num_iterations: int = 10, population_size: int = 20, keep_top_k: int = 5) → list[tuple[str, float, dict[str, Any]]]
Perform iterative refinement using remasking and a fitness function (async).
- Args:
sequence: Starting sequence fitness_function: Function that scores sequences (higher is better) num_iterations: Number of refinement iterations population_size: Number of variants per iteration keep_top_k: Number of top sequences to keep per iteration
- Returns:
List of (sequence, fitness, metadata) tuples for final population
- async predict_masked_positions(sequence: str, mask_positions: list[int]) → tuple[str, dict[int, float]]
Predict amino acids at masked positions.
Builds the masked sequence client-side (inserting
config.mask_tokenat each position), sends it to the model’s predict endpoint, and decodes the returned logits with temperature/top-k/top-p sampling.Falls back to reading a
"sequence"key from the response if the model returns a filled sequence directly instead of logits.- Args:
sequence: Original (unmasked) sequence. mask_positions: 0-indexed positions to replace.
- Returns:
Tuple of (predicted_sequence, confidences_dict)
- select_mask_positions(sequence: str, confidences: ndarray | None = None) → list[int]
Select positions to mask based on strategy.
- Args:
sequence: Input sequence confidences: Optional confidence scores per position (for low_confidence strategy)
- Returns:
List of positions to mask (0-indexed)
- class biolm.pipeline.MatrixExtractionSpec(prefix: str = 'ddg', values_key: str = 'ddG_matrix.values', row_labels_key: str = 'ddG_matrix.residue_axis', col_labels_key: str = 'ddG_matrix.amino_acid_axis', mutation_key: str | None = None, value_key: str | None = None)
Bases:
objectFlattens a per-mutation response into individual prediction rows.
Each mutation becomes a separate prediction row with prediction_type formatted as ‘{prefix}_{label}’ (e.g., ‘ddg_M1A’).
- Two modes:
Matrix mode (SPURS): 2D array + row/col labels.
List mode (ThermoMPNN): list of dicts with mutation name + value.
- Args:
prefix: Prediction type prefix (e.g. “ddg”). values_key: Dot-path to 2D array in response (matrix mode). row_labels_key: Dot-path to row labels (position labels). col_labels_key: Dot-path to column labels (amino acid labels). mutation_key: Dict key for mutation name (list mode). If set, uses list mode. value_key: Dict key for the numeric value (list mode).
- col_labels_key: str = 'ddG_matrix.amino_acid_axis'
- mutation_key: str | None = None
- prefix: str = 'ddg'
- row_labels_key: str = 'ddG_matrix.residue_axis'
- value_key: str | None = None
- values_key: str = 'ddG_matrix.values'
- exception biolm.pipeline.PipelineAPIAuthError(status_code: int, payload: Any, model_name: str)
Bases:
RuntimeErrorRaised when the BioLM API returns 401/402 (auth/billing) during a stage.
These errors are not retriable per-item — every batch will hit the same failure — so the pipeline fails fast instead of producing a silent empty result.
skip_on_error=Truedoes NOT swallow this; callers always see an unambiguous failure with the upstream error payload.
- class biolm.pipeline.PipelineContext(datastore: DuckDBDataStore, run_id: str)
Bases:
objectShared key-value store backed by DuckDB for inter-stage communication.
Stages can read/write arbitrary data through the context. Common use case: stage 1 predicts structures (stored in the
structurestable), stage 2 reads them for structure-conditioned generation.- Args:
datastore: The pipeline’s DuckDB datastore. run_id: Current pipeline run ID.
- get(key: str, default: Any = None) → Any
Retrieve a value from the pipeline context table.
- get_structure(sequence_id: int, model_name: str | None = None) → dict | None
Convenience: fetch a structure from the datastore’s structures table.
- get_structures_for_ws(ws: WorkingSet, model_name: str | None = None) → DataFrame
Fetch structures for all sequences in a WorkingSet.
- set(key: str, value: Any)
Store a value in the pipeline context table.
- class biolm.pipeline.PipelineMetadata(pipeline_id: str, cache_dir: Path, db_path: Path, run_id: str)
Bases:
objectMetadata for a pipeline run — lets users retrieve and reuse cached results.
- Attributes:
pipeline_id: Unique identifier for the pipeline’s cache directory. cache_dir: Path to the
.biolm/pipelines/cache directory. db_path: Path to the DuckDB database file inside the cache directory. run_id: The run ID for this execution (there can be multiple runssharing the same cache).
Example:
defaultpipeline = DataPipeline(sequences=[...]) pipeline.run() meta = pipeline.metadata print(meta.pipeline_id) # "20260302_143022_a1b2c3d4" print(meta.cache_dir) # ".biolm/pipelines/20260302_143022_a1b2c3d4" # Later — reuse the same cache: pipeline2 = DataPipeline( sequences=new_seqs, datastore=meta.db_path, # or str(meta.cache_dir) resume=True, )- cache_dir: Path
- db_path: Path
- pipeline_id: str
- run_id: str
- class biolm.pipeline.PipelinePlotter(pipeline, df: DataFrame | None = None)
Bases:
objectConvenience class for plotting pipeline results.
- Args:
pipeline: Pipeline instance df: Optional DataFrame (uses pipeline.get_final_data() if not provided)
- Example:
-
default
>>> plotter = PipelinePlotter(pipeline) >>> plotter.plot_funnel() >>> plotter.plot_distribution('tm')
- plot_correlation_matrix(**kwargs)
Plot correlation matrix.
- plot_distribution(column: str, **kwargs)
Plot distribution of a column.
- plot_distributions(columns: list[str] | None = None, **kwargs)
Alias for plot_predictions — plot distributions of prediction columns.
- plot_diversity(reference_sequence: str | None = None, **kwargs)
Plot sequence diversity.
- plot_funnel(**kwargs)
Plot pipeline funnel.
- plot_predictions(columns: list[str] | None = None, **kwargs)
Plot distributions of all numeric prediction columns in a multi-panel layout.
- Args:
- columns: Specific columns to plot. If None, auto-detects all numeric
columns except sequence_id, length, and hash.
**kwargs: Forwarded to matplotlib (e.g. figsize, bins).
- plot_scatter(x_col: str, y_col: str, **kwargs)
Plot scatter plot.
- plot_temperature_scan(metric_col: str, **kwargs)
Plot temperature scan results.
- biolm.pipeline.Predict(model_name: str, sequences: list[str] | DataFrame | str | Path, extractions: str | list | None = None, params: dict | None = None, **kwargs) → DataFrame
Convenience function for single-step prediction.
- Args:
model_name: BioLM model name sequences: Input sequences extractions: API response key(s) to extract. Required — pass the response
key for your model (e.g.
extractions='prediction'for temberture,extractions='mean_plddt'for esmfold). Use a list or ExtractionSpec for multiple extractions.params: Optional API parameters **kwargs: Additional arguments
- Returns:
DataFrame with predictions
- Example:
-
default
>>> df = Predict('temberture-regression', sequences=['MKTAYIAKQRQ'], extractions='prediction')
- class biolm.pipeline.RankingFilter(column: str, n: int | None = None, ascending: bool = False, method: str = 'top', percentile: float | None = None)
Bases:
BaseFilterFilter by ranking - select top N or bottom N by a column value.
This filter REQUIRES complete data to rank all sequences.
- Args:
column: Column name to rank by n: Number of sequences to select ascending: If True, select lowest values; if False, select highest (default) method: Ranking method (‘top’ for top N, ‘bottom’ for bottom N, ‘percentile’ for top/bottom %) percentile: If method=’percentile’, the percentile threshold (0-100)
- Example:
-
default
>>> # Top 100 by Tm >>> filter = RankingFilter('tm', n=100, ascending=False) >>> df_filtered = filter(df) >>> >>> # Bottom 50 by hamming distance >>> filter = RankingFilter('hamming_distance', n=50, ascending=True) >>> df_filtered = filter(df) >>> >>> # Top 10% by pLDDT >>> filter = RankingFilter('plddt', method='percentile', percentile=90) >>> df_filtered = filter(df)
- requires_complete_data: bool = True
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.RemaskingConfig(model_name: str = 'esm-150m', action: str = 'predict', mask_fraction: float = 0.15, mask_positions: str | list[int] = 'auto', num_iterations: int = 1, temperature: float = 1.0, top_k: int | None = None, top_p: float | None = None, mask_token: str = '<mask>', conserved_positions: list[int] | None = None, mask_strategy: str = 'random', block_size: int = 3, confidence_threshold: float = 0.8, parent_sequence: str | None = None, num_variants: int = 100)
Bases:
objectConfiguration for MLM remasking.
- Args:
model_name: MLM model to use (e.g., ‘esm-150m’, ‘esm-650m’, ‘esm-3b’, ‘esm3’, ‘esmc’) mask_fraction: Fraction of positions to mask (default: 0.15) mask_positions: Specific positions to mask, or ‘auto’ for random num_iterations: Number of remasking iterations (default: 1) temperature: Sampling temperature (default: 1.0) top_k: Top-k sampling (default: None) top_p: Nucleus sampling (default: None) mask_token: Token to use for masking (default: ‘<mask>’) conserved_positions: Positions that should never be masked mask_strategy: Strategy for selecting positions (‘random’, ‘low_confidence’, ‘blocks’) block_size: Size of blocks for block masking (default: 3) confidence_threshold: Threshold for low-confidence masking (default: 0.8)
- Example:
-
default
>>> # ESM2 150M remasking >>> config = RemaskingConfig( ... model_name='esm-150m', ... mask_fraction=0.15, ... num_iterations=5, ... temperature=1.0 ... ) >>> >>> # ESM3 with higher temperature >>> config = RemaskingConfig( ... model_name='esm3', ... mask_fraction=0.20, ... temperature=1.5 ... )
- action: str = 'predict'
- block_size: int = 3
- confidence_threshold: float = 0.8
- conserved_positions: list[int] | None = None
- mask_fraction: float = 0.15
- mask_positions: str | list[int] = 'auto'
- mask_strategy: str = 'random'
- mask_token: str = '<mask>'
- model_name: str = 'esm-150m'
- num_iterations: int = 1
- num_variants: int = 100
- parent_sequence: str | None = None
- temperature: float = 1.0
- to_spec() → dict
Return a serializable dict for pipeline definition persistence.
- top_k: int | None = None
- top_p: float | None = None
- class biolm.pipeline.SaturationMutagenesisConfig(parent_sequence: str, scoring_model: str, positions: list[int] | None = None, alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', scoring_action: str = 'predict', scoring_params: dict[str, typing.Any] = <factory>, score_field: str = 'ddg', top_n: int | None = 50, ascending: bool = True, exclude_synonymous: bool = True, batch_size: int = 8, label: str | None = None, pdb_str: str | None = None, chain: str = 'A')
Bases:
ScoringProtocolConfigSource config that generates a single-mutant library and filters by a prediction model.
Enumerates every single amino-acid substitution at the specified positions, scores each variant with scoring_model using scoring_action, then returns the top-top_n variants ranked by score_field.
Typical use: ThermoMPNN-D or ESM2StabP-guided design — predict ΔΔG for all single-point mutants, keep the most stabilising ones.
- Args:
parent_sequence: Wild-type sequence to mutate. scoring_model: BioLM model slug used to score variants (e.g.
'thermompnn-d'). positions: 0-indexed positions to enumerate. IfNone, all positionsin the sequence are enumerated.
alphabet: Amino acids to substitute. Defaults to the 20 canonical AAs. scoring_action: API action for the scoring model (default
'predict'). scoring_params: Extra params forwarded to the scoring model API. score_field: Key inside each model response that holds the numeric score(default
'ddg'). Supports nested access with'.'separator, e.g.'result.ddg'.- top_n: Number of top-scoring variants to retain (default 50).
None keeps all variants that receive a valid score.
- ascending: If
True, lower scores are better (e.g. negative ΔΔG means stabilising). Defaults to
True.- exclude_synonymous: If
True(default), skip substitutions that are identical to the wild-type residue.
batch_size: Sequences per API request when scoring (default 8). label: Optional label stored as
source_labelin results. pdb_str: Raw PDB file contents as a string (not a file path). Whenprovided, each scoring item is built as
{"pdb": pdb_str, "mutations": ["instead of"], "chain": chain} {"sequence": mutant_sequence}. Required by structure-aware models such as ThermoMPNN-D. PassNone(default) for sequence-only models like ESM2StabP.- chain: Chain identifier forwarded in structure-aware scoring items
(default
'A').
- top_n: Number of top-scoring variants to retain (default 50).
Example:
defaultfrom biolm.pipeline.generative import SaturationMutagenesisConfig, GenerativePipeline # Score all single-mutant variants at three positions with ThermoMPNN-D config = SaturationMutagenesisConfig( parent_sequence="MKTAYIAKQRQ", scoring_model="thermompnn-d", positions=[3, 7, 10], # 0-indexed; None → all positions score_field="ddg", # key in API response holding ΔΔG top_n=25, # keep top-25 most stabilising variants ascending=True, # lower ΔΔG = more stabilising pdb_str=open("protein.pdb").read(), # required by structure-aware models ) pipeline = GenerativePipeline(configs=[config]) pipeline.add_prediction("esmfold", extractions="mean_plddt", columns="plddt") results = pipeline.run()- alphabet: str = 'ACDEFGHIKLMNPQRSTVWY'
- ascending: bool = True
- batch_size: int = 8
- chain: str = 'A'
- exclude_synonymous: bool = True
- label: str | None = None
- parent_sequence: str
- pdb_str: str | None = None
- positions: list[int] | None = None
- score_field: str = 'ddg'
- scoring_action: str = 'predict'
- scoring_model: str
- scoring_params: dict[str, Any]
- to_spec() → dict
- top_n: int | None = 50
- class biolm.pipeline.ScoringProtocolConfig
Bases:
objectMarker base class for stages that score sequences and return a ranked subset.
Subclasses call a BioLM prediction model to assign a numeric score to each variant sequence enumerated by the config (e.g. all single-point mutants), then sort and return the top-N results. They do not produce wholly new sequences — the output is a scored, ranked subset of the candidate library built internally by the config (no separate generation step is needed).
The
scoring_actionfield on concrete subclasses selects the client method:'predict'callsBioLMApiClient.predict()and'score'callsBioLMApiClient.score(). Both are allowlisted ('predict','score') to prevent arbitrary method dispatch.Use
isinstance(config, ScoringProtocolConfig)()to branch on config type in pipeline dispatch functions.Example:
defaultfrom biolm.pipeline.generative import ( ScoringProtocolConfig, SaturationMutagenesisConfig, ) def dispatch(config): if isinstance(config, ScoringProtocolConfig): return run_scoring_stage(config) # returns ranked variant library return run_generative_stage(config) # returns novel sequences config = SaturationMutagenesisConfig( parent_sequence="MKTAYIAKQRQ", scoring_model="thermompnn-d", score_field="ddg", ) assert isinstance(config, ScoringProtocolConfig) # True
- class biolm.pipeline.SequenceClusterer(method: Literal['kmeans', 'dbscan', 'hierarchical'] = 'kmeans', n_clusters: int | None = None, similarity_metric: Literal['embedding'] = 'embedding', eps: float = 0.5, min_samples: int = 5, random_state: int = 42, mini_batch: bool = False, max_sample: int | None = None)
Bases:
objectCluster sequences by similarity.
Supports multiple clustering algorithms and similarity metrics.
- Performance Notes:
For >10k sequences with Hamming distance, consider using max_sample
Embedding-based clustering scales much better (O(n) with MiniBatch K-means)
Use mini_batch=True for very large datasets (>50k sequences)
- Args:
method: Clustering algorithm (‘kmeans’, ‘dbscan’, ‘hierarchical’) n_clusters: Number of clusters (for kmeans/hierarchical) similarity_metric: How to measure sequence similarity eps: DBSCAN epsilon parameter min_samples: DBSCAN minimum samples per cluster mini_batch: Use MiniBatchKMeans for large datasets (faster, approximate) max_sample: Maximum sequences to use for distance matrix (None = all)
- Example:
-
default
>>> # For large datasets, use sampling or mini-batch >>> clusterer = SequenceClusterer( ... method='kmeans', ... n_clusters=100, ... mini_batch=True # Much faster for large N ... ) >>> result = clusterer.cluster(sequences)
- cluster(sequences: list[str], embeddings: ndarray | None = None) → ClusteringResult
Cluster sequences and return assignments.
- Args:
sequences: List of protein sequences embeddings: Pre-computed embeddings (if using embedding metric)
- Returns:
ClusteringResult with cluster assignments and metrics
- class biolm.pipeline.SequenceLengthFilter(min_length: int | None = None, max_length: int | None = None)
Bases:
BaseFilterFilter by sequence length.
- Args:
min_length: Minimum sequence length (inclusive) max_length: Maximum sequence length (inclusive)
- Example:
-
default
>>> filter = SequenceLengthFilter(min_length=50, max_length=500) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.SequenceSourceConfig(sequences: list | DataFrame | str | Path | None = None, column: str = 'sequence', from_db: bool = False)
Bases:
objectUse existing sequences as the generation-slot source — no API calls made.
Plug into
set_generation()(or usepipeline.use_sequences()) to feed existing data through prediction/filter stages without generating new sequences. The provided sequences are added to the DuckDB via the normal dedup path, so sequences already present just return their existing IDs.- Args:
sequences: Source of sequences — one of:
list[str]: plain amino-acid stringspd.DataFrame: must contain column (default"sequence")str/Path: path to a CSV or FASTA (.fasta/.fa) fileNone: reload all sequences already in the DuckDB (requiresfrom_db=TrueOR leaving sequences as None)
- column: Column name when sequences is a DataFrame or CSV
(default
"sequence").- from_db: Pull all sequences already present in the pipeline’s DuckDB
instead of loading new ones. Equivalent to
sequences=None.
Example:
default# Inject a list pipeline.use_sequences(["MKTAY", "MKLLIV"]).run() # Use all sequences already in the DB (e.g. after from_db() recovery) pipeline.use_sequences(from_db=True).run() # Load from CSV pipeline.use_sequences("candidates.csv").run()- column: str = 'sequence'
- from_db: bool = False
- sequences: list | DataFrame | str | Path | None = None
- to_spec() → dict
Serialize for pipeline definition persistence.
Live DataFrames are not serializable — on reconstruct we fall back to
from_db=Trueso the existing DB sequences are reused. Plainlist[str]sequences are included directly so from_db() reconstruction can replay the same input without requiring an existing DB (GEN-09 fix).
- class biolm.pipeline.SingleStepPipeline(model_name: str, action: str = 'predict', sequences: list[str] | DataFrame | str | Path = None, params: dict | None = None, extractions=None, columns=None, embedding_extractor=None, **kwargs)
Bases:
DataPipelineSimplified pipeline for single-step predictions.
Convenience class for running a single prediction model on sequences.
- Args:
model_name: BioLM model name action: API action (‘predict’, ‘encode’, ‘score’) sequences: Input sequences params: Optional API parameters **kwargs: Additional arguments passed to DataPipeline
- Example:
-
default
>>> pipeline = SingleStepPipeline( ... model_name='esmfold', ... sequences=['MKTAYIAKQRQ', 'MKLAVID'] ... ) >>> results = pipeline.run() >>> df = pipeline.get_final_data()
- class biolm.pipeline.Stage(name: str, cache_key: str | None = None, depends_on: list[str] | None = None, model_name: str | None = None, max_concurrent: int = 10)
Bases:
ABCAbstract base class for pipeline stages.
A stage represents a single processing step in the pipeline. It can filter data, compute predictions, or transform sequences.
- Args:
name: Stage name (must be unique within pipeline) cache_key: Unused collision-dedup key (auto-derived by PredictionStage) depends_on: List of stage names this stage depends on model_name: Model name for predictions/structures max_concurrent: Maximum concurrent API calls (for rate limiting)
- Class Attributes:
- merge_mode: How this stage’s output is merged when it runs in parallel
with other stages.
"intersect"(default) = only keep sequences that pass all parallel stages (correct for filters)."union"= keep sequences that appear in any parallel stage output (correct for independent prediction stages that may skip sequences on error but should not drop others).
- merge_mode: str = 'intersect'
- async process(df: DataFrame, datastore: DuckDBDataStore, **kwargs) → tuple[pandas.core.frame.DataFrame, biolm.pipeline.base.StageResult]
Legacy DataFrame interface — used by streaming mode and GenerationStage.
Subclasses may override this for backward compatibility or for cases where a DataFrame is the natural input (e.g. generation with an empty df).
- abstract async process_ws(ws: WorkingSet, datastore: DuckDBDataStore, **kwargs) → tuple[biolm.pipeline.base.WorkingSet, biolm.pipeline.base.StageResult]
Process data using WorkingSet (DuckDB-native).
All stages must implement this method. Stages that need actual sequence data (e.g. ClusteringStage) should call
datastore.materialize_working_set(ws)internally — that is the stage’s responsibility, not the pipeline’s.- Args:
ws: Input WorkingSet (set of sequence IDs). datastore: DataStore for reading/writing data. **kwargs: Additional arguments (e.g.
run_id).- Returns:
Tuple of (output WorkingSet, StageResult).
- to_spec() → dict
Return a serializable dict describing this stage.
Used by BasePipeline.run_async() to persist pipeline definitions to DuckDB (enabling DataPipeline.from_db() recovery after kernel death).
Subclasses must override this. Raises NotImplementedError by default.
- class biolm.pipeline.StageResult(stage_name: str, input_count: int, output_count: int, filtered_count: int = 0, cached_count: int = 0, computed_count: int = 0, elapsed_time: float = 0.0, metadata: dict[str, typing.Any] = <factory>)
Bases:
objectResult from a pipeline stage.
- cached_count: int = 0
- computed_count: int = 0
- elapsed_time: float = 0.0
- filtered_count: int = 0
- input_count: int
- metadata: dict[str, Any]
- output_count: int
- stage_name: str
- class biolm.pipeline.StructureSpec(key: str, format: str | None = None, plddt_key: str | None = None, index: int | None = 0)
Bases:
objectSpecification for extracting and storing a structure from an API response.
- Args:
key: Response dict key containing the structure string (e.g. “pdb”, “cif”, “pdbs”). format: Structure format — “pdb” or “cif”. Auto-detected from key if None. plddt_key: Optional response key for a confidence score to store alongside. index: For list-valued keys (e.g. AF2 “pdbs”), which element to store (default 0).
- detect_format() → str
- format: str | None = None
- index: int | None = 0
- key: str
- plddt_key: str | None = None
- class biolm.pipeline.ThresholdFilter(column: str, min_value: float | None = None, max_value: float | None = None, keep_na: bool = False)
Bases:
BaseFilterFilter by column value threshold.
- Args:
column: Column name to filter on min_value: Minimum value (inclusive) max_value: Maximum value (inclusive) keep_na: Whether to keep rows with NaN values
- Example:
-
default
>>> filter = ThresholdFilter('tm', min_value=60.0) >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.ValidAminoAcidFilter(alphabet: str = 'ACDEFGHIKLMNPQRSTVWY', verbose: bool = True, column: str = 'sequence')
Bases:
BaseFilterFilter sequences to only those composed of valid amino acid characters.
Uses vectorized regex matching via
str.match()(C-level regex engine), which is ~100x faster than.apply(lambda)at million-sequence scale.- Args:
alphabet: String of allowed characters (default: 20 standard amino acids) verbose: If True, print count of removed sequences
- Example:
-
default
>>> filter = ValidAminoAcidFilter() >>> df_filtered = filter(df)
- to_spec() → dict
Return a serializable dict describing this filter.
Subclasses should override this. The base implementation raises
NotImplementedErrorso that pipeline definition serialization fails early for unsupported filter types.
- to_sql(ws_table: str = '_filter_ws', model_name: str | None = None) → str | None
Return a complete SQL SELECT that yields surviving
sequence_idvalues.The query must be scoped to the working set by JOINing with ws_table (a registered DuckDB table with a single
sequence_idcolumn). This ensures ranking/limit operations apply only to the current pipeline rows, not the entire datastore.- Args:
ws_table: Name of the registered temp table containing the working set. model_name: Optional model name to scope predictions to.
Example return value:
defaultSELECT w.sequence_id FROM _filter_ws w INNER JOIN predictions p ON w.sequence_id = p.sequence_id WHERE p.prediction_type = 'tm' AND p.value >= 60.0Returns
None(default) when the filter cannot be expressed as SQL. Filters that returnNonewill be executed via DataFrame materialization.
- class biolm.pipeline.WorkingSet(sequence_ids: frozenset[int])
Bases:
objectLightweight set of sequence IDs — replaces DataFrame as inter-stage transport.
Stages operate on DuckDB directly and pass only the set of surviving sequence IDs to the next stage. Materialization to DataFrame happens once at
get_final_data()time.Memory: 1M IDs ≈ 28 MB (frozenset[int]) vs 500 MB+ DataFrame.
- difference(other: WorkingSet) → WorkingSet
Return IDs in self but not in other.
- classmethod from_ids(ids) → WorkingSet
Create from any iterable of ints.
- intersect(other: WorkingSet) → WorkingSet
Return a new WorkingSet containing only IDs present in both sets.
- sequence_ids: frozenset[int]
- to_list() → list[int]
Return sorted list (useful for DuckDB queries).
- union(other: WorkingSet) → WorkingSet
Return a new WorkingSet containing IDs from either set.
- biolm.pipeline.analyze_diversity(sequences: list[str], max_sample: int | None = 10000) → dict
Convenience function for analyzing sequence diversity.
- Args:
sequences: List of protein sequences max_sample: Reserved for future embedding-based pairwise stats
- Returns:
Dictionary of diversity metrics (shannon_entropy, motif diversity, uniqueness)
- Example:
-
default
>>> metrics = analyze_diversity(sequences) >>> print(f"Entropy: {metrics['shannon_entropy']:.2f}")
- biolm.pipeline.cif_to_pdb(cif_path: str, output_path: str | None = None) → str
Convert a CIF structure file to PDB format.
Uses gemmi if available, falls back to biopython.
- Args:
cif_path: Path to input CIF (.cif / .mmcif) file. output_path: Destination PDB path. Defaults to same name with .pdb extension.
- Returns:
Path to written PDB file.
- Raises:
ImportError: If neither gemmi nor biopython is installed.
- biolm.pipeline.cluster_sequences(sequences: list[str], method: str = 'kmeans', n_clusters: int = 10, embeddings: ndarray | None = None, mini_batch: bool = False, max_sample: int | None = None, **kwargs) → ClusteringResult
Convenience function for clustering sequences using embeddings.
- Performance Tips:
For >50k sequences, use mini_batch=True
Requires pre-computed embeddings (similarity_metric=’embedding’)
- Args:
sequences: List of protein sequences method: Clustering algorithm n_clusters: Number of clusters embeddings: Pre-computed embeddings (required) mini_batch: Use MiniBatchKMeans for faster (approximate) clustering max_sample: Reserved; not yet used **kwargs: Additional arguments for SequenceClusterer
- Returns:
ClusteringResult
- biolm.pipeline.combine_filters(*filters: BaseFilter) → BaseFilter
Combine multiple filters into a single filter (applied sequentially).
Returns a
CompositeFilterwhich is serializable (unlike the previousCustomFilter-based implementation) and supports SQL fast-path evaluation when all sub-filters implementto_sql().- Args:
*filters: Variable number of filter objects
- Returns:
Combined filter
- Example:
-
default
>>> filter = combine_filters( ... ThresholdFilter('tm', min_value=60), ... SequenceLengthFilter(min_length=100) ... )
- biolm.pipeline.load_structure_string(path: str) → tuple[str, str]
Load a structure file and return (format, content_string).
- Args:
path: Path to a .pdb, .ent, .cif, or .mmcif file.
- Returns:
Tuple of (format_str, content_str) where format_str is ‘pdb’ or ‘cif’.
- Raises:
ValueError: If file extension is not recognised. FileNotFoundError: If file does not exist.
- biolm.pipeline.pdb_to_cif(pdb_path: str, output_path: str | None = None) → str
Convert a PDB structure file to CIF format.
Uses gemmi if available, falls back to biopython.
- Args:
pdb_path: Path to input PDB (.pdb / .ent) file. output_path: Destination CIF path. Defaults to same name with .cif extension.
- Returns:
Path to written CIF file.
- Raises:
ImportError: If neither gemmi nor biopython is installed.