use crate::error::FerroError;
use crate::hgvs::variant::HgvsVariant;
use crate::normalize::{NormalizeConfig, Normalizer};
use crate::reference::ReferenceProvider;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EquivalenceLevel {
Identical,
NormalizedMatch,
AccessionVersionDifference,
NotEquivalent,
}
impl EquivalenceLevel {
pub fn is_equivalent(&self) -> bool {
!matches!(self, EquivalenceLevel::NotEquivalent)
}
pub fn description(&self) -> &'static str {
match self {
EquivalenceLevel::Identical => "Identical representation",
EquivalenceLevel::NormalizedMatch => "Equivalent after normalization",
EquivalenceLevel::AccessionVersionDifference => {
"Same variant, different accession versions"
}
EquivalenceLevel::NotEquivalent => "Not equivalent",
}
}
}
impl std::fmt::Display for EquivalenceLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description())
}
}
#[derive(Debug, Clone)]
pub struct EquivalenceResult {
pub level: EquivalenceLevel,
pub normalized_first: Option<String>,
pub normalized_second: Option<String>,
pub notes: Vec<String>,
}
impl EquivalenceResult {
pub fn new(level: EquivalenceLevel) -> Self {
Self {
level,
normalized_first: None,
normalized_second: None,
notes: Vec::new(),
}
}
pub fn with_normalized(mut self, first: String, second: String) -> Self {
self.normalized_first = Some(first);
self.normalized_second = Some(second);
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn is_equivalent(&self) -> bool {
self.level.is_equivalent()
}
}
pub struct EquivalenceChecker<P: ReferenceProvider> {
normalizer: Normalizer<P>,
}
impl<P: ReferenceProvider> EquivalenceChecker<P> {
pub fn new(provider: P) -> Self {
Self {
normalizer: Normalizer::new(provider),
}
}
pub fn with_config(provider: P, config: NormalizeConfig) -> Self {
Self {
normalizer: Normalizer::with_config(provider, config),
}
}
pub fn check(
&self,
v1: &HgvsVariant,
v2: &HgvsVariant,
) -> Result<EquivalenceResult, FerroError> {
let str1 = v1.to_string();
let str2 = v2.to_string();
if str1 == str2 {
return Ok(EquivalenceResult::new(EquivalenceLevel::Identical)
.with_normalized(str1.clone(), str2.clone()));
}
if let Some(result) = self.check_accession_version_difference(v1, v2, &str1, &str2) {
return Ok(result);
}
let norm1 = self.normalizer.normalize(v1)?;
let norm2 = self.normalizer.normalize(v2)?;
let norm_str1 = norm1.to_string();
let norm_str2 = norm2.to_string();
if norm_str1 == norm_str2 {
Ok(EquivalenceResult::new(EquivalenceLevel::NormalizedMatch)
.with_normalized(norm_str1, norm_str2)
.with_note("Variants normalize to the same form"))
} else {
if let Some(result) =
self.check_accession_version_difference(&norm1, &norm2, &norm_str1, &norm_str2)
{
return Ok(result.with_note("Equivalent after normalization, different versions"));
}
Ok(EquivalenceResult::new(EquivalenceLevel::NotEquivalent)
.with_normalized(norm_str1, norm_str2)
.with_note("Variants do not normalize to the same form"))
}
}
fn check_accession_version_difference(
&self,
v1: &HgvsVariant,
v2: &HgvsVariant,
str1: &str,
str2: &str,
) -> Option<EquivalenceResult> {
let acc1 = match v1 {
HgvsVariant::NullAllele | HgvsVariant::UnknownAllele => return None,
HgvsVariant::Allele(_) => return None, _ => v1.accession()?,
};
let acc2 = match v2 {
HgvsVariant::NullAllele | HgvsVariant::UnknownAllele => return None,
HgvsVariant::Allele(_) => return None,
_ => v2.accession()?,
};
if acc1.prefix == acc2.prefix && acc1.number == acc2.number && acc1.version != acc2.version
{
let variant_part1 = extract_variant_part(str1);
let variant_part2 = extract_variant_part(str2);
if variant_part1 == variant_part2 {
return Some(
EquivalenceResult::new(EquivalenceLevel::AccessionVersionDifference)
.with_normalized(str1.to_string(), str2.to_string())
.with_note(format!(
"Same variant on different versions: {} vs {}",
acc1.version
.map(|v| v.to_string())
.unwrap_or_else(|| "no version".to_string()),
acc2.version
.map(|v| v.to_string())
.unwrap_or_else(|| "no version".to_string())
)),
);
}
}
None
}
pub fn all_equivalent(&self, variants: &[HgvsVariant]) -> Result<bool, FerroError> {
if variants.len() < 2 {
return Ok(true);
}
let first = &variants[0];
for variant in &variants[1..] {
let result = self.check(first, variant)?;
if !result.is_equivalent() {
return Ok(false);
}
}
Ok(true)
}
pub fn group_by_equivalence(
&self,
variants: &[HgvsVariant],
) -> Result<Vec<Vec<HgvsVariant>>, FerroError> {
let mut groups: Vec<Vec<HgvsVariant>> = Vec::new();
for variant in variants {
let mut found_group = false;
for group in &mut groups {
if !group.is_empty() {
let result = self.check(&group[0], variant)?;
if result.is_equivalent() {
group.push(variant.clone());
found_group = true;
break;
}
}
}
if !found_group {
groups.push(vec![variant.clone()]);
}
}
Ok(groups)
}
}
fn extract_variant_part(hgvs: &str) -> &str {
if let Some(pos) = hgvs.find(':') {
&hgvs[pos..]
} else {
hgvs
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hgvs::parser::parse_hgvs;
use crate::reference::MockProvider;
fn checker() -> EquivalenceChecker<MockProvider> {
EquivalenceChecker::new(MockProvider::with_test_data())
}
#[test]
fn test_identical_variants() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::Identical);
assert!(result.is_equivalent());
}
#[test]
fn test_different_variants() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.20A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
assert!(!result.is_equivalent());
}
#[test]
fn test_equivalence_level_is_equivalent() {
assert!(EquivalenceLevel::Identical.is_equivalent());
assert!(EquivalenceLevel::NormalizedMatch.is_equivalent());
assert!(EquivalenceLevel::AccessionVersionDifference.is_equivalent());
assert!(!EquivalenceLevel::NotEquivalent.is_equivalent());
}
#[test]
fn test_equivalence_level_description() {
assert!(!EquivalenceLevel::Identical.description().is_empty());
assert!(!EquivalenceLevel::NormalizedMatch.description().is_empty());
assert!(!EquivalenceLevel::AccessionVersionDifference
.description()
.is_empty());
assert!(!EquivalenceLevel::NotEquivalent.description().is_empty());
}
#[test]
fn test_equivalence_level_display() {
let level = EquivalenceLevel::Identical;
assert_eq!(level.to_string(), level.description());
}
#[test]
fn test_all_equivalent_empty() {
let checker = checker();
assert!(checker.all_equivalent(&[]).unwrap());
}
#[test]
fn test_all_equivalent_single() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
assert!(checker.all_equivalent(&[v1]).unwrap());
}
#[test]
fn test_all_equivalent_same() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v3 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
assert!(checker.all_equivalent(&[v1, v2, v3]).unwrap());
}
#[test]
fn test_all_equivalent_different() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.20A>G").unwrap();
assert!(!checker.all_equivalent(&[v1, v2]).unwrap());
}
#[test]
fn test_group_by_equivalence() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v3 = parse_hgvs("NM_000088.3:c.20A>G").unwrap();
let groups = checker.group_by_equivalence(&[v1, v2, v3]).unwrap();
assert_eq!(groups.len(), 2);
assert!(groups.iter().any(|g| g.len() == 2));
assert!(groups.iter().any(|g| g.len() == 1));
}
#[test]
fn test_extract_variant_part() {
assert_eq!(extract_variant_part("NM_000088.3:c.10A>G"), ":c.10A>G");
assert_eq!(extract_variant_part("no_colon"), "no_colon");
}
#[test]
fn test_equivalence_result_with_note() {
let result = EquivalenceResult::new(EquivalenceLevel::NormalizedMatch)
.with_note("Test note 1")
.with_note("Test note 2");
assert_eq!(result.notes.len(), 2);
assert_eq!(result.notes[0], "Test note 1");
assert_eq!(result.notes[1], "Test note 2");
}
#[test]
fn test_substitution_at_different_positions() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.11A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
}
#[test]
fn test_genomic_variants_identical() {
let checker = checker();
let v1 = parse_hgvs("NC_000001.11:g.12345A>G").unwrap();
let v2 = parse_hgvs("NC_000001.11:g.12345A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::Identical);
}
#[test]
fn test_genomic_variants_different() {
let checker = checker();
let v1 = parse_hgvs("NC_000001.11:g.12345A>G").unwrap();
let v2 = parse_hgvs("NC_000001.11:g.12346A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
}
#[test]
fn test_protein_variants_identical() {
let checker = checker();
let v1 = parse_hgvs("NP_000079.2:p.Val600Glu").unwrap();
let v2 = parse_hgvs("NP_000079.2:p.Val600Glu").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::Identical);
}
#[test]
fn test_deletion_variants() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10del").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10del").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::Identical);
}
#[test]
fn test_accession_version_difference_same_variant() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.4:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
assert!(result.is_equivalent());
assert!(result.notes.iter().any(|n| n.contains("version")));
}
#[test]
fn test_accession_version_difference_detected_before_normalize() {
let checker = checker();
let v1 = parse_hgvs("NC_000001.10:g.12345A>G").unwrap();
let v2 = parse_hgvs("NC_000001.11:g.12345A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_accession_version_difference_with_deletion() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10_12del").unwrap();
let v2 = parse_hgvs("NM_000088.4:c.10_12del").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_accession_version_difference_with_insertion() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10_11insATG").unwrap();
let v2 = parse_hgvs("NM_000088.4:c.10_11insATG").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_accession_version_different_variant_not_equivalent() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.4:c.20A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
assert!(!result.is_equivalent());
}
#[test]
fn test_different_accessions_entirely_not_equivalent() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_001234.1:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
}
#[test]
fn test_protein_accession_version_difference() {
let checker = checker();
let v1 = parse_hgvs("NP_000079.1:p.Val600Glu").unwrap();
let v2 = parse_hgvs("NP_000079.2:p.Val600Glu").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_no_version_vs_versioned() {
let checker = checker();
let v1 = parse_hgvs("NM_000088:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_version_difference_result_has_normalized_forms() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.4:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert!(result.normalized_first.is_some());
assert!(result.normalized_second.is_some());
}
#[test]
fn test_genomic_version_difference_grch37_vs_grch38() {
let checker = checker();
let v1 = parse_hgvs("NC_000001.10:g.100000A>G").unwrap();
let v2 = parse_hgvs("NC_000001.11:g.100000A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_version_difference_note_contains_versions() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.5:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
let has_version_note = result
.notes
.iter()
.any(|n| n.contains("3") && n.contains("5"));
assert!(has_version_note, "Notes should contain version numbers");
}
#[test]
fn test_multiple_variants_with_version_mix() {
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v3 = parse_hgvs("NM_000088.4:c.10A>G").unwrap();
assert!(checker
.all_equivalent(&[v1.clone(), v2.clone(), v3.clone()])
.unwrap());
let groups = checker.group_by_equivalence(&[v1, v2, v3]).unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 3);
}
#[test]
fn test_lrg_accession_version_difference() {
let checker = checker();
let v1 = parse_hgvs("LRG_1t1:c.10A>G").unwrap();
let v2 = parse_hgvs("LRG_1t2:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::NotEquivalent);
}
#[test]
fn test_ensembl_accession_version_difference() {
let checker = checker();
let v1 = parse_hgvs("ENST00000123456.1:c.10A>G").unwrap();
let v2 = parse_hgvs("ENST00000123456.2:c.10A>G").unwrap();
let result = checker.check(&v1, &v2).unwrap();
assert_eq!(result.level, EquivalenceLevel::AccessionVersionDifference);
}
#[test]
fn test_grouping_100_identical_variants() {
let checker = checker();
let variants: Vec<HgvsVariant> = (0..100)
.map(|_| parse_hgvs("NM_000088.3:c.10A>G").unwrap())
.collect();
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 100);
}
#[test]
fn test_grouping_100_unique_variants() {
let checker = checker();
let variants: Vec<HgvsVariant> = (1..=100)
.map(|i| parse_hgvs(&format!("NM_000088.3:c.{}A>G", i)).unwrap())
.collect();
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 100);
for group in &groups {
assert_eq!(group.len(), 1);
}
}
#[test]
fn test_grouping_mixed_identical_and_unique() {
let checker = checker();
let mut variants: Vec<HgvsVariant> = Vec::with_capacity(100);
for _ in 0..50 {
variants.push(parse_hgvs("NM_000088.3:c.10A>G").unwrap());
}
for _ in 0..50 {
variants.push(parse_hgvs("NM_000088.3:c.20C>T").unwrap());
}
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 2);
let sizes: Vec<usize> = groups.iter().map(|g| g.len()).collect();
assert!(sizes.contains(&50));
assert_eq!(sizes.iter().sum::<usize>(), 100);
}
#[test]
fn test_grouping_interleaved_variants() {
let checker = checker();
let mut variants: Vec<HgvsVariant> = Vec::with_capacity(60);
for i in 0..20 {
variants.push(parse_hgvs(&format!("NM_000088.3:c.{}A>G", 10)).unwrap());
variants.push(parse_hgvs(&format!("NM_000088.3:c.{}A>G", 20)).unwrap());
variants.push(parse_hgvs(&format!("NM_000088.3:c.{}A>G", 30)).unwrap());
let _ = i; }
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 3);
for group in &groups {
assert_eq!(group.len(), 20);
}
}
#[test]
fn test_grouping_accession_version_differences() {
let checker = checker();
let mut variants: Vec<HgvsVariant> = Vec::with_capacity(40);
for _ in 0..20 {
variants.push(parse_hgvs("NM_000088.3:c.10A>G").unwrap());
}
for _ in 0..20 {
variants.push(parse_hgvs("NM_000088.4:c.10A>G").unwrap());
}
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 40);
}
#[test]
fn test_grouping_diverse_variant_types() {
let checker = checker();
let variant_strs = [
"NM_000088.3:c.10A>G", "NM_000088.3:c.20del", "NM_000088.3:c.30_31insATG", "NM_000088.3:c.40dup", "NC_000001.11:g.12345A>G", ];
let variants: Vec<HgvsVariant> = variant_strs
.iter()
.map(|s| parse_hgvs(s).unwrap())
.collect();
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 5);
}
#[test]
fn test_grouping_empty_input() {
let checker = checker();
let variants: Vec<HgvsVariant> = vec![];
let groups = checker.group_by_equivalence(&variants).unwrap();
assert!(groups.is_empty());
}
#[test]
fn test_grouping_single_variant() {
let checker = checker();
let variants = vec![parse_hgvs("NM_000088.3:c.10A>G").unwrap()];
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 1);
}
#[test]
fn test_grouping_performance_200_variants() {
use std::time::Instant;
let checker = checker();
let mut variants: Vec<HgvsVariant> = Vec::with_capacity(200);
for pos in (1..=10).map(|x| x * 10) {
for _ in 0..20 {
variants.push(parse_hgvs(&format!("NM_000088.3:c.{}A>G", pos)).unwrap());
}
}
let start = Instant::now();
let groups = checker.group_by_equivalence(&variants).unwrap();
let duration = start.elapsed();
assert_eq!(groups.len(), 10);
for group in &groups {
assert_eq!(group.len(), 20);
}
assert!(
duration.as_secs() < 5,
"Grouping took too long: {:?}",
duration
);
}
#[test]
fn test_all_equivalent_performance_100() {
use std::time::Instant;
let checker = checker();
let variants: Vec<HgvsVariant> = (0..100)
.map(|_| parse_hgvs("NM_000088.3:c.10A>G").unwrap())
.collect();
let start = Instant::now();
let result = checker.all_equivalent(&variants).unwrap();
let duration = start.elapsed();
assert!(result);
assert!(
duration.as_secs() < 2,
"all_equivalent took too long: {:?}",
duration
);
}
#[test]
fn test_all_equivalent_early_exit_on_non_equivalent() {
use std::time::Instant;
let checker = checker();
let mut variants: Vec<HgvsVariant> = Vec::with_capacity(100);
variants.push(parse_hgvs("NM_000088.3:c.10A>G").unwrap());
variants.push(parse_hgvs("NM_000088.3:c.20A>G").unwrap());
for _ in 0..98 {
variants.push(parse_hgvs("NM_000088.3:c.10A>G").unwrap());
}
let start = Instant::now();
let result = checker.all_equivalent(&variants).unwrap();
let duration = start.elapsed();
assert!(!result);
assert!(
duration.as_millis() < 1000,
"all_equivalent should exit early"
);
}
#[test]
fn test_pairwise_check_performance() {
use std::time::Instant;
let checker = checker();
let v1 = parse_hgvs("NM_000088.3:c.10A>G").unwrap();
let v2 = parse_hgvs("NM_000088.3:c.20A>G").unwrap();
let start = Instant::now();
for _ in 0..1000 {
let _ = checker.check(&v1, &v2).unwrap();
}
let duration = start.elapsed();
assert!(
duration.as_secs() < 2,
"Pairwise checks too slow: {:?}",
duration
);
}
#[test]
fn test_grouping_stability_same_results() {
let checker = checker();
let variants: Vec<HgvsVariant> = vec![
parse_hgvs("NM_000088.3:c.10A>G").unwrap(),
parse_hgvs("NM_000088.3:c.20A>G").unwrap(),
parse_hgvs("NM_000088.3:c.10A>G").unwrap(),
parse_hgvs("NM_000088.3:c.30A>G").unwrap(),
parse_hgvs("NM_000088.3:c.20A>G").unwrap(),
];
let groups1 = checker.group_by_equivalence(&variants).unwrap();
let groups2 = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups1.len(), groups2.len());
let mut sizes1: Vec<_> = groups1.iter().map(|g| g.len()).collect();
let mut sizes2: Vec<_> = groups2.iter().map(|g| g.len()).collect();
sizes1.sort();
sizes2.sort();
assert_eq!(sizes1, sizes2);
}
#[test]
fn test_grouping_preserves_variant_order_in_groups() {
let checker = checker();
let variants: Vec<HgvsVariant> = vec![
parse_hgvs("NM_000088.3:c.10A>G").unwrap(), parse_hgvs("NM_000088.3:c.10A>G").unwrap(), parse_hgvs("NM_000088.3:c.10A>G").unwrap(), ];
let groups = checker.group_by_equivalence(&variants).unwrap();
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].len(), 3);
for v in &groups[0] {
assert_eq!(v.to_string(), "NM_000088.3:c.10A>G");
}
}
}