@@ -34,6 +34,11 @@ std::shared_ptr<TreeNode> parse_auspice_tree(
3434 node->children .push_back (child_node);
3535 }
3636
37+ if (node_map.find (node->node_id ) != node_map.end ()) {
38+ throw silo::preprocessing::PreprocessingException (
39+ fmt::format (" Duplicate node ID found in Newick string: '{}'" , node->node_id .string )
40+ );
41+ }
3742 node_map[node->node_id ] = node;
3843 return node;
3944}
@@ -149,6 +154,11 @@ std::shared_ptr<TreeNode> parseSubtree(
149154 node->node_id = parseLabel (sv);
150155 skipWhitespace (sv);
151156
157+ if (node_map.find (node->node_id ) != node_map.end ()) {
158+ throw silo::preprocessing::PreprocessingException (
159+ fmt::format (" Duplicate node ID found in Newick string: '{}'" , node->node_id .string )
160+ );
161+ }
152162 node_map[node->node_id ] = node;
153163
154164 return node;
@@ -178,7 +188,7 @@ PhyloTree PhyloTree::fromNewickString(const std::string& newick_string) {
178188 }
179189 } catch (const std::exception& e) {
180190 throw silo::preprocessing::PreprocessingException (
181- fmt::format (" Error when parsing the Newick string: '{}'" , newick_string)
191+ fmt::format (" Error when parsing the Newick string '{}': {} " , newick_string, e. what () )
182192 );
183193 }
184194
@@ -206,7 +216,7 @@ PhyloTree PhyloTree::fromNewickFile(const std::filesystem::path& newick_path) {
206216 return fromNewickString (contents.str ());
207217 } catch (const std::exception& e) {
208218 throw silo::preprocessing::PreprocessingException (
209- fmt::format (" Error when parsing the Newick file: '{}'" , newick_path.string ())
219+ fmt::format (" Error when parsing the Newick file '{}': {} " , newick_path.string (), e. what ())
210220 );
211221 }
212222}
@@ -216,14 +226,60 @@ PhyloTree PhyloTree::fromFile(const std::filesystem::path& path) {
216226
217227 std::transform (ext.begin (), ext.end (), ext.begin (), ::tolower);
218228
229+ if (ext != " .nwk" && ext != " .json" ) {
230+ throw silo::preprocessing::PreprocessingException (fmt::format (
231+ " Error when parsing tree file: '{}'. Path must end with .nwk or .json" , path.string ()
232+ ));
233+ }
219234 if (ext == " .nwk" ) {
220235 return common::PhyloTree::fromNewickFile (path);
221236 } else if (ext == " .json" ) {
222237 return common::PhyloTree::fromAuspiceJSONFile (path);
223238 }
224- throw silo::preprocessing::PreprocessingException (fmt::format (
225- " Error when parsing tree file: '{}'. Path must end with .nwk or .json" , path.string ()
226- ));
239+ }
240+
241+ void PhyloTree::validateNodeExists (const TreeNodeId& node_id) {
242+ if (nodes.find (node_id) == nodes.end ()) {
243+ throw silo::preprocessing::PreprocessingException (
244+ fmt::format (" Node '{}' not found in the tree." , node_id.string )
245+ );
246+ }
247+ }
248+
249+ void PhyloTree::validateNodeExists (const std::string& node_label) {
250+ auto node_id = TreeNodeId{node_label};
251+ validateNodeExists (node_id);
252+ }
253+
254+ roaring::Roaring PhyloTree::getDescendants (const TreeNodeId& node_id) {
255+ validateNodeExists (node_id);
256+ auto child_it = nodes.find (node_id);
257+ roaring::Roaring result_bitmap;
258+ if (!child_it->second ) {
259+ throw silo::preprocessing::PreprocessingException (" Node is null." );
260+ }
261+ std::function<void (const std::shared_ptr<TreeNode>&)> dfs =
262+ [&](const std::shared_ptr<TreeNode>& current) {
263+ if (!current)
264+ return ;
265+ if (current->isLeaf ()) {
266+ if (current->row_index .has_value ()) {
267+ result_bitmap.add (current->row_index .value ());
268+ }
269+ }
270+ for (const auto & child : current->children ) {
271+ dfs (child);
272+ }
273+ };
274+ if (child_it->second ->isLeaf ()) {
275+ return result_bitmap;
276+ }
277+ dfs (child_it->second );
278+ return result_bitmap;
279+ }
280+
281+ roaring::Roaring PhyloTree::getDescendants (const std::string& node_label) {
282+ return getDescendants (TreeNodeId{node_label});
227283}
228284
229285} // namespace silo::common
0 commit comments