@@ -8905,6 +8905,7 @@ static void
89058905test_sort_tables_errors (void )
89068906{
89078907 int ret ;
8908+ tsk_id_t ret_id ;
89088909 tsk_treeseq_t ts ;
89098910 tsk_table_collection_t tables ;
89108911 tsk_bookmark_t pos ;
@@ -8968,6 +8969,32 @@ test_sort_tables_errors(void)
89688969 ret = tsk_table_collection_sort (& tables , & pos , 0 );
89698970 CU_ASSERT_EQUAL_FATAL (ret , TSK_ERR_SORT_OFFSET_NOT_SUPPORTED );
89708971
8972+ /* Test TSK_ERR_MUTATION_PARENT_INCONSISTENT */
8973+ ret = tsk_table_collection_clear (& tables , 0 );
8974+ CU_ASSERT_EQUAL_FATAL (ret , 0 );
8975+ tables .sequence_length = 1.0 ;
8976+
8977+ ret_id = tsk_node_table_add_row (& tables .nodes , 0 , 0.0 , TSK_NULL , TSK_NULL , NULL , 0 );
8978+ CU_ASSERT_FATAL (ret_id >= 0 );
8979+ ret_id = tsk_site_table_add_row (& tables .sites , 0.0 , "x" , 1 , NULL , 0 );
8980+ CU_ASSERT_FATAL (ret_id >= 0 );
8981+
8982+ ret_id
8983+ = tsk_mutation_table_add_row (& tables .mutations , 0 , 0 , 2 , 0.0 , "a" , 1 , NULL , 0 );
8984+ CU_ASSERT_FATAL (ret_id >= 0 );
8985+ ret_id
8986+ = tsk_mutation_table_add_row (& tables .mutations , 0 , 0 , 3 , 0.0 , "b" , 1 , NULL , 0 );
8987+ CU_ASSERT_FATAL (ret_id >= 0 );
8988+ ret_id
8989+ = tsk_mutation_table_add_row (& tables .mutations , 0 , 0 , 1 , 0.0 , "c" , 1 , NULL , 0 );
8990+ CU_ASSERT_FATAL (ret_id >= 0 );
8991+ ret_id
8992+ = tsk_mutation_table_add_row (& tables .mutations , 0 , 0 , 2 , 0.0 , "d" , 1 , NULL , 0 );
8993+ CU_ASSERT_FATAL (ret_id >= 0 );
8994+
8995+ ret = tsk_table_collection_sort (& tables , NULL , 0 );
8996+ CU_ASSERT_EQUAL_FATAL (ret , TSK_ERR_MUTATION_PARENT_INCONSISTENT );
8997+
89718998 tsk_table_collection_free (& tables );
89728999 tsk_treeseq_free (& ts );
89739000}
@@ -9032,6 +9059,120 @@ test_sort_tables_mutation_times(void)
90329059 tsk_treeseq_free (& ts );
90339060}
90349061
9062+ static void
9063+ test_sort_tables_mutations (void )
9064+ {
9065+ int ret ;
9066+ tsk_table_collection_t tables ;
9067+
9068+ /* Sorting hierarchy:
9069+ * 1. site
9070+ * 2. time (when known)
9071+ * 3. node_time
9072+ * 4. num_descendants: parent mutations first
9073+ * 5. node_id
9074+ * 6. mutation_id
9075+ */
9076+
9077+ const char * sites = "0.0 A\n"
9078+ "0.5 T\n"
9079+ "0.75 G\n" ;
9080+
9081+ const char * mutations_unsorted =
9082+ /* Test site criterion (primary) - site 1 should come after site 0 */
9083+ "1 0 X -1 0.0\n" /* mut 0: site 1, will be sorted after site 0 mutations */
9084+ "0 0 Y -1 0.0\n" /* mut 1: site 0, will be sorted before site 1 mutations */
9085+
9086+ /* Test time criterion - within same site, earlier time first */
9087+ "0 4 B -1 2.0\n" /* mut 2: site 0, node 4 (time 1.0), time 2.0 (later time)
9088+ */
9089+ "0 5 A -1 2.5\n" /* mut 3: site 0, node 5 (time 2.0), time 2.5 (earlier
9090+ relative) */
9091+
9092+ /* Test unknown vs known times - unknown times at site 2, fall back to node_time
9093+ sorting */
9094+ "2 4 U2 -1\n" /* mut 4: site 2, node 4 (time 1.0), unknown time - falls back
9095+ to node_time */
9096+ "2 4 U3 -1\n" /* mut 5: site 2, node 4 (time 1.0), unknown time - should use
9097+ mutation_id as tiebreaker */
9098+ "2 5 U1 -1\n" /* mut 6: site 2, node 5 (time 2.0), unknown time - falls back
9099+ to node_time */
9100+
9101+ /* Test node_time criterion - same site, same mut time, different node times */
9102+ "0 4 D -1 1.5\n" /* mut 7: site 0, node 4 (time 1.0), mut time 1.5 */
9103+ "0 5 C -1 2.5\n" /* mut 8: site 0, node 5 (time 2.0), mut time 2.5 - same
9104+ mut time */
9105+
9106+ /* Test num_descendants criterion with mutation parent-child relationships */
9107+ "0 2 P -1 0.0\n" /* mut 9: site 0, node 2, parent mutation (0 descendants
9108+ initially) */
9109+ "0 1 C1 9 0.0\n" /* mut 10: site 0, node 1, child of mut 9 (parent now has
9110+ 1+ descendants) */
9111+ "0 1 C2 9 0.0\n" /* mut 11: site 0, node 1, another child of mut 9 (parent
9112+ now has 2+ descendants) */
9113+ "0 3 Q -1 0.0\n" /* mut 12: site 0, node 3, no children (0 descendants) */
9114+ "0 0 C3 10 0.0\n" /* mut 13: site 0, node 0, child of mut 10 (making mut 9 a
9115+ grandparent) */
9116+
9117+ /* Test node and mutation_id criteria for final tiebreaking */
9118+ "0 0 Z1 -1 0.0\n" /* mut 14: site 0, node 0, no parent, will test node+id
9119+ ordering */
9120+ "0 0 Z2 -1 0.0\n" ; /* mut 15: site 0, node 0, no parent, later in input =
9121+ higher ID */
9122+
9123+ const char * mutations_sorted =
9124+ /* Site 0 mutations - known times first, sorted by time */
9125+ "0 5 A -1 2.5\n"
9126+ "0 5 C -1 2.5\n"
9127+ "0 4 B -1 2.0\n"
9128+ "0 4 D -1 1.5\n"
9129+ "0 2 P -1 0.0\n"
9130+ "0 1 C1 4 0.0\n"
9131+ "0 0 Y -1 0.0\n"
9132+ "0 0 C3 5 0.0\n"
9133+ "0 0 Z1 -1 0.0\n"
9134+ "0 0 Z2 -1 0.0\n"
9135+ "0 1 C2 4 0.0\n"
9136+ "0 3 Q -1 0.0\n"
9137+
9138+ /* Site 1 mutations */
9139+ "1 0 X -1 0.0\n"
9140+
9141+ /* Site 2 mutations - unknown times, sorted by node_time then other criteria */
9142+ "2 5 U1 -1\n"
9143+ "2 4 U2 -1\n"
9144+ "2 4 U3 -1\n" ;
9145+
9146+ ret = tsk_table_collection_init (& tables , 0 );
9147+ CU_ASSERT_EQUAL_FATAL (ret , 0 );
9148+ tables .sequence_length = 1.0 ;
9149+ parse_nodes (single_tree_ex_nodes , & tables .nodes );
9150+ parse_edges (single_tree_ex_edges , & tables .edges );
9151+
9152+ parse_sites (sites , & tables .sites );
9153+ CU_ASSERT_EQUAL_FATAL (tables .sites .num_rows , 3 );
9154+
9155+ parse_mutations (mutations_unsorted , & tables .mutations );
9156+ CU_ASSERT_EQUAL_FATAL (tables .mutations .num_rows , 16 );
9157+
9158+ ret = tsk_table_collection_sort (& tables , NULL , 0 );
9159+ CU_ASSERT_EQUAL_FATAL (ret , 0 );
9160+
9161+ tsk_table_collection_t expected ;
9162+ ret = tsk_table_collection_init (& expected , 0 );
9163+ CU_ASSERT_EQUAL_FATAL (ret , 0 );
9164+ expected .sequence_length = 1.0 ;
9165+ parse_nodes (single_tree_ex_nodes , & expected .nodes );
9166+ parse_edges (single_tree_ex_edges , & expected .edges );
9167+ parse_sites (sites , & expected .sites );
9168+ parse_mutations (mutations_sorted , & expected .mutations );
9169+
9170+ CU_ASSERT_TRUE (tsk_mutation_table_equals (& tables .mutations , & expected .mutations , 0 ));
9171+
9172+ tsk_table_collection_free (& expected );
9173+ tsk_table_collection_free (& tables );
9174+ }
9175+
90359176static void
90369177test_sort_tables_canonical_errors (void )
90379178{
@@ -11608,6 +11749,7 @@ main(int argc, char **argv)
1160811749 { "test_sort_tables_errors" , test_sort_tables_errors },
1160911750 { "test_sort_tables_individuals" , test_sort_tables_individuals },
1161011751 { "test_sort_tables_mutation_times" , test_sort_tables_mutation_times },
11752+ { "test_sort_tables_mutations" , test_sort_tables_mutations },
1161111753 { "test_sort_tables_migrations" , test_sort_tables_migrations },
1161211754 { "test_sort_tables_no_edge_metadata" , test_sort_tables_no_edge_metadata },
1161311755 { "test_sort_tables_offsets" , test_sort_tables_offsets },
0 commit comments