@@ -35,6 +35,11 @@ std::shared_ptr<TreeNode> parse_auspice_tree(
3535 }
3636
3737 // Insert node into the map *after* children so it's fully constructed
38+ if (node_map.find (node->node_id ) != node_map.end ()) {
39+ throw silo::preprocessing::PreprocessingException (
40+ fmt::format (" Duplicate node ID found in Newick string: '{}'" , node->node_id .string )
41+ );
42+ }
3843 node_map[node->node_id ] = node;
3944 return node;
4045}
@@ -150,6 +155,11 @@ std::shared_ptr<TreeNode> parseSubtree(
150155 node->node_id = parseLabel (sv);
151156 skipWhitespace (sv);
152157
158+ if (node_map.find (node->node_id ) != node_map.end ()) {
159+ throw silo::preprocessing::PreprocessingException (
160+ fmt::format (" Duplicate node ID found in Newick string: '{}'" , node->node_id .string )
161+ );
162+ }
153163 node_map[node->node_id ] = node;
154164
155165 return node;
@@ -179,7 +189,7 @@ PhyloTree PhyloTree::fromNewickString(const std::string& newick_string) {
179189 }
180190 } catch (const std::exception& e) {
181191 throw silo::preprocessing::PreprocessingException (
182- fmt::format (" Error when parsing the Newick string: '{}'" , newick_string)
192+ fmt::format (" Error when parsing the Newick string '{}': {} " , newick_string, e. what () )
183193 );
184194 }
185195
@@ -207,7 +217,7 @@ PhyloTree PhyloTree::fromNewickFile(const std::filesystem::path& newick_path) {
207217 return fromNewickString (contents.str ());
208218 } catch (const std::exception& e) {
209219 throw silo::preprocessing::PreprocessingException (
210- fmt::format (" Error when parsing the Newick file: '{}'" , newick_path.string ())
220+ fmt::format (" Error when parsing the Newick file '{}': {} " , newick_path.string (), e. what ())
211221 );
212222 }
213223}
@@ -217,14 +227,60 @@ PhyloTree PhyloTree::fromFile(const std::filesystem::path& path) {
217227
218228 std::transform (ext.begin (), ext.end (), ext.begin (), ::tolower);
219229
230+ if (ext != " .nwk" && ext != " .json" ) {
231+ throw silo::preprocessing::PreprocessingException (fmt::format (
232+ " Error when parsing tree file: '{}'. Path must end with .nwk or .json" , path.string ()
233+ ));
234+ }
220235 if (ext == " .nwk" ) {
221236 return common::PhyloTree::fromNewickFile (path);
222237 } else if (ext == " .json" ) {
223238 return common::PhyloTree::fromAuspiceJSONFile (path);
224239 }
225- throw silo::preprocessing::PreprocessingException (fmt::format (
226- " Error when parsing tree file: '{}'. Path must end with .nwk or .json" , path.string ()
227- ));
240+ }
241+
242+ void PhyloTree::validateNodeExists (const TreeNodeId& node_id) {
243+ if (nodes.find (node_id) == nodes.end ()) {
244+ throw silo::preprocessing::PreprocessingException (
245+ fmt::format (" Node '{}' not found in the tree." , node_id.string )
246+ );
247+ }
248+ }
249+
250+ void PhyloTree::validateNodeExists (const std::string& node_label) {
251+ auto node_id = TreeNodeId{node_label};
252+ validateNodeExists (node_id);
253+ }
254+
255+ roaring::Roaring PhyloTree::getDescendants (const TreeNodeId& node_id) {
256+ validateNodeExists (node_id);
257+ auto child_it = nodes.find (node_id);
258+ roaring::Roaring result_bitmap;
259+ if (!child_it->second ) {
260+ throw silo::preprocessing::PreprocessingException (" Node is null." );
261+ }
262+ std::function<void (const std::shared_ptr<TreeNode>&)> dfs =
263+ [&](const std::shared_ptr<TreeNode>& current) {
264+ if (!current)
265+ return ;
266+ if (current->isLeaf ()) {
267+ if (current->row_index .has_value ()) {
268+ result_bitmap.add (current->row_index .value ());
269+ }
270+ }
271+ for (const auto & child : current->children ) {
272+ dfs (child);
273+ }
274+ };
275+ if (child_it->second ->isLeaf ()) {
276+ return result_bitmap;
277+ }
278+ dfs (child_it->second );
279+ return result_bitmap;
280+ }
281+
282+ roaring::Roaring PhyloTree::getDescendants (const std::string& node_label) {
283+ return getDescendants (TreeNodeId{node_label});
228284}
229285
230286} // namespace silo::common
0 commit comments