From 6d36294e20d7f44aaed8dda3e30cf7ccb0052f5b Mon Sep 17 00:00:00 2001 From: Maksim Filippov Date: Wed, 18 Mar 2026 16:31:39 +0000 Subject: [PATCH 1/2] Fix pvacvector no-edge path scoring crash. Handle single-peptide runs without junction edges by emitting stable NA score metadata instead of dividing by zero, and add a regression test to prevent future crashes. --- pvactools/tools/pvacvector/run.py | 12 +++++++++--- tests/test_pvacvector.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pvactools/tools/pvacvector/run.py b/pvactools/tools/pvacvector/run.py index 1b5ea2a40..31c2e14b5 100644 --- a/pvactools/tools/pvacvector/run.py +++ b/pvactools/tools/pvacvector/run.py @@ -313,16 +313,22 @@ def find_optimal_path(graph, distance_matrix, seq_dict, base_output_dir, junctio for id in state: print("\t", id) - median_score = str(cumulative_weight/len(all_scores)) + if len(all_scores) > 0: + median_score = str(cumulative_weight/len(all_scores)) + lowest_score = str(min_score) + score_list = ','.join(all_scores) + else: + median_score = 'NA' + lowest_score = 'NA' + score_list = 'NA' peptide_id_list = ','.join(names) - score_list = ','.join(all_scores) output = list() output.append(">") output.append(peptide_id_list) output.append("|Median_Junction_Score:") output.append(median_score) output.append("|Lowest_Junction_Score:") - output.append(str(min_score)) + output.append(lowest_score) output.append("|All_Junction_Scores:") output.append(score_list) output.append("\n") diff --git a/tests/test_pvacvector.py b/tests/test_pvacvector.py index 62b2a50d0..37f996f92 100644 --- a/tests/test_pvacvector.py +++ b/tests/test_pvacvector.py @@ -464,6 +464,36 @@ def test_pvacvector_remove_peptides(self): output_dir.cleanup() + def test_find_optimal_path_single_peptide_without_edges_does_not_crash(self): + with tempfile.TemporaryDirectory() as output_dir: + seq_dict = {"MT.TEST.1": "SYFPEITHI"} + graph = run.initialize_graph(seq_dict.keys()) + distance_matrix = run.create_distance_matrix(graph) + junctions_file = run.write_junctions_file(graph, output_dir) + args = argparse.Namespace( + spacers=["None"], + sample_name="single_peptide_no_edges", + ) + + results_file, error = run.find_optimal_path( + graph, + distance_matrix, + seq_dict, + output_dir, + junctions_file, + args, + ) + + self.assertIsNone(error) + self.assertIsNotNone(results_file) + self.assertTrue(os.path.exists(results_file)) + with open(results_file, "r") as fh: + contents = fh.read() + self.assertIn("|Median_Junction_Score:", contents) + self.assertIn("|Lowest_Junction_Score:", contents) + self.assertIn("|All_Junction_Scores:", contents) + self.assertIn("SYFPEITHI", contents) + def test_prevent_clipping_best_peptide(self): output_dir = tempfile.TemporaryDirectory() input_file = os.path.join(self.test_data_dir, 'Test.vector.prevent_clipping_best_peptide.input.fa') From 2e8c77b2d0917f262fbc09d52025813f4c6728a5 Mon Sep 17 00:00:00 2001 From: Maksim Filippov Date: Wed, 18 Mar 2026 22:47:39 +0000 Subject: [PATCH 2/2] Enforce pvacvector pathfinder preconditions. Make find_optimal_path fail fast when called with an invalid graph and update the regression test to assert the expected exception for out-of-contract invocation. --- pvactools/tools/pvacvector/run.py | 16 +++++++--------- tests/test_pvacvector.py | 31 ++++++++++++------------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/pvactools/tools/pvacvector/run.py b/pvactools/tools/pvacvector/run.py index 31c2e14b5..ebf52c9e7 100644 --- a/pvactools/tools/pvacvector/run.py +++ b/pvactools/tools/pvacvector/run.py @@ -257,6 +257,10 @@ def create_distance_matrix(Paths): return distance_matrix def find_optimal_path(graph, distance_matrix, seq_dict, base_output_dir, junctions_file, args): + (valid, error) = check_graph_valid(graph, seq_dict) + if not valid: + raise Exception("Invalid graph passed to find_optimal_path: {}".format(error)) + init_state = sorted(graph.nodes()) if not os.environ.get('TEST_FLAG') or os.environ.get('TEST_FLAG') == '0': random.shuffle(init_state) @@ -313,22 +317,16 @@ def find_optimal_path(graph, distance_matrix, seq_dict, base_output_dir, junctio for id in state: print("\t", id) - if len(all_scores) > 0: - median_score = str(cumulative_weight/len(all_scores)) - lowest_score = str(min_score) - score_list = ','.join(all_scores) - else: - median_score = 'NA' - lowest_score = 'NA' - score_list = 'NA' + median_score = str(cumulative_weight/len(all_scores)) peptide_id_list = ','.join(names) + score_list = ','.join(all_scores) output = list() output.append(">") output.append(peptide_id_list) output.append("|Median_Junction_Score:") output.append(median_score) output.append("|Lowest_Junction_Score:") - output.append(lowest_score) + output.append(str(min_score)) output.append("|All_Junction_Scores:") output.append(score_list) output.append("\n") diff --git a/tests/test_pvacvector.py b/tests/test_pvacvector.py index 37f996f92..be239d551 100644 --- a/tests/test_pvacvector.py +++ b/tests/test_pvacvector.py @@ -464,7 +464,7 @@ def test_pvacvector_remove_peptides(self): output_dir.cleanup() - def test_find_optimal_path_single_peptide_without_edges_does_not_crash(self): + def test_find_optimal_path_raises_on_invalid_graph(self): with tempfile.TemporaryDirectory() as output_dir: seq_dict = {"MT.TEST.1": "SYFPEITHI"} graph = run.initialize_graph(seq_dict.keys()) @@ -475,24 +475,17 @@ def test_find_optimal_path_single_peptide_without_edges_does_not_crash(self): sample_name="single_peptide_no_edges", ) - results_file, error = run.find_optimal_path( - graph, - distance_matrix, - seq_dict, - output_dir, - junctions_file, - args, - ) - - self.assertIsNone(error) - self.assertIsNotNone(results_file) - self.assertTrue(os.path.exists(results_file)) - with open(results_file, "r") as fh: - contents = fh.read() - self.assertIn("|Median_Junction_Score:", contents) - self.assertIn("|Lowest_Junction_Score:", contents) - self.assertIn("|All_Junction_Scores:", contents) - self.assertIn("SYFPEITHI", contents) + with self.assertRaises(Exception) as context: + run.find_optimal_path( + graph, + distance_matrix, + seq_dict, + output_dir, + junctions_file, + args, + ) + + self.assertIn("Invalid graph passed to find_optimal_path", str(context.exception)) def test_prevent_clipping_best_peptide(self): output_dir = tempfile.TemporaryDirectory()