1use std::{fmt::Display, str::FromStr, sync::Arc};
2
3use bincode::{Decode, Encode};
4use mzcore::sequence::{Annotation, Peptidoform, Region, UnAmbiguous};
5use serde::{Deserialize, Serialize};
6
7use super::species::Species;
8use crate::Allele;
9
10#[derive(Clone, Debug, Decode, Deserialize, Encode, Serialize)]
12pub struct Germlines {
13 pub species: Species,
15 pub h: Chain,
17 pub k: Chain,
19 pub l: Chain,
21 pub i: Chain,
23}
24
25impl Germlines {
26 pub fn new(species: Species) -> Self {
28 Self {
29 species,
30 h: Chain::default(),
31 k: Chain::default(),
32 l: Chain::default(),
33 i: Chain::default(),
34 }
35 }
36
37 pub fn insert(&mut self, germline: Germline) {
39 match &germline.name.chain {
40 ChainType::Heavy => self.h.insert(germline),
41 ChainType::LightKappa => self.k.insert(germline),
42 ChainType::LightLambda => self.l.insert(germline),
43 ChainType::Iota => self.i.insert(germline),
44 }
45 }
46}
47
48impl<'a> IntoIterator for &'a Germlines {
49 type IntoIter = std::array::IntoIter<(ChainType, &'a Chain), 4>;
50 type Item = (ChainType, &'a Chain);
51
52 fn into_iter(self) -> Self::IntoIter {
53 [
54 (ChainType::Heavy, &self.h),
55 (ChainType::LightKappa, &self.k),
56 (ChainType::LightLambda, &self.l),
57 (ChainType::Iota, &self.i),
58 ]
59 .into_iter()
60 }
61}
62
63impl Germlines {
64 pub fn iter(
66 &self,
67 ) -> impl DoubleEndedIterator<Item = (ChainType, &Chain)> + ExactSizeIterator + '_ {
68 [
69 (ChainType::Heavy, &self.h),
70 (ChainType::LightKappa, &self.k),
71 (ChainType::LightLambda, &self.l),
72 (ChainType::Iota, &self.i),
73 ]
74 .into_iter()
75 }
76}
77
78#[cfg(feature = "rayon")]
79use rayon::prelude::*;
80#[cfg(feature = "rayon")]
81impl<'a> IntoParallelIterator for &'a Germlines {
82 type Item = (ChainType, &'a Chain);
83 type Iter = rayon::array::IntoIter<(ChainType, &'a Chain), 4>;
84
85 fn into_par_iter(self) -> Self::Iter {
86 [
87 (ChainType::Heavy, &self.h),
88 (ChainType::LightKappa, &self.k),
89 (ChainType::LightLambda, &self.l),
90 (ChainType::Iota, &self.i),
91 ]
92 .into_par_iter()
93 }
94}
95
96#[derive(Clone, Debug, Decode, Default, Deserialize, Encode, Serialize)]
98pub struct Chain {
99 pub variable: Vec<Arc<Germline>>,
101 pub joining: Vec<Arc<Germline>>,
103 pub c: Vec<Arc<Germline>>,
105 pub a: Vec<Arc<Germline>>,
107 pub d: Vec<Arc<Germline>>,
109 pub e: Vec<Arc<Germline>>,
111 pub g: Vec<Arc<Germline>>,
113 pub m: Vec<Arc<Germline>>,
115 pub n: Vec<Arc<Germline>>,
117 pub o: Vec<Arc<Germline>>,
119 pub r: Vec<Arc<Germline>>,
121 pub t: Vec<Arc<Germline>>,
123 pub w: Vec<Arc<Germline>>,
125 pub y: Vec<Arc<Germline>>,
127 pub z: Vec<Arc<Germline>>,
129}
130
131impl Chain {
132 pub fn insert(&mut self, mut germline: Germline) {
136 let db = match &germline.name.kind {
137 GeneType::V => &mut self.variable,
138 GeneType::J => &mut self.joining,
139 GeneType::C(None) => &mut self.c,
140 GeneType::C(Some(Constant::A)) => &mut self.a,
141 GeneType::C(Some(Constant::D)) => &mut self.d,
142 GeneType::C(Some(Constant::E)) => &mut self.e,
143 GeneType::C(Some(Constant::G)) => &mut self.g,
144 GeneType::C(Some(Constant::M)) => &mut self.m,
145 GeneType::C(Some(Constant::N)) => &mut self.n,
146 GeneType::C(Some(Constant::O)) => &mut self.o,
147 GeneType::C(Some(Constant::R)) => &mut self.r,
148 GeneType::C(Some(Constant::T)) => &mut self.t,
149 GeneType::C(Some(Constant::W)) => &mut self.w,
150 GeneType::C(Some(Constant::Y)) => &mut self.y,
151 GeneType::C(Some(Constant::Z)) => &mut self.z,
152 };
153
154 match db.binary_search_by_key(&germline.name, |g| g.name.clone()) {
155 Ok(index) => {
158 match db[index].alleles.binary_search_by_key(&germline.alleles[0].0, |a| a.0) {
159 Ok(_allele_index) => {
160 panic!(
188 "Not allowed to have multiple sequences for one allele in a germline"
189 )
190 }
191 Err(allele_index) => Arc::get_mut(&mut db[index])
192 .map(|g| {
193 g.alleles.insert(allele_index, germline.alleles.pop().unwrap());
194 })
195 .expect("Multiple copies of Arc while building IMGT structure"),
196 }
197 }
198 Err(index) => db.insert(index, Arc::new(germline)),
199 }
200 }
201
202 pub fn doc_row(&self) -> String {
204 format!(
205 "|{}/{}|{}/{}|{}/{}|",
206 self.variable.len(),
207 self.variable.iter().map(|g| g.alleles.len()).sum::<usize>(),
208 self.joining.len(),
209 self.joining.iter().map(|g| g.alleles.len()).sum::<usize>(),
210 self.constant().count(),
211 self.constant().map(|g| g.alleles.len()).sum::<usize>(),
212 )
213 }
214
215 pub fn constant(&self) -> impl Iterator<Item = &Arc<Germline>> {
217 self.c
218 .iter()
219 .chain(self.a.iter())
220 .chain(self.d.iter())
221 .chain(self.e.iter())
222 .chain(self.g.iter())
223 .chain(self.m.iter())
224 .chain(self.n.iter())
225 .chain(self.o.iter())
226 .chain(self.r.iter())
227 .chain(self.t.iter())
228 .chain(self.w.iter())
229 .chain(self.y.iter())
230 .chain(self.z.iter())
231 }
232}
233
234impl<'a> IntoIterator for &'a Chain {
235 type IntoIter = std::array::IntoIter<(GeneType, &'a [Arc<Germline>]), 15>;
236 type Item = (GeneType, &'a [Arc<Germline>]);
237
238 fn into_iter(self) -> Self::IntoIter {
239 [
240 (GeneType::V, self.variable.as_slice()),
241 (GeneType::J, self.joining.as_slice()),
242 (GeneType::C(None), self.c.as_slice()),
243 (GeneType::C(Some(Constant::A)), self.a.as_slice()),
244 (GeneType::C(Some(Constant::D)), self.d.as_slice()),
245 (GeneType::C(Some(Constant::E)), self.e.as_slice()),
246 (GeneType::C(Some(Constant::G)), self.g.as_slice()),
247 (GeneType::C(Some(Constant::M)), self.m.as_slice()),
248 (GeneType::C(Some(Constant::N)), self.n.as_slice()),
249 (GeneType::C(Some(Constant::O)), self.o.as_slice()),
250 (GeneType::C(Some(Constant::R)), self.r.as_slice()),
251 (GeneType::C(Some(Constant::T)), self.t.as_slice()),
252 (GeneType::C(Some(Constant::W)), self.w.as_slice()),
253 (GeneType::C(Some(Constant::Y)), self.y.as_slice()),
254 (GeneType::C(Some(Constant::Z)), self.z.as_slice()),
255 ]
256 .into_iter()
257 }
258}
259
260impl Chain {
261 pub fn iter(
263 &self,
264 ) -> impl DoubleEndedIterator<Item = (GeneType, &[Arc<Germline>])> + ExactSizeIterator + '_
265 {
266 [
267 (GeneType::V, self.variable.as_slice()),
268 (GeneType::J, self.joining.as_slice()),
269 (GeneType::C(None), self.c.as_slice()),
270 (GeneType::C(Some(Constant::A)), self.a.as_slice()),
271 (GeneType::C(Some(Constant::D)), self.d.as_slice()),
272 (GeneType::C(Some(Constant::E)), self.e.as_slice()),
273 (GeneType::C(Some(Constant::G)), self.g.as_slice()),
274 (GeneType::C(Some(Constant::M)), self.m.as_slice()),
275 (GeneType::C(Some(Constant::N)), self.n.as_slice()),
276 (GeneType::C(Some(Constant::O)), self.o.as_slice()),
277 (GeneType::C(Some(Constant::R)), self.r.as_slice()),
278 (GeneType::C(Some(Constant::T)), self.t.as_slice()),
279 (GeneType::C(Some(Constant::W)), self.w.as_slice()),
280 (GeneType::C(Some(Constant::Y)), self.y.as_slice()),
281 (GeneType::C(Some(Constant::Z)), self.z.as_slice()),
282 ]
283 .into_iter()
284 }
285}
286
287#[cfg(feature = "rayon")]
288impl<'a> IntoParallelIterator for &'a Chain {
289 type Item = (GeneType, &'a [Arc<Germline>]);
290 type Iter = rayon::array::IntoIter<(GeneType, &'a [Arc<Germline>]), 15>;
291
292 fn into_par_iter(self) -> Self::Iter {
293 [
294 (GeneType::V, self.variable.as_slice()),
295 (GeneType::J, self.joining.as_slice()),
296 (GeneType::C(None), self.c.as_slice()),
297 (GeneType::C(Some(Constant::A)), self.a.as_slice()),
298 (GeneType::C(Some(Constant::D)), self.d.as_slice()),
299 (GeneType::C(Some(Constant::E)), self.e.as_slice()),
300 (GeneType::C(Some(Constant::G)), self.g.as_slice()),
301 (GeneType::C(Some(Constant::M)), self.m.as_slice()),
302 (GeneType::C(Some(Constant::N)), self.m.as_slice()),
303 (GeneType::C(Some(Constant::O)), self.o.as_slice()),
304 (GeneType::C(Some(Constant::R)), self.r.as_slice()),
305 (GeneType::C(Some(Constant::T)), self.t.as_slice()),
306 (GeneType::C(Some(Constant::W)), self.w.as_slice()),
307 (GeneType::C(Some(Constant::Y)), self.y.as_slice()),
308 (GeneType::C(Some(Constant::Z)), self.z.as_slice()),
309 ]
310 .into_par_iter()
311 }
312}
313
314#[derive(Clone, Debug, Decode, Deserialize, Encode, Eq, PartialEq, Serialize)]
316pub struct Germline {
317 pub species: Species,
319 pub name: Gene,
321 pub alleles: Vec<(usize, AnnotatedSequence, String)>,
323}
324
325impl<'a> IntoIterator for &'a Germline {
326 type IntoIter = std::slice::Iter<'a, (usize, AnnotatedSequence, String)>;
327 type Item = &'a (usize, AnnotatedSequence, String);
328
329 fn into_iter(self) -> Self::IntoIter {
330 self.alleles.iter()
331 }
332}
333
334impl Germline {
335 pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
337 self.into_iter()
338 }
339
340 pub fn select_allele(&self, allele: Option<usize>) -> Option<Allele<'_>> {
342 allele
343 .map_or_else(
344 || self.alleles.first(),
345 |allele| self.alleles.iter().find(|(i, ..)| *i == allele),
346 )
347 .map(|(i, sequence, acc)| Allele {
348 species: self.species,
349 gene: std::borrow::Cow::Borrowed(&self.name),
350 number: *i,
351 sequence: &sequence.sequence,
352 regions: &sequence.regions,
353 annotations: &sequence.annotations,
354 acc,
355 })
356 }
357}
358
359#[cfg(feature = "rayon")]
360impl<'a> IntoParallelIterator for &'a Germline {
361 type Item = &'a (usize, AnnotatedSequence, String);
362 type Iter = rayon::slice::Iter<'a, (usize, AnnotatedSequence, String)>;
363
364 fn into_par_iter(self) -> Self::Iter {
365 self.alleles.par_iter()
366 }
367}
368
369#[derive(Clone, Debug, Decode, Deserialize, Encode, Eq, PartialEq, Serialize)]
371pub struct AnnotatedSequence {
372 pub sequence: Peptidoform<UnAmbiguous>,
374 pub regions: Vec<(Region, usize)>,
376 pub annotations: Vec<(Annotation, usize)>,
379}
380
381impl AnnotatedSequence {
382 pub fn new(
384 sequence: Peptidoform<UnAmbiguous>,
385 regions: Vec<(Region, usize)>,
386 mut conserved: Vec<(Annotation, usize)>,
387 ) -> Self {
388 conserved.sort_unstable_by_key(|c| c.1);
389 Self {
390 sequence,
391 regions,
392 annotations: conserved,
393 }
394 }
395}
396
397#[derive(
399 Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, Hash, Decode, Encode,
400)]
401pub struct Gene {
402 pub chain: ChainType,
404 pub kind: GeneType,
406 pub number: Option<usize>,
408 pub family: Vec<(Option<usize>, String)>,
410}
411
412impl Display for Gene {
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 const fn to_roman(n: usize) -> &'static str {
415 [
416 "0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
417 ][n]
418 }
419
420 write!(
421 f,
422 "IG{}{}{}{}",
423 self.chain,
424 self.kind,
425 self.number
426 .as_ref()
427 .map_or_else(String::new, |n| format!("({})", to_roman(*n))),
428 if self.number.is_some() && !self.family.is_empty() {
429 "-"
430 } else {
431 ""
432 }
433 )?;
434
435 let mut first = true;
436 let mut last_str = false;
437 for element in &self.family {
438 if !first && !last_str {
439 write!(f, "-")?;
440 }
441 write!(
442 f,
443 "{}{}",
444 element.0.map(|i| i.to_string()).unwrap_or_default(),
445 element.1
446 )?;
447 last_str = !element.1.is_empty();
448 first = false;
449 }
450 Ok(())
451 }
452}
453
454impl Gene {
455 pub fn from_imgt_name_with_allele(s: &str) -> Result<(Self, usize), String> {
459 let mut recent_error = "Empty name".to_string();
460 for s in s.split(" or ") {
462 let s = s.trim_end_matches(" F");
463 let (gene, tail) = match Self::from_imgt_name_internal(s) {
464 Ok(v) => v,
465 Err(err) => {
466 recent_error = err;
467 continue;
468 }
469 };
470 if tail.is_empty() {
471 return Ok((gene, 1));
472 }
473 match tail.strip_prefix('*').map_or_else(
474 || Err(format!("Invalid allele spec: `{tail}`")),
475 |tail| tail.parse().map_err(|_| format!("Invalid allele spec: `{}`", &tail)),
476 ) {
477 Ok(allele) => return Ok((gene, allele)),
478 Err(err) => {
479 recent_error = err;
480 }
481 }
482 }
483 Err(recent_error)
484 }
485
486 pub fn from_imgt_name(s: &str) -> Result<Self, String> {
490 Self::from_imgt_name_internal(s).map(|(gene, _)| gene)
491 }
492
493 fn from_imgt_name_internal(s: &str) -> Result<(Self, &str), String> {
496 #[expect(clippy::missing_panics_doc)] fn parse_family_name(s: &str) -> (Option<(Option<usize>, String)>, &str) {
498 let num = s.chars().take_while(char::is_ascii_digit).collect::<String>();
499 let tail = s
500 .chars()
501 .skip(num.len())
502 .take_while(char::is_ascii_alphabetic)
503 .collect::<String>();
504 let rest = &s[num.len() + tail.len()..];
505 if num.is_empty() && tail.is_empty() {
506 return (None, s);
507 }
508 let num = if num.is_empty() {
509 None
510 } else {
511 Some(num.parse().unwrap())
512 };
513 (Some((num, tail)), rest)
514 }
515
516 fn from_roman(s: &str) -> Option<usize> {
517 match s {
518 "Ⅰ" | "I" => Some(1),
519 "Ⅱ" | "II" => Some(2),
520 "Ⅲ" | "III" => Some(3),
521 "Ⅳ" | "IV" => Some(4),
522 "Ⅴ" | "V" => Some(5),
523 "Ⅵ" | "VI" => Some(6),
524 "Ⅶ" | "VII" => Some(7),
525 "Ⅷ" | "VIII" => Some(8),
526 "Ⅸ" | "IX" => Some(9),
527 "Ⅹ" | "X" => Some(10),
528 _ => None,
529 }
530 }
531
532 if s.starts_with("IG") {
533 let chain = s[2..3].parse().map_err(|()| format!("Invalid chain: `{}`", &s[2..3]))?;
534 let gene = s[3..4].parse().map_err(|()| format!("Invalid gene: `{}`", &s[3..4]))?;
535 let mut start = 4;
536 let number = if s.len() > 4 && &s[4..5] == "(" {
537 let end = s[5..]
538 .find(')')
539 .ok_or_else(|| format!("Invalid gene number `{}` out of `{}`", &s[4..], s))?;
540 start += end + 2;
541 Some(from_roman(&s[5..5 + end]).ok_or_else(|| {
542 format!("Invalid roman numeral (or too big) `{}`", &s[5..5 + end])
543 })?)
544 } else {
545 None
546 };
547 let mut tail = s[start..].trim_start_matches('-');
548 let mut family = Vec::new();
549 while let (Some(branch), t) = parse_family_name(tail) {
550 family.push(branch);
551 tail = t.trim_start_matches('-');
552 }
553
554 Ok((
555 Self {
556 chain,
557 kind: gene,
558 number,
559 family,
560 },
561 tail,
562 ))
563 } else {
564 Err("Gene name does not start with IG")?
565 }
566 }
567}
568
569#[derive(
571 Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Decode, Encode,
572)]
573pub enum ChainType {
574 Heavy = 0,
576 LightKappa,
578 LightLambda,
580 Iota,
582}
583
584impl TryFrom<usize> for ChainType {
585 type Error = ();
586
587 fn try_from(i: usize) -> Result<Self, Self::Error> {
588 match i {
589 0 => Ok(Self::Heavy),
590 1 => Ok(Self::LightKappa),
591 2 => Ok(Self::LightLambda),
592 3 => Ok(Self::Iota),
593 _ => Err(()),
594 }
595 }
596}
597
598impl FromStr for ChainType {
599 type Err = ();
600
601 fn from_str(s: &str) -> Result<Self, Self::Err> {
602 match s {
603 "H" => Ok(Self::Heavy),
604 "κ" | "K" => Ok(Self::LightKappa),
605 "λ" | "L" => Ok(Self::LightLambda),
606 "ι" | "I" => Ok(Self::Iota),
607 _ => Err(()),
608 }
609 }
610}
611
612impl Display for ChainType {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614 write!(f, "{}", match self {
615 Self::Heavy => "H",
616 Self::LightKappa => "K",
617 Self::LightLambda => "L",
618 Self::Iota => "I",
619 })
620 }
621}
622
623#[derive(
625 Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Decode, Encode,
626)]
627pub enum GeneType {
628 V,
630 J,
632 C(Option<Constant>),
634}
635
636#[allow(missing_docs)]
638#[derive(
639 Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Decode, Encode,
640)]
641pub enum Constant {
642 A,
643 D,
644 E,
645 G,
646 M,
647 N,
648 O,
649 R,
650 T,
651 W,
652 Y,
653 Z,
654}
655
656impl FromStr for GeneType {
657 type Err = ();
658
659 fn from_str(s: &str) -> Result<Self, Self::Err> {
660 match s {
661 "V" => Ok(Self::V),
662 "J" => Ok(Self::J),
663 "C" => Ok(Self::C(None)),
664 "α" | "A" => Ok(Self::C(Some(Constant::A))),
665 "δ" | "D" => Ok(Self::C(Some(Constant::D))),
666 "ε" | "E" => Ok(Self::C(Some(Constant::E))),
667 "ɣ" | "G" => Ok(Self::C(Some(Constant::G))),
668 "μ" | "M" => Ok(Self::C(Some(Constant::M))),
669 "ν" | "N" => Ok(Self::C(Some(Constant::N))),
670 "ο" | "O" => Ok(Self::C(Some(Constant::O))),
671 "ρ" | "R" => Ok(Self::C(Some(Constant::R))),
672 "τ" | "T" => Ok(Self::C(Some(Constant::T))),
673 "ω" | "W" => Ok(Self::C(Some(Constant::W))),
674 "υ" | "Y" => Ok(Self::C(Some(Constant::Y))),
675 "ζ" | "Z" => Ok(Self::C(Some(Constant::Z))),
676 _ => Err(()),
677 }
678 }
679}
680
681impl Display for GeneType {
682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683 write!(f, "{}", match self {
684 Self::V => "V",
685 Self::J => "J",
686 Self::C(None) => "C",
687 Self::C(Some(Constant::A)) => "A",
688 Self::C(Some(Constant::D)) => "D",
689 Self::C(Some(Constant::E)) => "E",
690 Self::C(Some(Constant::G)) => "G",
691 Self::C(Some(Constant::M)) => "M",
692 Self::C(Some(Constant::N)) => "N",
693 Self::C(Some(Constant::O)) => "O",
694 Self::C(Some(Constant::R)) => "R",
695 Self::C(Some(Constant::T)) => "T",
696 Self::C(Some(Constant::W)) => "W",
697 Self::C(Some(Constant::Y)) => "Y",
698 Self::C(Some(Constant::Z)) => "Z",
699 })
700 }
701}
702
703#[expect(clippy::missing_panics_doc)]
704#[test]
705fn imgt_names() {
706 assert_eq!(
707 Gene::from_imgt_name_with_allele("IGHV3-23*03")
708 .map(|(g, a)| (g.to_string(), a))
709 .unwrap(),
710 ("IGHV3-23".to_string(), 3)
711 );
712 assert_eq!(
713 Gene::from_imgt_name_with_allele("IGKV6-d*01")
714 .map(|(g, a)| (g.to_string(), a))
715 .unwrap(),
716 ("IGKV6-d".to_string(), 1)
717 );
718}