Skip to content

Commit ae2980a

Browse files
committed
added multi-processing for final dimer solve
1 parent 5c6de3c commit ae2980a

File tree

5 files changed

+72
-22
lines changed

5 files changed

+72
-22
lines changed

docs/how_varvamp_works.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ To search for the best amplicon, varVAMP uses a graph based approach.
5656
7. Test amplicons for their [minimal free energy](https://en.wikipedia.org/wiki/Gibbs_free_energy) at their lowest primer temperature with [`seqfold`](https://github.com/Lattice-Automation/seqfold) and filter to avoid secondary structures. Amplicons with large potential deletions (>QAMPLICON_DEL_CUTOFF) will be ignored. Smaller deletions will be accepted.
5757
8. Take the best qPCR schemes of overlapping schemes.
5858

59+
60+
#### Multi-processing
61+
varVAMP can use multiple cores at some steps in the workflow. If you have performance issues
62+
at the following steps it might be worth increasing the number of cores.
63+
64+
1. All workflows:
65+
- BLAST search: Each amplicon is searched for off-targets in the BLAST database.
66+
- Primer dimer search against a user-provided list of primers.
67+
68+
2. Tiled workflow:
69+
- Final primer dimer solve.
70+
71+
3. qPCR workflow:
72+
- Amplicon deltaG calculation: Each amplicon is checked for potential secondary structures.
73+
5974
#### Penalty calculation
6075

6176
```python

varvamp/command.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def shared_workflow(args, log_file):
304304
progress_text=f"{len(left_primer_candidates)} fw and {len(right_primer_candidates)} rv potential primers"
305305
)
306306

307-
# filter primers against non-dimer sequences if provided, can use multi-processing
307+
# filter primers against user-provided list of compatible primers, can use multi-processing
308308
if compatible_primers is not None:
309309
left_primer_candidates = primers.filter_non_dimer_candidates(
310310
left_primer_candidates, compatible_primers, args.threads
@@ -433,7 +433,9 @@ def tiled_workflow(args, amplicons, left_primer_candidates, right_primer_candida
433433
amplicon_scheme,
434434
left_primer_candidates,
435435
right_primer_candidates,
436-
all_primers)
436+
all_primers,
437+
args.threads
438+
)
437439

438440
# report dimers solve
439441
if n_initial_dimers > 0 and not dimers_not_solved:

varvamp/scripts/default_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
# primer dimer constraints
3434
PRIMER_MAX_DIMER_TMP = 35 # max melting temp for dimers, lower temperature means more stringent filtering
3535
PRIMER_MAX_DIMER_DELTAG = -9000 # max allowed gibbs free energy for dimer formation, higher values mean more stringent filtering
36-
END_OVERLAP = 5 # maximum allowed nt overlap between primer ends
36+
END_OVERLAP = 5 # maximum allowed nt overlap between ends of primers to be considered a dimer
3737

3838
# QPCR parameters
3939
# basic probe parameters

varvamp/scripts/logging.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def confirm_config(args, log_file):
291291
"PRIMER_MAX_DINUC_REPEATS",
292292
"PRIMER_GC_END",
293293
"PRIMER_MAX_DIMER_TMP",
294+
"PRIMER_MAX_DIMER_DELTAG",
294295
"PRIMER_MIN_3_WITHOUT_AMB",
295296
"PCR_MV_CONC",
296297
"PCR_DV_CONC",
@@ -446,15 +447,20 @@ def confirm_config(args, log_file):
446447
)
447448
if config.PRIMER_HAIRPIN < 0:
448449
raise_error(
449-
"decreasing hairpin melting temp to negative values "
450-
"will influence successful primer search!",
450+
"decreasing hairpin melting temp to negative values will influence successful primer search!",
451451
log_file
452452
)
453-
if config.PRIMER_MAX_DIMER_TMP < 0:
453+
if config.PRIMER_MAX_DIMER_TMP < 21:
454454
raise_error(
455-
"there is no need to set max dimer melting temp below 0.",
455+
"there is no need to set max dimer melting temp below room temperature.",
456456
log_file
457457
)
458+
if config.PRIMER_MAX_DIMER_DELTAG > -6000:
459+
raise_error(
460+
"primer interactions with deltaG values higher than -6000 are generally considered unproblematic.",
461+
log_file
462+
)
463+
458464
if config.PRIMER_MAX_BASE_PENALTY < 8:
459465
raise_error(
460466
"decreasing the base penalty will filter out more primers.",
@@ -595,7 +601,6 @@ def goodbye_message():
595601
messages = [
596602
"Thank you. Come again.",
597603
">Placeholder for your advertisement<",
598-
"Make primers great again!",
599604
"Ciao cacao!",
600605
"And now lets pray to the PCR gods.",
601606
"**bibobibobop** task finished",
@@ -611,6 +616,19 @@ def goodbye_message():
611616
"Task failed successfully.",
612617
"Never gonna give you up, never gonna let you down.",
613618
"Have you tried turning it off and on again?",
619+
"Just try it. PCR is magic!",
620+
"One goat was sacrificed for this primer design to work.",
621+
"You seem trustworthy. Here's a cookie!",
622+
"Owwww yeah, primers done!",
623+
"These primers were designed without AI assistance.",
624+
"Research is fun (if you ignore the pipetting).",
625+
"Balance your primers, balance your life.",
626+
"Keep calm and PCR on.",
627+
"In silico we trust.",
628+
"May the primers be with you.",
629+
"Designing primers like a boss!",
630+
"Primer design completed. Time for a break!",
631+
"Eureka! Your primers are ready.",
614632
"Look, I am your primer scheme.",
615633
"Quod erat demonstrandum.",
616634
"Miau?",

varvamp/scripts/scheme.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# BUILT-INS
66
import heapq
77
import math
8+
from multiprocessing import Pool
89

910
# varVAMP
1011
from varvamp.scripts import config, primers
@@ -375,31 +376,45 @@ def test_overlaps_for_dimers(overlapping_primers, non_dimers):
375376
return [first_overlap, second_overlap]
376377

377378

378-
def check_and_solve_heterodimers(amplicon_scheme, left_primer_candidates, right_primer_candidates, all_primers):
379+
def _solve_single_dimer(args):
380+
"""
381+
Helper function for multiprocessing: solve a single dimer independently.
382+
Returns (amp_index, primer_name, new_primer) tuples or empty list if no solution.
383+
"""
384+
dimer, amplicon_scheme, left_primer_candidates, right_primer_candidates, non_dimers_all_pools = args
385+
pool = amplicon_scheme[dimer[0][0]]["pool"]
386+
non_dimers = non_dimers_all_pools[pool]
387+
388+
overlapping_primers = get_overlapping_primers(dimer, left_primer_candidates, right_primer_candidates)
389+
new_primers = test_overlaps_for_dimers(overlapping_primers, non_dimers)
390+
391+
return new_primers if new_primers else []
392+
393+
394+
def check_and_solve_heterodimers(amplicon_scheme, left_primer_candidates, right_primer_candidates, all_primers, num_processes):
379395
"""
380396
check scheme for heterodimers, try to find
381397
new primers that overlap and replace the existing ones.
382-
this can lead to new primer dimers. therefore the scheme
383-
is checked a second time. if there are still primer dimers
384-
present the non-solvable dimers are returned
398+
Uses multiprocessing to solve dimers in parallel.
385399
"""
386-
387400
primer_dimers, non_dimers_all_pools = test_scheme_for_dimers(amplicon_scheme)
388401
n_initial_dimers = len(primer_dimers)
389402

390403
if not primer_dimers:
391404
return [], []
392405

393-
for dimer in primer_dimers:
394-
# determine the pool this dimer belongs to (both primers are in the same pool)
395-
pool = amplicon_scheme[dimer[0][0]]["pool"]
396-
non_dimers = non_dimers_all_pools[pool]
406+
# Prepare arguments for each dimer
407+
args_list = [
408+
(dimer, amplicon_scheme, left_primer_candidates, right_primer_candidates, non_dimers_all_pools)
409+
for dimer in primer_dimers
410+
]
411+
412+
# Solve dimers in parallel
413+
with Pool(processes=num_processes) as pool:
414+
results = pool.map(_solve_single_dimer, args_list)
397415

398-
# get overlapping primers that have not been considered
399-
overlapping_primers = get_overlapping_primers(dimer, left_primer_candidates, right_primer_candidates)
400-
# test all possible primers against each other for dimers
401-
new_primers = test_overlaps_for_dimers(overlapping_primers, non_dimers)
402-
# now change these primers in the scheme
416+
# Apply all solutions to the scheme
417+
for new_primers in results:
403418
if new_primers:
404419
for amp_index, primer_name, primer in new_primers:
405420
# overwrite in final scheme

0 commit comments

Comments
 (0)