From 01861c86d3615f984ca85782f6f8fc997c32f26e Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 01:23:23 +0200 Subject: [PATCH 1/7] Update load_fasta to return if the header has hairpin_left or hairpin_right --- badread/misc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/badread/misc.py b/badread/misc.py index f7e9d7e..9bcef9f 100644 --- a/badread/misc.py +++ b/badread/misc.py @@ -122,7 +122,7 @@ def load_fastq(filename, output=sys.stderr, dot_interval=1000): def load_fasta(filename): fasta_seqs = collections.OrderedDict() - depths, circular = {}, {} + depths, circular, hairpin_left, hairpin_right = {}, {}, {}, {} p = re.compile(r'depth=([\d.]+)') with get_open_func(filename)(filename, 'rt') as fasta_file: name = '' @@ -145,11 +145,13 @@ def load_fasta(filename): else: depths[short_name] = 1.0 circular[short_name] = 'circular=true' in name.lower() + hairpin_left[short_name] = 'hairpin_left=true' in name.lower() + hairpin_right[short_name] = 'hairpin_right=true' in name.lower() else: sequence.append(line) if name: fasta_seqs[name.split()[0]] = ''.join(sequence).upper() - return fasta_seqs, depths, circular + return fasta_seqs, depths, circular, hairpin_left, hairpin_right RANDOM_SEQ_DICT = {0: 'A', 1: 'C', 2: 'G', 3: 'T'} From 65115c60937bbe2910cb328b164f43cc5069cc53 Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:04:45 +0200 Subject: [PATCH 2/7] Add hairpin read-through reads --- badread/simulate.py | 65 ++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/badread/simulate.py b/badread/simulate.py index da7bc23..48a7217 100644 --- a/badread/simulate.py +++ b/badread/simulate.py @@ -34,7 +34,7 @@ def simulate(args, output=sys.stderr): if args.seed is not None: random.seed(args.seed) np.random.seed(args.seed) - ref_seqs, ref_depths, ref_circular = load_reference(args.reference, output) + ref_seqs, ref_depths, ref_circular, left_hairpin, right_hairpin = load_reference(args.reference, output) rev_comp_ref_seqs = {name: reverse_complement(seq) for name, seq in ref_seqs.items()} frag_lengths = FragmentLengths(args.mean_frag_length, args.frag_length_stdev, output) adjust_depths(ref_seqs, ref_depths, ref_circular, frag_lengths, args) @@ -89,7 +89,7 @@ def simulate(args, output=sys.stderr): def build_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_contig_weights, - ref_circular, args, start_adapt_rate, start_adapt_amount, end_adapt_rate, + ref_circular, left_hairpin, right_hairpin, args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount): fragment = [get_start_adapter(start_adapt_rate, start_adapt_amount, args.start_adapter_seq)] info = [] @@ -105,7 +105,7 @@ def build_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_c if random_chance(settings.CHIMERA_START_ADAPTER_CHANCE): fragment.append(args.start_adapter_seq) frag_seq, frag_info = get_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, - ref_contigs, ref_contig_weights, ref_circular, args) + ref_contigs, ref_contig_weights, ref_circular, left_hairpin, right_hairpin, args) fragment.append(frag_seq) info.append(','.join(frag_info)) fragment.append(get_end_adapter(end_adapt_rate, end_adapt_amount, args.end_adapter_seq)) @@ -146,7 +146,7 @@ def get_target_size(ref_size, quantity): def get_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_contig_weights, - ref_circular, args): + ref_circular, left_hairpin, right_hairpin args): fragment_length = frag_lengths.get_fragment_length() fragment_type = get_fragment_type(args) if fragment_type == 'junk': @@ -158,7 +158,7 @@ def get_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_con # repeatedly until we get a result. for _ in range(1000): seq, info = get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, - ref_contig_weights, ref_circular) + ref_contig_weights, ref_circular, left_hairpin, right_hairpin) if seq != '': return seq, info sys.exit('Error: failed to generate any sequence fragments - are your read lengths ' @@ -181,23 +181,31 @@ def get_fragment_type(args): def get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, - ref_contig_weights, ref_circular): + ref_contig_weights, ref_circular, left_hairpin, right_hairpin): if len(ref_contigs) == 1: contig = ref_contigs[0] else: contig = random.choices(ref_contigs, weights=ref_contig_weights)[0] + info = [contig] if random_chance(0.5): seq = ref_seqs[contig] + rv_seq = rev_comp_ref_seqs[contig] # save to use for hairpin info.append('+strand') + strand = '+' else: seq = rev_comp_ref_seqs[contig] + rv_seq = ref_seqs[contig] # save to use for hairpin info.append('-strand') + strand = '-' + + # is there a hairpin at the relevant end of the contig? + hairpin_at_end= (right_hairpin[contig] if strand == '+' else left_hairpin[contig]) # If the reference contig is linear and the fragment length is long enough, then we just # return the entire fragment, start to end. - if fragment_length >= len(seq) and not ref_circular[contig]: + if fragment_length >= len(seq) and not ref_circular[contig] and not hairpin_at_end: info.append('0-' + str(len(seq))) return seq, info @@ -209,13 +217,6 @@ def get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, start_pos = random.randint(0, len(seq)-1) end_pos = start_pos + fragment_length - # The ending position might be past the end of the sequence. If the sequence is linear, we - # fix this now. - if not ref_circular[contig] and end_pos > len(seq): - end_pos = len(seq) - - info.append(f'{start_pos}-{end_pos}') - # For circular contigs, we may have to loop the read around the contig. if ref_circular[contig]: if end_pos <= len(seq): @@ -223,11 +224,37 @@ def get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, else: looped_end_pos = end_pos - len(seq) assert looped_end_pos > 0 + + info.append(f'{start_pos}-{end_pos}') return seq[start_pos:] + seq[:looped_end_pos], info - # For linear contigs, no looping is needed. - else: - return seq[start_pos:end_pos], info + # The ending position might be past the end of the sequence. If the sequence is linear, we + # fix this now. + + if not ref_circular[contig] and end_pos > len(seq): + # If the read would extend past the end of a linear contigs with + # a hairpin at the end, we allow it to extend through the hairpin + # into the other strand (reverse complement) + if hairpin_at_end: + fwd_seq = seq[start_pos:] + left_over_bases = fragment_length - len(fwd_seq) + + # do not allow multiple hairpin loops + # as I have observed this in real data so far + if left_over_bases > len(rv_seq): + return '', '' # read would extend past the end of the other strand, so we fail to get the read + + if left_over_bases > 0: + hp_seq = rv_seq[:left_over_bases] + else: + hp_seq = '' + info.append(f'{start_pos}-{len(seq)} (hairpin) 0-{left_over_bases}') + return fwd_seq + hp_seq, info + + # If there is no hairpin terminate at contig end. + end_pos = len(seq) + info.append(f'{start_pos}-{end_pos}') + return seq[start_pos:end_pos], info def get_junk_fragment(fragment_length): @@ -478,7 +505,7 @@ def print_progress(count, bp, target, output): def load_reference(reference, output): print('', file=output) print(f'Loading reference from {reference}', file=output) - ref_seqs, ref_depths, ref_circular = load_fasta(reference) + ref_seqs, ref_depths, ref_circular, left_hairpin, right_hairpin = load_fasta(reference) plural = '' if len(ref_seqs) == 1 else 's' print(f' {len(ref_seqs):,} contig{plural}:', file=output) for contig in ref_seqs: @@ -488,7 +515,7 @@ def load_reference(reference, output): if len(ref_seqs) > 1: total_size = sum(len(s) for s in ref_seqs.values()) print(f' total size: {total_size:,} bp', file=output) - return ref_seqs, ref_depths, ref_circular + return ref_seqs, ref_depths, ref_circular, left_hairpin, right_hairpin def print_intro(output): From 22532db0c89c5290b81726a4227efaddff0589f2 Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:18:33 +0200 Subject: [PATCH 3/7] Update simulate to input left_hairpin and right_hairpin when calling build fragment --- badread/simulate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/badread/simulate.py b/badread/simulate.py index 48a7217..b43e5d2 100644 --- a/badread/simulate.py +++ b/badread/simulate.py @@ -62,8 +62,8 @@ def simulate(args, output=sys.stderr): print_progress(count, total_size, target_size, output) while total_size < target_size: fragment, info = build_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, - ref_contig_weights, ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, end_adapt_amount) + ref_contig_weights, ref_circular, left_hairpin, right_hairpin, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) target_identity = identities.get_identity() seq, quals, actual_identity, identity_by_qscores = \ sequence_fragment(fragment, target_identity, error_model, qscore_model) @@ -94,7 +94,7 @@ def build_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_c fragment = [get_start_adapter(start_adapt_rate, start_adapt_amount, args.start_adapter_seq)] info = [] frag_seq, frag_info = get_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, - ref_contigs, ref_contig_weights, ref_circular, args) + ref_contigs, ref_contig_weights, ref_circular, left_hairpin, right_hairpin, args) fragment.append(frag_seq) info.append(','.join(frag_info)) @@ -146,7 +146,7 @@ def get_target_size(ref_size, quantity): def get_fragment(frag_lengths, ref_seqs, rev_comp_ref_seqs, ref_contigs, ref_contig_weights, - ref_circular, left_hairpin, right_hairpin args): + ref_circular, left_hairpin, right_hairpin, args): fragment_length = frag_lengths.get_fragment_length() fragment_type = get_fragment_type(args) if fragment_type == 'junk': From df48603b3b58185e0b053aba9395d490ba954e37 Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:18:58 +0200 Subject: [PATCH 4/7] Add tests for hairpin read-through behaviour --- test/test_fragments.py | 99 ++++++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/test/test_fragments.py b/test/test_fragments.py index 75ca488..98ea209 100644 --- a/test/test_fragments.py +++ b/test/test_fragments.py @@ -38,6 +38,8 @@ def setUp(self): self.ref_contigs, self.ref_contig_weights = \ badread.simulate.get_ref_contig_weights(self.ref_seqs, self.ref_depths) self.trials = 100 + self.hairpin_left = {'r': False} + self.hairpin_right = {'r': False} def tearDown(self): self.null.close() @@ -59,8 +61,8 @@ def test_plain(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) if fragment in forward_ref: forward_count += 1 @@ -104,8 +106,8 @@ def test_full_adapters(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) self.assertTrue(fragment.startswith(args.start_adapter_seq)) self.assertTrue(fragment.endswith(args.end_adapter_seq)) @@ -131,8 +133,8 @@ def test_chimeras(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) lengths.append(len(fragment)) self.assertGreater(max(lengths), 1000) # Chimeras make for some longer fragments @@ -151,6 +153,8 @@ def setUp(self): self.ref_contigs, self.ref_contig_weights = \ badread.simulate.get_ref_contig_weights(self.ref_seqs, self.ref_depths) self.trials = 100 + self.hairpin_left = {'r': False} + self.hairpin_right = {'r': False} def tearDown(self): self.null.close() @@ -172,8 +176,8 @@ def test_plain(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) if fragment in forward_ref: forward_count += 1 @@ -201,8 +205,8 @@ def test_full_adapters(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) self.assertTrue(fragment.startswith(args.start_adapter_seq)) self.assertTrue(fragment.endswith(args.end_adapter_seq)) @@ -224,8 +228,8 @@ def test_chimeras(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) lengths.append(len(fragment)) self.assertTrue(len(fragment) % 1000 == 0) @@ -246,8 +250,8 @@ def test_enlarging_glitches(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) lengths.append(len(fragment)) self.assertGreater(statistics.mean(lengths), 1000) @@ -267,8 +271,8 @@ def test_shrinking_glitches(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) lengths.append(len(fragment)) self.assertLess(statistics.mean(lengths), 1000) @@ -289,8 +293,8 @@ def test_junk_and_random(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) if fragment in forward_ref: forward_count += 1 @@ -318,6 +322,8 @@ def setUp(self): self.ref_contigs, self.ref_contig_weights = \ badread.simulate.get_ref_contig_weights(self.ref_seqs, self.ref_depths) self.trials = 100 + self.hairpin_left = {'r': False} + self.hairpin_right = {'r': False} def tearDown(self): self.null.close() @@ -338,8 +344,8 @@ def test_bias_on(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) self.assertEqual(len(fragment), 1000) @@ -358,6 +364,8 @@ def setUp(self): self.ref_contigs, self.ref_contig_weights = \ badread.simulate.get_ref_contig_weights(self.ref_seqs, self.ref_depths) self.trials = 100 + self.hairpin_left = {'r': False} + self.hairpin_right = {'r': False} def tearDown(self): self.null.close() @@ -377,8 +385,8 @@ def test_whole_ref(self): fragment, info = \ badread.simulate.build_fragment(self.lengths, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, self.ref_contig_weights, - self.ref_circular, args, start_adapt_rate, - start_adapt_amount, end_adapt_rate, + self.ref_circular, self.hairpin_left, self.hairpin_right, + args, start_adapt_rate, start_adapt_amount, end_adapt_rate, end_adapt_amount) self.assertEqual(len(fragment), 1000) if fragment == self.ref_seqs['r']: @@ -411,3 +419,50 @@ def test_junk(self): random_seq = badread.simulate.get_junk_fragment(self.seq_len) compressed_seq = zlib.compress(random_seq.encode()) self.assertLess(len(compressed_seq), len(random_seq) / 10) + +class TestHairpinReadthrough(unittest.TestCase): + + def setUp(self): + self.null = open(os.devnull, 'w') + self.ref_len = 1000 + self.ref_seqs = {'r': badread.misc.get_random_sequence(self.ref_len)} + self.rev_comp_ref_seqs = {'r': badread.misc.reverse_complement(self.ref_seqs['r'])} + self.ref_depths = {'r': 1.0} + self.ref_circular = {'r': False} + self.left_hairpin = {'r': True} + self.right_hairpin = {'r': True} + self.ref_contigs, self.ref_contig_weights = badread.simulate.get_ref_contig_weights( + self.ref_seqs, self.ref_depths) + self.trials = 100 + + def tearDown(self): + self.null.close() + + def test_readthrough_happens(self): + # fragment > contig guarantees overrun on linear contig + frag_len = 1001 # always leads to tread through, but does not lead to multi-loop readthrough + lengths = badread.fragment_lengths.FragmentLengths(frag_len, 0, self.null) + + for _ in range(self.trials): + seq, info = badread.simulate.get_real_fragment( + frag_len, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, + self.ref_contig_weights, self.ref_circular, self.left_hairpin, + self.right_hairpin + ) + self.assertNotEqual(seq, '') + self.assertEqual(len(seq), frag_len) + self.assertTrue(any('hairpin' in x for x in info)) + + def test_no_multi_loop_readthrough(self): + # In the current implementation only one hairpin can be read-through + # leftover always > opposite strand length, so function must reject + frag_len = 2500 + + for _ in range(self.trials): + seq, info = badread.simulate.get_real_fragment( + frag_len, self.ref_seqs, self.rev_comp_ref_seqs, self.ref_contigs, + self.ref_contig_weights, self.ref_circular, self.left_hairpin, + self.right_hairpin + ) + self.assertEqual(seq, '') + self.assertEqual(info, '') \ No newline at end of file From a36dd527d48fb86c6347101ec33b397506f4ce5d Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:33:58 +0200 Subject: [PATCH 5/7] Update tests and calls to functions that were altered --- badread/error_model.py | 2 +- badread/qscore_model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/badread/error_model.py b/badread/error_model.py index 1ffbcc4..1e7ac01 100755 --- a/badread/error_model.py +++ b/badread/error_model.py @@ -29,7 +29,7 @@ def make_error_model(args, output=sys.stderr, dot_interval=1000): - refs, _, _ = load_fasta(args.reference) + refs, _, _,_,_ = load_fasta(args.reference) reads = load_fastq(args.reads, output=output) alignments = load_alignments(args.alignment, args.max_alignments, output=output) if len(alignments) == 0: diff --git a/badread/qscore_model.py b/badread/qscore_model.py index 3e0af98..f0afc79 100755 --- a/badread/qscore_model.py +++ b/badread/qscore_model.py @@ -76,7 +76,7 @@ def get_qscores(seq, frag, qscore_model): def make_qscore_model(args, output=sys.stderr, dot_interval=1000): - refs, _, _ = load_fasta(args.reference) + refs, _, _,_,_ = load_fasta(args.reference) reads = load_fastq(args.reads, output=output) alignments = load_alignments(args.alignment, args.max_alignments, output=output) if len(alignments) == 0: From bb4aba50a540c4ddb12910b108a399c7b14e082d Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:44:14 +0200 Subject: [PATCH 6/7] update get_real_fragment to always return positions --- badread/plot_window_identity.py | 2 +- badread/simulate.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/badread/plot_window_identity.py b/badread/plot_window_identity.py index 9a39821..096de1d 100755 --- a/badread/plot_window_identity.py +++ b/badread/plot_window_identity.py @@ -26,7 +26,7 @@ def plot_window_identity(args, output=sys.stdout): reads = load_fastq(args.reads, output=output) - refs, _, _ = load_fasta(args.reference) + refs, _, _,_,_ = load_fasta(args.reference) alignments = load_alignments(args.alignment, output=output) for a in alignments: diff --git a/badread/simulate.py b/badread/simulate.py index b43e5d2..90523f1 100644 --- a/badread/simulate.py +++ b/badread/simulate.py @@ -219,13 +219,13 @@ def get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, # For circular contigs, we may have to loop the read around the contig. if ref_circular[contig]: + info.append(f'{start_pos}-{end_pos}') if end_pos <= len(seq): return seq[start_pos:end_pos], info else: looped_end_pos = end_pos - len(seq) assert looped_end_pos > 0 - info.append(f'{start_pos}-{end_pos}') return seq[start_pos:] + seq[:looped_end_pos], info # The ending position might be past the end of the sequence. If the sequence is linear, we @@ -253,7 +253,7 @@ def get_real_fragment(fragment_length, ref_seqs, rev_comp_ref_seqs, ref_contigs, # If there is no hairpin terminate at contig end. end_pos = len(seq) - info.append(f'{start_pos}-{end_pos}') + info.append(f'{start_pos}-{end_pos}') return seq[start_pos:end_pos], info From 224b62571434b623002dd4b88a34758ab99ead74 Mon Sep 17 00:00:00 2001 From: dalofa Date: Wed, 1 Apr 2026 02:45:00 +0200 Subject: [PATCH 7/7] update tests to use update function calls --- test/test_misc.py | 6 +++--- test/test_references.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_misc.py b/test/test_misc.py index 9fb15a8..2f399d5 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -146,17 +146,17 @@ def check_fastq(self, reads): def test_load_non_gzipped_fasta(self): filename = os.path.join(os.path.dirname(__file__), 'test_ref_1.fasta') - seqs, depths, circular = badread.misc.load_fasta(filename) + seqs, depths, circular, _, _ = badread.misc.load_fasta(filename) self.check_fasta(seqs, depths, circular) def test_load_gzipped_fasta(self): filename = os.path.join(os.path.dirname(__file__), 'test_ref_1.fasta.gz') - seqs, depths, circular = badread.misc.load_fasta(filename) + seqs, depths, circular,_,_ = badread.misc.load_fasta(filename) self.check_fasta(seqs, depths, circular) def test_load_bad_format_fasta(self): filename = os.path.join(os.path.dirname(__file__), 'test_ref_1_bad.fasta') - seqs, depths, circular = badread.misc.load_fasta(filename) + seqs, depths, circular, _, _ = badread.misc.load_fasta(filename) self.check_fasta(seqs, depths, circular) def test_load_fastq(self): diff --git a/test/test_references.py b/test/test_references.py index 154e884..3660354 100644 --- a/test/test_references.py +++ b/test/test_references.py @@ -25,7 +25,7 @@ class TestLinearFragments(unittest.TestCase): def setUp(self): null = open(os.devnull, 'w') ref_filename = os.path.join(os.path.dirname(__file__), 'test_ref_1.fasta') - self.ref_seqs, self.ref_depths, self.ref_circular = \ + self.ref_seqs, self.ref_depths, self.ref_circular, _, _ = \ badread.simulate.load_reference(ref_filename, output=null) null.close()