From 104ea3cc1e70b7708d9528b37291a01b6c57e426 Mon Sep 17 00:00:00 2001 From: Joshua Allen Date: Tue, 28 Apr 2026 10:21:51 -0500 Subject: [PATCH 1/2] =?UTF-8?q?Fix=20off-by-one=20in=20trinucleotide=20sli?= =?UTF-8?q?ce=20and=20remove=20stray=20SNV=E2=86=92all=5Fins=20append?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sample_trinucs returns the center (mutated) position, but the trinuc slice was starting at that position rather than one before it, so trinuc[1] was the 3' context base instead of the ref base. generate_snv uses trinuc[1] as the ref base for transition-matrix lookup and REF==ALT zeroing, so the wrong base was being consulted — Keshav's zeroing fix in #267 only worked when reference[pos] == reference[pos+1]. Fix: shift the slice to reference[local_location-1: local_location+2] so trinuc[0]=5' flank, trinuc[1]=ref base, trinuc[2]=3' flank. Also remove the dead self.all_ins.append(temp_snv) in generate_snv; SNVs were being accumulated in MutationModel.all_ins (a list that is never read) rather than nowhere, a copy-paste artifact from generate_deletion. Co-Authored-By: Claude Sonnet 4.6 --- neat/models/mutation_model.py | 1 - neat/read_simulator/utils/generate_variants.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/neat/models/mutation_model.py b/neat/models/mutation_model.py index 9c7a3074..3eb84774 100644 --- a/neat/models/mutation_model.py +++ b/neat/models/mutation_model.py @@ -125,7 +125,6 @@ def generate_snv(self, trinucleotide: Seq, reference_location: int, rng: Generat # Now pick a random alternate, weighted by the probabilities alt = rng.choice(ALLOWED_NUCL, p=transition_probs) temp_snv = SingleNucleotideVariant(reference_location, alt=alt) - self.all_ins.append(temp_snv) return temp_snv def generate_insertion(self, location: int, ref: Seq, rng: Generator) -> Insertion: diff --git a/neat/read_simulator/utils/generate_variants.py b/neat/read_simulator/utils/generate_variants.py index 4a29962b..6f3a5af6 100644 --- a/neat/read_simulator/utils/generate_variants.py +++ b/neat/read_simulator/utils/generate_variants.py @@ -238,6 +238,8 @@ def generate_variants( location = local_location + ref_start # relative to overall contig if local_location == 0: continue + # local_location is the center (mutated) base returned by sample_trinucs; + # shift slice left by 1 so trinuc[0]=5' flank, trinuc[1]=ref base, trinuc[2]=3' flank trinuc = reference[local_location-1: local_location+2].seq.upper() disallowed_chars = False for letter in trinuc: From c5b896f2e15cef9c2e012ebeb76608cd238dfd77 Mon Sep 17 00:00:00 2001 From: Joshua Allen Date: Sun, 3 May 2026 08:33:34 -0500 Subject: [PATCH 2/2] Remove orphaned all_ins and all_dels lists from MutationModel These lists were never read anywhere; ContigVariants has its own independent all_ins/all_dels used for overlap detection. Co-Authored-By: Claude Sonnet 4.6 --- neat/models/mutation_model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/neat/models/mutation_model.py b/neat/models/mutation_model.py index 3eb84774..ffc6ddcf 100644 --- a/neat/models/mutation_model.py +++ b/neat/models/mutation_model.py @@ -75,8 +75,6 @@ def __init__(self, self.variant_probs = variant_probs self.transition_matrix = transition_matrix self.is_cancer = is_cancer - self.all_dels = [] - self.all_ins = [] def get_mutation_type(self, rng: Generator) -> VariantTypes: """ @@ -157,5 +155,4 @@ def generate_deletion(self, location: int, rng: Generator) -> Deletion: # Plus one so we make sure to grab the first base too. # Note: if we happen to go past the end of the sequence, it will just be shorter. temp_del = Deletion(location, length) - self.all_dels.append(temp_del) return temp_del