positions.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. use std::{cmp::Ordering, fmt::Display, ops::Range, str::FromStr};
  2. use anyhow::Context;
  3. use rayon::prelude::*;
  4. use serde::{Deserialize, Serialize};
  5. /// Represents a position in the genome using 0-based coordinates.
  6. ///
  7. /// This struct encapsulates a genomic position, consisting of a contig (chromosome)
  8. /// identifier and a position within that contig.
  9. ///
  10. /// # Fields
  11. /// * `contig` - An 8-bit unsigned integer representing the contig/chromosome
  12. /// * `position` - A 32-bit unsigned integer representing the 0-based position within the contig
  13. ///
  14. /// # Derives
  15. /// This struct derives the following traits:
  16. /// * `Debug` for easier debugging and logging
  17. /// * `PartialEq` and `Eq` for equality comparisons
  18. /// * `Clone` for creating deep copies
  19. /// * `Serialize` and `Deserialize` for serialization (e.g., to JSON or other formats)
  20. /// * `Hash` for use in hash-based collections
  21. /// * `Default` for creating a default instance
  22. ///
  23. /// # Note
  24. /// The position is 0-based, meaning the first position in a contig is represented as 0.
  25. ///
  26. /// # Usage
  27. /// This struct is typically used to precisely locate genomic features or variants
  28. /// within a reference genome.
  29. ///
  30. /// # Example
  31. /// ```
  32. /// let position = GenomePosition {
  33. /// contig: 1, // Representing chromosome 1
  34. /// position: 1000, // 1001st base pair in the chromosome (remember it's 0-based)
  35. /// };
  36. /// ```
  37. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, Default)]
  38. pub struct GenomePosition {
  39. pub contig: u8,
  40. pub position: u32,
  41. }
  42. impl GenomePosition {
  43. /// Returns the contig (chromosome) identifier as a human-readable string.
  44. ///
  45. /// This method converts the internal numeric representation of the contig
  46. /// to a string format commonly used in genomics, such as "chr1", "chrX", etc...
  47. ///
  48. /// # Returns
  49. /// A `String` representing the contig identifier.
  50. ///
  51. /// # Examples
  52. ///
  53. /// ```
  54. /// let pos = GenomePosition { contig: 1, position: 1000 };
  55. /// assert_eq!(pos.contig(), "chr1");
  56. ///
  57. /// let pos_x = GenomePosition { contig: 23, position: 500 };
  58. /// assert_eq!(pos_x.contig(), "chrX");
  59. /// ```
  60. ///
  61. /// # Note
  62. /// This method uses the `num_to_contig` function to perform the conversion.
  63. /// Special cases like X, Y chromosomes and mitochondrial DNA are handled appropriately.
  64. pub fn contig(&self) -> String {
  65. num_to_contig(self.contig)
  66. }
  67. }
  68. /// Implements the `Display` trait for `GenomePosition`.
  69. ///
  70. /// This implementation allows a `GenomePosition` to be formatted as a string
  71. /// in the form "contig:position", where contig is the string representation
  72. /// of the chromosome (e.g., "chr1", "chrX") and position is the 0-based position.
  73. ///
  74. /// # Example
  75. /// ```
  76. /// let pos = GenomePosition { contig: 1, position: 1000 };
  77. /// assert_eq!(format!("{}", pos), "chr1:1000");
  78. /// ```
  79. impl Display for GenomePosition {
  80. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  81. write!(f, "{}:{}", self.contig(), self.position)
  82. }
  83. }
  84. /// Implements the `FromStr` trait for `GenomePosition`.
  85. ///
  86. /// This allows creating a `GenomePosition` from a string in the format "contig:position".
  87. ///
  88. /// # Errors
  89. /// Returns an `anyhow::Error` if:
  90. /// - The input string doesn't contain a ':' character
  91. /// - The contig part can't be converted to a numeric representation
  92. /// - The position part can't be parsed as a `u32`
  93. ///
  94. /// # Example
  95. /// ```
  96. /// use std::str::FromStr;
  97. /// let pos = GenomePosition::from_str("chr1:1000").unwrap();
  98. /// assert_eq!(pos.contig, 1);
  99. /// assert_eq!(pos.position, 1000);
  100. /// ```
  101. impl FromStr for GenomePosition {
  102. type Err = anyhow::Error;
  103. fn from_str(s: &str) -> anyhow::Result<Self> {
  104. let (c, p) = s
  105. .split_once(":")
  106. .ok_or(anyhow::anyhow!("Can't split {s}"))?;
  107. Ok(Self {
  108. contig: contig_to_num(c),
  109. position: p.parse()?,
  110. })
  111. }
  112. }
  113. /// Implements `TryFrom<(&str, &str)>` for `GenomePosition`.
  114. ///
  115. /// This allows creating a `GenomePosition` from a tuple of two string slices,
  116. /// where the first represents the contig and the second represents the position.
  117. ///
  118. /// # Errors
  119. /// Returns an `anyhow::Error` if:
  120. /// - The contig part can't be converted to a numeric representation
  121. /// - The position part can't be parsed as a `u32`
  122. ///
  123. /// # Example
  124. /// ```
  125. /// use std::convert::TryFrom;
  126. /// let pos = GenomePosition::try_from(("chr1", "1000")).unwrap();
  127. /// assert_eq!(pos.contig, 1);
  128. /// assert_eq!(pos.position, 1000);
  129. /// ```
  130. impl TryFrom<(&str, &str)> for GenomePosition {
  131. type Error = anyhow::Error;
  132. fn try_from((c, p): (&str, &str)) -> anyhow::Result<Self> {
  133. Ok(GenomePosition {
  134. contig: contig_to_num(c),
  135. position: p.parse().context(format!("Can't parse {p} into u32."))?,
  136. })
  137. }
  138. }
  139. /// Implements conversion from `VcfPosition` to `GenomePosition`.
  140. ///
  141. /// This conversion adjusts for the difference between 1-based VCF coordinates
  142. /// and 0-based genomic coordinates. It also handles the special case of
  143. /// position 0 in VCF, which represents a variant inside telomeres.
  144. ///
  145. /// # Note
  146. /// This implementation assumes that `VcfPosition` uses 1-based coordinates.
  147. ///
  148. /// # Example
  149. /// ```
  150. /// let vcf_pos = VcfPosition { contig: 1, position: 1001 };
  151. /// let genome_pos: GenomePosition = vcf_pos.into();
  152. /// assert_eq!(genome_pos.contig, 1);
  153. /// assert_eq!(genome_pos.position, 1000);
  154. /// ```
  155. impl From<VcfPosition> for GenomePosition {
  156. fn from(value: VcfPosition) -> Self {
  157. let position = if value.position == 0 {
  158. // position 0 is a variant inside telomeres
  159. 0
  160. } else {
  161. value.position - 1
  162. };
  163. Self {
  164. contig: value.contig,
  165. position,
  166. }
  167. }
  168. }
  169. /// Implements conversion from `GenomePosition` to `(String, String)`.
  170. ///
  171. /// This conversion creates a tuple where the first element is the string
  172. /// representation of the contig (e.g., "chr1", "chrX") and the second
  173. /// element is the string representation of the position.
  174. ///
  175. /// # Example
  176. /// ```
  177. /// let pos = GenomePosition { contig: 1, position: 1000 };
  178. /// let (contig, position): (String, String) = pos.into();
  179. /// assert_eq!(contig, "chr1");
  180. /// assert_eq!(position, "1000");
  181. /// ```
  182. impl From<GenomePosition> for (String, String) {
  183. fn from(val: GenomePosition) -> Self {
  184. (num_to_contig(val.contig), val.position.to_string())
  185. }
  186. }
  187. /// Implements `PartialOrd` for `GenomePosition`.
  188. ///
  189. /// This implementation allows `GenomePosition` instances to be compared
  190. /// and ordered based first on their contig, then on their position.
  191. impl PartialOrd for GenomePosition {
  192. fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  193. Some(self.cmp(other))
  194. }
  195. }
  196. /// Implements `Ord` for `GenomePosition`.
  197. ///
  198. /// This implementation defines a total ordering for `GenomePosition` instances.
  199. /// Positions are first compared by their contig, and if the contigs are equal,
  200. /// then by their position.
  201. ///
  202. /// # Example
  203. /// ```
  204. /// let pos1 = GenomePosition { contig: 1, position: 1000 };
  205. /// let pos2 = GenomePosition { contig: 1, position: 2000 };
  206. /// let pos3 = GenomePosition { contig: 2, position: 500 };
  207. /// assert!(pos1 < pos2);
  208. /// assert!(pos2 < pos3);
  209. /// ```
  210. impl Ord for GenomePosition {
  211. fn cmp(&self, other: &Self) -> Ordering {
  212. match self.contig.cmp(&other.contig) {
  213. Ordering::Equal => self.position.cmp(&other.position),
  214. other => other,
  215. }
  216. }
  217. }
  218. /// Converts a contig (chromosome) identifier string to its numeric representation.
  219. ///
  220. /// # Arguments
  221. /// * `contig` - A string slice representing the contig identifier (e.g., "chr1", "chrX")
  222. ///
  223. /// # Returns
  224. /// A `u8` representing the numeric value of the contig:
  225. /// * 1-22 for autosomes
  226. /// * 23 for X chromosome
  227. /// * 24 for Y chromosome
  228. /// * 25 for mitochondrial DNA
  229. /// * `u8::MAX` (255) for any unrecognized input
  230. ///
  231. /// # Examples
  232. /// ```
  233. /// assert_eq!(contig_to_num("chr1"), 1);
  234. /// assert_eq!(contig_to_num("chrX"), 23);
  235. /// assert_eq!(contig_to_num("chrM"), 25);
  236. /// assert_eq!(contig_to_num("chr_unknown"), u8::MAX);
  237. /// ```
  238. ///
  239. /// # Note
  240. /// This function assumes the input string starts with "chr" for standard chromosomes.
  241. /// The "chr" prefix is trimmed before parsing numeric values.
  242. pub fn contig_to_num(contig: &str) -> u8 {
  243. match contig {
  244. "chrX" => 23,
  245. "chrY" => 24,
  246. "chrM" => 25,
  247. c => c.trim_start_matches("chr").parse().unwrap_or(u8::MAX),
  248. }
  249. }
  250. /// Converts a numeric contig (chromosome) representation to its string identifier.
  251. ///
  252. /// # Arguments
  253. /// * `num` - A `u8` representing the numeric value of the contig
  254. ///
  255. /// # Returns
  256. /// A `String` representing the contig identifier:
  257. /// * "chr1" to "chr22" for autosomes
  258. /// * "chrX" for 23
  259. /// * "chrY" for 24
  260. /// * "chrM" for 25
  261. /// * "chr{num}" for any other input
  262. ///
  263. /// # Examples
  264. /// ```
  265. /// assert_eq!(num_to_contig(1), "chr1");
  266. /// assert_eq!(num_to_contig(23), "chrX");
  267. /// assert_eq!(num_to_contig(25), "chrM");
  268. /// ```
  269. ///
  270. /// # Note
  271. /// This function always prepends "chr" to the output string.
  272. pub fn num_to_contig(num: u8) -> String {
  273. match num {
  274. 23 => "chrX".to_string(),
  275. 24 => "chrY".to_string(),
  276. 25 => "chrM".to_string(),
  277. c => format!("chr{c}"),
  278. }
  279. }
  280. /// Represents a position in a VCF (Variant Call Format) file using 1-based coordinates.
  281. ///
  282. /// This struct encapsulates a genomic position as represented in VCF files, consisting of a contig (chromosome)
  283. /// identifier and a position within that contig.
  284. ///
  285. /// # Fields
  286. /// * `contig` - An 8-bit unsigned integer representing the contig/chromosome
  287. /// * `position` - A 32-bit unsigned integer representing the 1-based position within the contig
  288. ///
  289. /// # Derives
  290. /// This struct derives the following traits:
  291. /// * `Debug` for easier debugging and logging
  292. /// * `PartialEq` and `Eq` for equality comparisons
  293. /// * `Clone` for creating deep copies
  294. /// * `Serialize` and `Deserialize` for serialization (e.g., to JSON or other formats)
  295. ///
  296. /// # Note
  297. /// The position is 1-based, as per VCF file format specifications.
  298. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
  299. pub struct VcfPosition {
  300. pub contig: u8,
  301. pub position: u32,
  302. }
  303. /// Implements `TryFrom<(&str, &str)>` for `VcfPosition`.
  304. ///
  305. /// This allows creating a `VcfPosition` from a tuple of two string slices,
  306. /// where the first represents the contig and the second represents the position.
  307. ///
  308. /// # Errors
  309. /// Returns an `anyhow::Error` if:
  310. /// - The contig part can't be converted to a numeric representation
  311. /// - The position part can't be parsed as a `u32`
  312. ///
  313. /// # Example
  314. /// ```
  315. /// use std::convert::TryFrom;
  316. /// let pos = VcfPosition::try_from(("chr1", "1000")).unwrap();
  317. /// assert_eq!(pos.contig, 1);
  318. /// assert_eq!(pos.position, 1000);
  319. /// ```
  320. impl TryFrom<(&str, &str)> for VcfPosition {
  321. type Error = anyhow::Error;
  322. fn try_from((c, p): (&str, &str)) -> anyhow::Result<Self> {
  323. Ok(VcfPosition {
  324. contig: contig_to_num(c),
  325. position: p.parse().context(format!("Can't parse {p} into u32."))?,
  326. })
  327. }
  328. }
  329. /// Implements conversion from `GenomePosition` to `VcfPosition`.
  330. ///
  331. /// This conversion adjusts for the difference between 0-based genomic coordinates
  332. /// and 1-based VCF coordinates.
  333. ///
  334. /// # Example
  335. /// ```
  336. /// let genome_pos = GenomePosition { contig: 1, position: 999 };
  337. /// let vcf_pos: VcfPosition = genome_pos.into();
  338. /// assert_eq!(vcf_pos.contig, 1);
  339. /// assert_eq!(vcf_pos.position, 1000);
  340. /// ```
  341. impl From<GenomePosition> for VcfPosition {
  342. fn from(value: GenomePosition) -> Self {
  343. Self {
  344. contig: value.contig,
  345. position: value.position + 1,
  346. }
  347. }
  348. }
  349. /// Implements conversion from `VcfPosition` to `(String, String)`.
  350. ///
  351. /// This conversion creates a tuple where the first element is the string
  352. /// representation of the contig (e.g., "chr1", "chrX") and the second
  353. /// element is the string representation of the position.
  354. ///
  355. /// # Example
  356. /// ```
  357. /// let pos = VcfPosition { contig: 1, position: 1000 };
  358. /// let (contig, position): (String, String) = pos.into();
  359. /// assert_eq!(contig, "chr1");
  360. /// assert_eq!(position, "1000");
  361. /// ```
  362. impl From<VcfPosition> for (String, String) {
  363. fn from(val: VcfPosition) -> Self {
  364. (num_to_contig(val.contig), val.position.to_string())
  365. }
  366. }
  367. /// Represents a range in the genome using 0-based, half-open coordinates [start, end).
  368. ///
  369. /// This struct encapsulates a genomic range, consisting of a contig (chromosome)
  370. /// identifier and a range of positions within that contig.
  371. ///
  372. /// # Fields
  373. /// * `contig` - An 8-bit unsigned integer representing the contig/chromosome
  374. /// * `range` - A `Range<u32>` representing the start (inclusive) and end (exclusive) positions
  375. ///
  376. /// # Derives
  377. /// This struct derives the following traits:
  378. /// * `Debug` for easier debugging and logging
  379. /// * `PartialEq` and `Eq` for equality comparisons
  380. /// * `Clone` for creating deep copies
  381. /// * `Serialize` and `Deserialize` for serialization (e.g., to JSON or other formats)
  382. ///
  383. /// # Note
  384. /// The range is 0-based and half-open, meaning the start position is included but the end position is not.
  385. /// This is consistent with many programming languages and bioinformatics tools.
  386. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
  387. pub struct GenomeRange {
  388. pub contig: u8,
  389. pub range: Range<u32>,
  390. }
  391. /// Implements `TryFrom<(&str, &str, &str)>` for `GenomeRange`.
  392. ///
  393. /// This allows creating a `GenomeRange` from a tuple of three string slices,
  394. /// where the first represents the contig, the second the start position,
  395. /// and the third the end position.
  396. ///
  397. /// # Errors
  398. /// Returns an `anyhow::Error` if:
  399. /// - The contig part can't be converted to a numeric representation
  400. /// - Either the start or end position can't be parsed as a `u32`
  401. ///
  402. /// # Example
  403. /// ```
  404. /// use std::convert::TryFrom;
  405. /// let range = GenomeRange::try_from(("chr1", "1000", "2000")).unwrap();
  406. /// assert_eq!(range.contig, 1);
  407. /// assert_eq!(range.range.start, 1000);
  408. /// assert_eq!(range.range.end, 2000);
  409. /// ```
  410. impl TryFrom<(&str, &str, &str)> for GenomeRange {
  411. type Error = anyhow::Error;
  412. fn try_from((c, s, e): (&str, &str, &str)) -> anyhow::Result<Self> {
  413. Ok(Self {
  414. contig: contig_to_num(c),
  415. range: Range {
  416. start: s.parse().context(format!("Can't parse {s} into u32."))?,
  417. end: e.parse().context(format!("Can't parse {e} into u32."))?,
  418. },
  419. })
  420. }
  421. }
  422. impl GenomeRange {
  423. /// Creates a `GenomeRange` from 1-based inclusive start and end positions.
  424. ///
  425. /// This method is useful when working with data sources that use 1-based coordinates
  426. /// and inclusive ranges. It converts the input to the 0-based, half-open format used by `GenomeRange`.
  427. ///
  428. /// # Arguments
  429. /// * `contig` - A string slice representing the contig/chromosome
  430. /// * `start` - A string slice representing the 1-based inclusive start position
  431. /// * `end` - A string slice representing the 1-based inclusive end position
  432. ///
  433. /// # Returns
  434. /// A `Result` containing the `GenomeRange` if successful, or an `anyhow::Error` if parsing fails.
  435. ///
  436. /// # Errors
  437. /// Returns an `anyhow::Error` if:
  438. /// - The contig can't be converted to a numeric representation
  439. /// - Either the start or end position can't be parsed as a `u32`
  440. ///
  441. /// # Example
  442. /// ```
  443. /// let range = GenomeRange::from_1_inclusive("chr1", "1000", "2000").unwrap();
  444. /// assert_eq!(range.contig, 1);
  445. /// assert_eq!(range.range.start, 999); // Converted to 0-based
  446. /// assert_eq!(range.range.end, 2000); // End is exclusive in 0-based
  447. /// ```
  448. pub fn from_1_inclusive(contig: &str, start: &str, end: &str) -> anyhow::Result<Self> {
  449. Ok(Self {
  450. contig: contig_to_num(contig),
  451. range: Range {
  452. start: start
  453. .parse::<u32>()
  454. .context(format!("Can't parse {start} into u32."))?
  455. - 1,
  456. end: end
  457. .parse()
  458. .context(format!("Can't parse {end} into u32."))?,
  459. },
  460. })
  461. }
  462. }
  463. /// Finds overlaps between a list of genome positions and a list of genome ranges in parallel.
  464. ///
  465. /// This function uses Rayon for parallel processing, dividing the positions into chunks
  466. /// and processing them concurrently.
  467. ///
  468. /// # Arguments
  469. /// * `positions` - A slice of references to `GenomePosition`s
  470. /// * `ranges` - A slice of references to `GenomeRange`s
  471. ///
  472. /// # Returns
  473. /// A `Vec<usize>` containing the indices of positions that overlap with any range.
  474. ///
  475. /// # Performance
  476. /// This function is optimized for scenarios where both `positions` and `ranges` are sorted
  477. /// by contig and position/range start. It uses this ordering to avoid unnecessary comparisons.
  478. ///
  479. /// # Example
  480. /// ```
  481. /// let positions = vec![
  482. /// &GenomePosition { contig: 1, position: 100 },
  483. /// &GenomePosition { contig: 1, position: 200 },
  484. /// &GenomePosition { contig: 2, position: 150 },
  485. /// ];
  486. /// let ranges = vec![
  487. /// &GenomeRange { contig: 1, range: 50..150 },
  488. /// &GenomeRange { contig: 2, range: 100..200 },
  489. /// ];
  490. /// let overlaps = overlaps_par(&positions, &ranges);
  491. /// assert_eq!(overlaps, vec!);
  492. /// ```
  493. pub fn overlaps_par(positions: &[&GenomePosition], ranges: &[&GenomeRange]) -> Vec<usize> {
  494. let chunk_size = (positions.len() / rayon::current_num_threads()).max(1);
  495. positions
  496. .par_chunks(chunk_size)
  497. .enumerate()
  498. .flat_map(|(chunk_idx, chunk)| {
  499. let mut result = Vec::new();
  500. let mut range_idx = ranges.partition_point(|r| r.contig < chunk[0].contig);
  501. for (pos_idx, pos) in chunk.iter().enumerate() {
  502. while range_idx < ranges.len() && ranges[range_idx].contig < pos.contig {
  503. range_idx += 1;
  504. }
  505. if range_idx < ranges.len() && ranges[range_idx].contig == pos.contig {
  506. while range_idx < ranges.len()
  507. && ranges[range_idx].contig == pos.contig
  508. && ranges[range_idx].range.end <= pos.position
  509. {
  510. range_idx += 1;
  511. }
  512. if range_idx < ranges.len()
  513. && ranges[range_idx].contig == pos.contig
  514. && ranges[range_idx].range.contains(&pos.position)
  515. {
  516. result.push(chunk_idx * chunk_size + pos_idx);
  517. }
  518. }
  519. }
  520. result
  521. })
  522. .collect()
  523. }
  524. /// A parallel version of the overlap finding function that works with any types
  525. /// implementing `GetGenomePosition` and `GetGenomeRange` traits.
  526. ///
  527. /// This function first converts the input slices to vectors of `GenomePosition` and `GenomeRange`,
  528. /// then calls `overlaps_par`.
  529. ///
  530. /// # Arguments
  531. /// * `a` - A slice of items implementing `GetGenomePosition`
  532. /// * `b` - A slice of items implementing `GetGenomeRange`
  533. ///
  534. /// # Returns
  535. /// A `Vec<usize>` containing the indices of positions that overlap with any range.
  536. ///
  537. /// # Example
  538. /// ```
  539. /// struct MyPosition(GenomePosition);
  540. /// impl GetGenomePosition for MyPosition {
  541. /// fn position(&self) -> &GenomePosition { &self.0 }
  542. /// }
  543. ///
  544. /// struct MyRange(GenomeRange);
  545. /// impl GetGenomeRange for MyRange {
  546. /// fn range(&self) -> &GenomeRange { &self.0 }
  547. /// }
  548. ///
  549. /// let positions = vec![
  550. /// MyPosition(GenomePosition { contig: 1, position: 100 }),
  551. /// MyPosition(GenomePosition { contig: 1, position: 200 }),
  552. /// ];
  553. /// let ranges = vec![
  554. /// MyRange(GenomeRange { contig: 1, range: 50..150 }),
  555. /// ];
  556. /// let overlaps = par_overlaps(&positions, &ranges);
  557. /// assert_eq!(overlaps, vec!);
  558. /// ```
  559. pub fn par_overlaps(a: &[impl GetGenomePosition], b: &[impl GetGenomeRange]) -> Vec<usize> {
  560. let a: Vec<_> = a.iter().map(|e| e.position()).collect();
  561. let b: Vec<_> = b.iter().map(|e| e.range()).collect();
  562. overlaps_par(&a, &b)
  563. }
  564. /// A trait for types that can provide a reference to a `GenomePosition`.
  565. ///
  566. /// Implement this trait for types that contain or can compute a `GenomePosition`.
  567. pub trait GetGenomePosition {
  568. // Returns a reference to the `GenomePosition` for this item.
  569. fn position(&self) -> &GenomePosition;
  570. }
  571. /// A trait for types that can provide a reference to a `GenomeRange`.
  572. ///
  573. /// Implement this trait for types that contain or can compute a `GenomeRange`.
  574. pub trait GetGenomeRange {
  575. /// Returns a reference to the `GenomeRange` for this item.
  576. fn range(&self) -> &GenomeRange;
  577. }