Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ and the project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]
### Added
- (Add upcoming features here.)
- **Fraction of Native Contacts (Q-Value) Analysis**: New `qvalue` module implementing the Best-Hummer-Eaton Q metric for measuring protein folding state and native structure retention.
- User-configurable parameters: `reference_frame`, `beta_const`, `lambda_const`, `native_cutoff`
- Automatic native contact identification and metadata reporting
- Integration with multi-analysis orchestrator and slide generation
- Full CLI support with `fastmda qvalue` command
- New features since `v1.0.0`.
### Changed
- (Add behavior/CLI/API changes here.)
### Fixed
Expand Down
1 change: 1 addition & 0 deletions docs/source/analysis/qvalue.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Outputs

- ``qvalue.dat`` — Q-values (one per frame, range [0, 1])
- ``qvalue_stats.dat`` — mean and standard deviation when ``compute_stat=True``
- ``qvalue_contact_list.dat`` — residue index pairs (1-based) used as native contacts
- ``qvalue.png`` — Line plot of Q-value vs. frame with metadata annotation
- ``qvalue_metadata.json`` — Complete metadata including:

Expand Down
21 changes: 18 additions & 3 deletions src/fastmdanalysis/analysis/qvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,14 @@ def run(self) -> Dict[str, np.ndarray]:
r = md.compute_distances(self.traj, native_contacts) # shape (N_frames, N_contacts)
r0 = md.compute_distances(self.traj[self.reference_frame], native_contacts)[0] # shape (N_contacts,)

# Build residue-pair contact list (1-based residue indices)
contact_pairs = []
for atom_i, atom_j in native_contacts:
res_i = self.traj.topology.atom(atom_i).residue.index + 1
res_j = self.traj.topology.atom(atom_j).residue.index + 1
contact_pairs.append((res_i, res_j))
contact_pairs = np.asarray(contact_pairs, dtype=int)

# Compute Q-values using Best-Hummer-Eaton formula
logger.debug("Computing Q-values...")
q_values = np.mean(
Expand All @@ -244,7 +252,10 @@ def run(self) -> Dict[str, np.ndarray]:
)

self.data = np.asarray(q_values, dtype=float).reshape(-1, 1)
self.results = {"qvalue": self.data}
self.results = {
"qvalue": self.data,
"contact_list": contact_pairs,
}

if self.compute_stat:
mean_val = float(np.nanmean(q_values))
Expand All @@ -256,10 +267,15 @@ def run(self) -> Dict[str, np.ndarray]:
header="mean std",
fmt="%.6f",
)

# Save data
logger.info("Saving Q-value data...")
self._save_data(self.data, "qvalue", header="qvalue", fmt="%.6f")
self._save_data(
contact_pairs,
"qvalue_contact_list",
header="residue_i residue_j (1-based)",
fmt="%d",
)

# Save metadata
self._save_metadata()
Expand Down Expand Up @@ -341,7 +357,6 @@ def plot(self, data: Optional[np.ndarray] = None, **kwargs):
line_kwargs = {"linestyle": linestyle, "marker": marker}
if color is not None:
line_kwargs["color"] = color

ax.plot(x, y, **line_kwargs)
if self.compute_stat:
mean_val = float(np.nanmean(y))
Expand Down
4 changes: 4 additions & 0 deletions tests/test_qvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def test_qvalue_metadata(fastmda, tmp_path):
metadata_path = output_dir / "qvalue_metadata.json"
assert metadata_path.exists()

# Contact list exists
contact_path = output_dir / "qvalue_contact_list.dat"
assert contact_path.exists()

# Load and validate metadata
with open(metadata_path, "r") as f:
metadata = json.load(f)
Expand Down