rust-sasa-python 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -659,7 +659,7 @@ dependencies = [
659
659
 
660
660
  [[package]]
661
661
  name = "rust-sasa-python"
662
- version = "0.2.2"
662
+ version = "0.2.4"
663
663
  dependencies = [
664
664
  "nalgebra",
665
665
  "pdbtbx",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rust-sasa-python"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  edition = "2024"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -119,6 +119,43 @@ sasa_values = sasa.calculate_sasa_internal(atoms, probe_radius=1.4, n_points=100
119
119
  print(f"SASA values: {sasa_values}")
120
120
  ```
121
121
 
122
+ **`calculate_sasa_internal_at_residue_level(atoms_in: list[tuple[tuple[float, float, float], float, int]], probe_radius: float, n_points: int) -> list[ResidueResult]`**
123
+
124
+ Calculate SASA at the residue level for a set of atoms directly without PDB file parsing. This function groups atoms by residue ID and calculates the average SASA per atom within each residue.
125
+
126
+ **Parameters:**
127
+ - `atoms_in`: List of tuples, each containing:
128
+ - Atom coordinates as `(x, y, z)` tuple of floats
129
+ - Atom radius as float
130
+ - Residue ID as int (used to group atoms into residues)
131
+ - `probe_radius`: Probe sphere radius in Ångströms
132
+ - `n_points`: Number of points for surface sampling
133
+
134
+ **Returns:**
135
+ - List of `ResidueResult` objects with:
136
+ - `chain_id`: Always "UNK" (unknown)
137
+ - `residue_name`: Always "UNK" (unknown)
138
+ - `residue_number`: The residue ID from input
139
+ - `sasa`: Average SASA per atom in the residue
140
+
141
+ **Example:**
142
+ ```python
143
+ import rust_sasa_python as sasa
144
+
145
+ # Define atoms with residue grouping
146
+ # Residue 1: two atoms, Residue 2: one atom
147
+ atoms = [
148
+ ((0.0, 0.0, 0.0), 1.7, 1), # Carbon in residue 1
149
+ ((1.5, 0.0, 0.0), 1.2, 1), # Hydrogen in residue 1
150
+ ((0.0, 3.0, 0.0), 1.55, 2), # Nitrogen in residue 2
151
+ ]
152
+
153
+ # Calculate residue-level SASA
154
+ residues = sasa.calculate_sasa_internal_at_residue_level(atoms, probe_radius=1.4, n_points=100)
155
+ for residue in residues:
156
+ print(f"Residue {residue.residue_number}: {residue.sasa:.2f} Ų (avg per atom)")
157
+ ```
158
+
122
159
  ## Usage Examples
123
160
 
124
161
  ### Basic Usage
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rust-sasa-python
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -20,7 +20,7 @@ A high-performance **Python library** for computing SASA (Solvent Accessible Sur
20
20
 
21
21
  ## Features
22
22
 
23
- - 🦀 **Pure Rust Backend**: Leverages Rust's performance and safety.
23
+ - 🦀 **Powered by [RustSASA](https://github.com/maxall41/RustSASA)**: Leverages Rust's performance and safety.
24
24
  - ⚡️ **Ludicrous Speed**: **46X** faster than Biopython, **7X** faster than Freesasa.
25
25
  - 🐍 **Pythonic Interface**: Clean, intuitive API.
26
26
  - 🔧 **Configurable**: Customizable probe radius and sampling points.
@@ -51,7 +51,7 @@ print(f"Polar SASA: {result.polar:.2f}")
51
51
  print(f"Non-polar SASA: {result.non_polar:.2f}")
52
52
  ```
53
53
 
54
- See [DOCS](DOCS.md) for more information and API reference.
54
+ See [DOCS](https://github.com/maxall41/rust-sasa-python/blob/main/DOCS.md) for more information and API reference.
55
55
 
56
56
  ## Contributing
57
57
 
@@ -4,7 +4,7 @@ A high-performance **Python library** for computing SASA (Solvent Accessible Sur
4
4
 
5
5
  ## Features
6
6
 
7
- - 🦀 **Pure Rust Backend**: Leverages Rust's performance and safety.
7
+ - 🦀 **Powered by [RustSASA](https://github.com/maxall41/RustSASA)**: Leverages Rust's performance and safety.
8
8
  - ⚡️ **Ludicrous Speed**: **46X** faster than Biopython, **7X** faster than Freesasa.
9
9
  - 🐍 **Pythonic Interface**: Clean, intuitive API.
10
10
  - 🔧 **Configurable**: Customizable probe radius and sampling points.
@@ -35,7 +35,7 @@ print(f"Polar SASA: {result.polar:.2f}")
35
35
  print(f"Non-polar SASA: {result.non_polar:.2f}")
36
36
  ```
37
37
 
38
- See [DOCS](DOCS.md) for more information and API reference.
38
+ See [DOCS](https://github.com/maxall41/rust-sasa-python/blob/main/DOCS.md) for more information and API reference.
39
39
 
40
40
  ## Contributing
41
41
 
@@ -3,8 +3,12 @@ use pdbtbx;
3
3
  use pyo3::exceptions::PyRuntimeError;
4
4
  use pyo3::prelude::*;
5
5
  use rust_sasa::options::SASAOptions;
6
- use rust_sasa::{Atom, calculate_sasa_internal as calculate_sasa_internal_internal};
7
- use rust_sasa::{AtomLevel, ChainLevel, ProteinLevel, ResidueLevel}; // As in long long
6
+ use rust_sasa::{Atom, SASAProcessor, calculate_sasa_internal as calculate_sasa_internal_internal};
7
+ use rust_sasa::{AtomLevel, ChainLevel, ProteinLevel, ResidueLevel};
8
+ use simd::simd_sum; // As in long long
9
+ use std::collections::HashMap;
10
+
11
+ mod simd;
8
12
 
9
13
  #[pyclass]
10
14
  #[derive(Clone)]
@@ -35,7 +39,7 @@ pub struct Residue {
35
39
  #[pyo3(get)]
36
40
  pub residue_name: String,
37
41
  #[pyo3(get)]
38
- pub residue_number: i32,
42
+ pub residue_number: u32,
39
43
  #[pyo3(get)]
40
44
  pub sasa: f32,
41
45
  }
@@ -195,7 +199,7 @@ impl SASACalculator {
195
199
  residue_results.push(Residue {
196
200
  chain_id: result.chain_id,
197
201
  residue_name: result.name,
198
- residue_number: result.serial_number as i32,
202
+ residue_number: result.serial_number as u32,
199
203
  sasa: result.value,
200
204
  });
201
205
  }
@@ -316,6 +320,39 @@ pub fn calculate_sasa_internal(
316
320
  ))
317
321
  }
318
322
 
323
+ #[pyfunction]
324
+ fn calculate_sasa_internal_at_residue_level(
325
+ atoms_in: Vec<((f32, f32, f32), f32, usize)>,
326
+ probe_radius: f32,
327
+ n_points: usize,
328
+ ) -> PyResult<Vec<Residue>> {
329
+ let atom_sasa = calculate_sasa_internal(atoms_in.clone(), probe_radius, n_points).unwrap();
330
+
331
+ let mut residue_groups: HashMap<usize, Vec<f32>> = HashMap::new();
332
+
333
+ for (i, atom) in atoms_in.iter().enumerate() {
334
+ let parent_id = atom.2;
335
+ residue_groups
336
+ .entry(parent_id)
337
+ .or_insert_with(Vec::new)
338
+ .push(atom_sasa[i]);
339
+ }
340
+
341
+ let mut residue_sasa = Vec::new();
342
+ for (parent_id, sasa_values) in residue_groups {
343
+ let local_sum = simd_sum(sasa_values.as_slice());
344
+
345
+ residue_sasa.push(Residue {
346
+ chain_id: "UNK".to_string(),
347
+ residue_name: "UNK".to_string(),
348
+ residue_number: parent_id as u32,
349
+ sasa: local_sum,
350
+ });
351
+ }
352
+
353
+ Ok(residue_sasa)
354
+ }
355
+
319
356
  #[pymodule]
320
357
  fn rust_sasa_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
321
358
  m.add_class::<SASACalculator>()?;
@@ -329,6 +366,10 @@ fn rust_sasa_python(m: &Bound<'_, PyModule>) -> PyResult<()> {
329
366
  m.add_function(wrap_pyfunction!(calculate_atom_sasa, m)?)?;
330
367
  m.add_function(wrap_pyfunction!(calculate_chain_sasa, m)?)?;
331
368
  m.add_function(wrap_pyfunction!(calculate_sasa_internal, m)?)?;
369
+ m.add_function(wrap_pyfunction!(
370
+ calculate_sasa_internal_at_residue_level,
371
+ m
372
+ )?)?;
332
373
 
333
374
  Ok(())
334
375
  }
@@ -0,0 +1,33 @@
1
+ const LANES: usize = 16;
2
+ #[allow(clippy::needless_range_loop)]
3
+ pub fn simd_sum(values: &[f32]) -> f32 {
4
+ let chunks = values.chunks_exact(LANES);
5
+ let remainder = chunks.remainder();
6
+
7
+ let sum = chunks.fold([0.0f32; LANES], |mut acc, chunk| {
8
+ let chunk: [f32; LANES] = chunk.try_into().unwrap();
9
+ for i in 0..LANES {
10
+ acc[i] += chunk[i];
11
+ }
12
+ acc
13
+ });
14
+
15
+ let remainder: f32 = remainder.iter().copied().sum();
16
+
17
+ let mut reduced = 0.0f32;
18
+ for i in 0..LANES {
19
+ reduced += sum[i];
20
+ }
21
+ reduced + remainder
22
+ }
23
+
24
+ pub(crate) fn serialize_chain_id(s: &str) -> isize {
25
+ let mut result = 0;
26
+ for c in s.chars() {
27
+ if c.is_ascii_alphabetic() {
28
+ let position = c.to_ascii_uppercase() as isize - 64;
29
+ result = result * 10 + position;
30
+ }
31
+ }
32
+ result
33
+ }