Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,7 @@ class MyModelWrapper:
atom_array = structure["asym_unit"]
# ... model-specific featurization ...
conditioning = my_model_features(atom_array)
x_init = torch.zeros(n_atoms, 3, device=self.device)
return GenerativeModelInput(x_init=x_init, conditioning=conditioning)
return GenerativeModelInput(conditioning=conditioning)

def step(
self,
Expand All @@ -418,7 +417,7 @@ class MyModelWrapper:
shape: tuple[int, ...] | None = None,
) -> Tensor:
"""Sample from the prior (typically Gaussian noise)."""
n_atoms = features.x_init.shape[-2] if features else shape[-2]
n_atoms = features.conditioning.num_atoms if features else shape[-2]
return torch.randn(batch_size, n_atoms, 3, device=self.device)
```

Expand Down
86 changes: 10 additions & 76 deletions src/sampleworks/models/boltz/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,16 +314,13 @@ class BoltzConfig:
Output directory for processed Boltz files.
num_workers : int
Number of parallel workers for preprocessing.
ensemble_size : int
Number of samples to generate (batch dimension of x_init).
recycling_steps : int
Number of recycling steps to perform during featurization Pairformer pass.

"""

out_dir: str | Path | None = None
num_workers: int = 0
ensemble_size: int = 1
recycling_steps: int = 3


Expand All @@ -332,7 +329,6 @@ def process_structure_for_boltz(
*,
out_dir: str | Path | None = None,
num_workers: int = 0,
ensemble_size: int = 1,
recycling_steps: int | None = 3,
) -> dict:
"""Annotate an Atomworks structure with Boltz-specific configuration.
Expand All @@ -346,8 +342,6 @@ def process_structure_for_boltz(
Defaults to structure metadata ID or "boltz_output".
num_workers : int
Number of parallel workers for preprocessing.
ensemble_size : int
Number of samples to generate (batch dimension of x_init).
recycling_steps : int | None
Number of recycling steps to perform during featurization Pairformer pass.
Will set to 3 if None.
Expand All @@ -372,7 +366,6 @@ def process_structure_for_boltz(
config = BoltzConfig(
out_dir=out_dir or structure.get("metadata", {}).get("id", "boltz_output"),
num_workers=num_workers,
ensemble_size=ensemble_size,
recycling_steps=recycling_steps,
)
return {**structure, "_boltz_config": config}
Expand Down Expand Up @@ -649,7 +642,7 @@ def _setup_data_module(
def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
"""From an Atomworks structure, calculate Boltz-2 input features.

Runs Pairformer pass and initializes x_init from prior distribution.
Runs the Pairformer pass to produce conditioning features.

NOTE: Has side effect of creating Boltz input YAML and initial processed files with
create_boltz_input_from_structure() and the data module setup.
Expand All @@ -665,7 +658,7 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
Returns
-------
GenerativeModelInput[BoltzConditioning]
Model input with x_init and Pairformer conditioning.
Model input with Pairformer conditioning.
"""
self.cached_representations.clear()

Expand All @@ -681,8 +674,6 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
else base_dir
)
num_workers = config.num_workers
ensemble_size = config.ensemble_size

input_path = create_boltz_input_from_structure( # side effect of creating Boltz input YAML
structure,
out_dir,
Expand Down Expand Up @@ -721,32 +712,7 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
model_atom_array=model_atom_array,
)

# Use atom count from Boltz featurization in atom_pad_mask (via conditioning) to ensure
# consistency between x_init shape and atom_pad_mask used in step(). Note: x_init=None is
# a temporary placeholder; initialize_from_prior derives shape from conditioning.
feats = pairformer_out["feats"]
atom_mask = cast(Tensor, feats.get("atom_pad_mask"))
num_atoms = int(atom_mask.sum())

# x_init should be the reference coordinates for alignment purposes.
if true_atom_array is not None and len(true_atom_array) == num_atoms:
x_init = torch.tensor(true_atom_array.coord, device=self.device, dtype=torch.float32)
x_init = match_batch(x_init.unsqueeze(0), target_batch_size=ensemble_size).clone()
else:
# TODO: The temp features situation is not ideal and I think we can do better probably
# not sure exactly what the best way to handle x_init is, should define it a bit better
# most likely
logger.warning(
"True structure not available or atom count mismatch; initializing "
"x_init from prior. This means align_to_input will not work properly,"
" and reward functions dependent on this won't be accurate."
)
temp_features = GenerativeModelInput[BoltzConditioning](
x_init=cast(Any, None), conditioning=conditioning
)
x_init = self.initialize_from_prior(batch_size=ensemble_size, features=temp_features)

return GenerativeModelInput(x_init=x_init, conditioning=conditioning)
return GenerativeModelInput(conditioning=conditioning)

def _pairformer_pass(
self, features: dict[str, Any], recycling_steps: int = 3
Expand Down Expand Up @@ -951,8 +917,7 @@ def initialize_from_prior(
if shape is not None:
if len(shape) != 2 or shape[1] != 3:
raise ValueError("shape must be of the form (num_atoms, 3)")
x_init = torch.randn((batch_size, *shape), device=self.device)
return x_init
return torch.randn((batch_size, *shape), device=self.device)
if features is None or features.conditioning is None:
raise ValueError("Either features or shape must be provided to initialize_from_prior()")

Expand All @@ -963,9 +928,7 @@ def initialize_from_prior(

num_atoms = int(atom_mask.sum())

x_init = torch.randn((batch_size, num_atoms, 3), device=self.device)

return x_init
return torch.randn((batch_size, num_atoms, 3), device=self.device)


class Boltz1Wrapper:
Expand Down Expand Up @@ -1109,7 +1072,7 @@ def _setup_data_module(
def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
"""From an Atomworks structure, calculate Boltz-1 input features.

Runs Pairformer pass and initializes x_init from prior distribution.
Runs the Pairformer pass to produce conditioning features.

NOTE: Has side effect of creating Boltz input YAML and initial processed files with
create_boltz_input_from_structure() and the data module setup.
Expand All @@ -1125,7 +1088,7 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
Returns
-------
GenerativeModelInput[BoltzConditioning]
Model input with x_init and Pairformer conditioning.
Model input with Pairformer conditioning.
"""
self.cached_representations.clear()

Expand All @@ -1141,8 +1104,6 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
else base_dir
)
num_workers = config.num_workers
ensemble_size = config.ensemble_size

input_path = create_boltz_input_from_structure(
structure,
out_dir,
Expand Down Expand Up @@ -1180,31 +1141,7 @@ def featurize(self, structure: dict) -> GenerativeModelInput[BoltzConditioning]:
model_atom_array=model_atom_array,
)

# x_init should be the reference coordinates for alignment purposes.
# The guidance scalers call initialize_from_prior() separately for starting noise.
# Use atom count from Boltz featurization to ensure shape consistency with model.
feats = pairformer_out["feats"]
atom_mask = cast(Tensor, feats.get("atom_pad_mask"))
num_atoms = int(atom_mask.sum())

if true_atom_array is not None and len(true_atom_array) == num_atoms:
x_init = torch.tensor(true_atom_array.coord, device=self.device, dtype=torch.float32)
x_init = match_batch(x_init.unsqueeze(0), target_batch_size=ensemble_size).clone()
else:
# TODO: The temp features situation is not ideal and I think we can do better probably
# not sure exactly what the best way to handle x_init is, should define it a bit better
# most likely
logger.warning(
"True structure not available or atom count mismatch; initializing "
"x_init from prior. This means align_to_input will not work properly,"
" and reward functions dependent on this won't be accurate."
)
temp_features = GenerativeModelInput[BoltzConditioning](
x_init=cast(Any, None), conditioning=conditioning
)
x_init = self.initialize_from_prior(batch_size=ensemble_size, features=temp_features)

return GenerativeModelInput(x_init=x_init, conditioning=conditioning)
return GenerativeModelInput(conditioning=conditioning)

def step(
self,
Expand Down Expand Up @@ -1312,8 +1249,7 @@ def initialize_from_prior(
if shape is not None:
if len(shape) != 2 or shape[1] != 3:
raise ValueError("shape must be of the form (num_atoms, 3)")
x_init = torch.randn((batch_size, *shape), device=self.device)
return x_init
return torch.randn((batch_size, *shape), device=self.device)
if features is None or features.conditioning is None:
raise ValueError("Either features or shape must be provided to initialize_from_prior()")

Expand All @@ -1324,9 +1260,7 @@ def initialize_from_prior(

num_atoms = int(atom_mask.sum())

x_init = torch.randn((batch_size, num_atoms, 3), device=self.device)

return x_init
return torch.randn((batch_size, num_atoms, 3), device=self.device)

def _pairformer_pass(
self, features: dict[str, Any], recycling_steps: int = 3
Expand Down
44 changes: 4 additions & 40 deletions src/sampleworks/models/protenix/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ProtenixConditioning:
Raw feature dict for diffusion module.
num_atoms : int
Number of atoms in the Protenix model's representation. This is the authoritative
atom count for the diffusion module and should be used for x_init shape.
atom count for the diffusion module and prior initialization.
pair_z : Tensor | None
Cached pair representation for diffusion conditioning.
p_lm : Tensor | None
Expand Down Expand Up @@ -93,8 +93,6 @@ class ProtenixConfig:
----------
out_dir : str | Path | None
Output directory for intermediate JSON file.
ensemble_size : int
Number of ensemble members to generate.
recycling_steps : int | None
Number of recycling steps to perform. If None, uses model default.
use_msa : bool
Expand All @@ -104,7 +102,6 @@ class ProtenixConfig:
"""

out_dir: str | Path | None = None
ensemble_size: int = 1
recycling_steps: int | None = None
use_msa: bool = True
enable_diffusion_shared_vars_cache: bool = True
Expand All @@ -114,7 +111,6 @@ def annotate_structure_for_protenix(
structure: dict,
*,
out_dir: str | Path | None = None,
ensemble_size: int = 1,
recycling_steps: int | None = None,
use_msa: bool = True,
enable_diffusion_shared_vars_cache: bool = True,
Expand All @@ -128,8 +124,6 @@ def annotate_structure_for_protenix(
out_dir : str | Path | None
Output directory for intermediate files.
Defaults to structure metadata ID or "protenix_output".
ensemble_size : int
Number of ensemble members to generate.
recycling_steps : int | None
Number of recycling steps to perform. If None, uses model default.
use_msa : bool
Expand All @@ -144,7 +138,6 @@ def annotate_structure_for_protenix(
"""
config = ProtenixConfig(
out_dir=out_dir or structure.get("metadata", {}).get("id", "protenix_output"),
ensemble_size=ensemble_size,
recycling_steps=recycling_steps,
use_msa=use_msa,
enable_diffusion_shared_vars_cache=enable_diffusion_shared_vars_cache,
Expand Down Expand Up @@ -327,7 +320,7 @@ def __init__(
def featurize(self, structure: dict) -> GenerativeModelInput[ProtenixConditioning]:
"""From an Atomworks structure, calculate Protenix input features.

Runs Pairformer pass and initializes x_init from prior distribution.
Runs the Pairformer pass to produce conditioning features.

Parameters
----------
Expand All @@ -340,14 +333,13 @@ def featurize(self, structure: dict) -> GenerativeModelInput[ProtenixConditionin
Returns
-------
GenerativeModelInput[ProtenixConditioning]
Model input with x_init and Pairformer conditioning.
Model input with Pairformer conditioning.
"""
config = structure.get("_protenix_config", ProtenixConfig())
if isinstance(config, dict):
config = ProtenixConfig(**config)

out_dir = config.out_dir or structure.get("metadata", {}).get("id", "protenix_output")
ensemble_size = config.ensemble_size
use_msa = config.use_msa
recycling_steps = config.recycling_steps
enable_diffusion_shared_vars_cache = config.enable_diffusion_shared_vars_cache
Expand Down Expand Up @@ -505,35 +497,7 @@ def featurize(self, structure: dict) -> GenerativeModelInput[ProtenixConditionin
model_atom_array=model_aa,
)

# x_init should be the reference coordinates for alignment purposes.
# Must match num_atoms_protenix so downstream samplers/scalers see
# consistent shapes with the model representation.
if "asym_unit" in structure:
n_input = len(atom_array)
if n_input != num_atoms_protenix:
logger.warning(
f"Atom-count mismatch: atom_array has {n_input} atoms, "
f"atom_array_protenix has {num_atoms_protenix} atoms. "
"Using atom_array_protenix coords for x_init to match model "
"atom count.",
)
x_init = torch.as_tensor(
atom_array_protenix.coord, device=self.device, dtype=torch.float32
)
else:
x_init = torch.as_tensor(atom_array.coord, device=self.device, dtype=torch.float32)
x_init = match_batch(x_init.unsqueeze(0), target_batch_size=ensemble_size).clone()
else:
logger.warning(
"True structure not available, so initializing "
"x_init from prior. This means align_to_input will not work properly,"
" and reward functions dependent on this won't be accurate."
)
x_init = self.initialize_from_prior(
batch_size=ensemble_size, shape=(num_atoms_protenix, 3)
)

return GenerativeModelInput(x_init=x_init, conditioning=conditioning)
return GenerativeModelInput(conditioning=conditioning)

def _pairformer_pass(
self, features: dict[str, Any], grad_needed: bool = False, **kwargs
Expand Down
13 changes: 0 additions & 13 deletions src/sampleworks/models/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,12 @@
class GenerativeModelInput(Generic[C]): # noqa: UP046 (for Python 3.11 compatibility)
"""Container for inputs to generative models.

The x_init tensor is typically sampled from a prior distribution,
with shape determined by the input data (e.g., sequence length
determines atom count).

Attributes
----------
x_init : Float[Array, "*batch atoms 3"]
Initial structure coordinates.
This can be a reference structure (e.g. for alignment during sampling)
or a noisy sample from a prior distribution. This should have the proper
shape expected for the given ensemble being sampled, e.g. (4, atoms, 3) for
ensemble size 4.
conditioning : C | None
Model-specific conditioning features, or None.
"""

# TODO: make x_init more general (not just Float),
# relate this to StateT in Sampler protocol?
x_init: Float[Array, "*batch atoms 3"]
conditioning: C | None


Expand Down
Loading
Loading