commit 7b1db0bf76158daaa8f82a8bebdb4ca904077c89
parent 4df491f5c3fe088f5dfd92e281d0698182b70c39
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Thu, 5 Mar 2026 16:55:38 +0100
Add the tree path to the sln-get tool
The caller can browse the tree using new options. The mesh output then
corresponds to that of the selected node. Like the node description,
which is also a new option introduced by this commit. It describes
the selected node.
Diffstat:
| M | doc/sln-get.1 | | | 100 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| M | src/sln_get.c | | | 171 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
2 files changed, 244 insertions(+), 27 deletions(-)
diff --git a/doc/sln-get.1 b/doc/sln-get.1
@@ -15,7 +15,7 @@
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with this program. If not, see <http://www.gnu.org/licenses/>.
-.Dd March 4, 2026
+.Dd March 5, 2026
.Dt SLN-GET 1
.Os
.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
@@ -25,7 +25,9 @@
.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.Sh SYNOPSIS
.Nm
-.Op Fl hmv
+.Op Fl hlmnrv
+.Op Fl L Ar nlevels
+.Op Fl R Ar nlevels
.Op Ar tree
.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.Sh DESCRIPTION
@@ -39,10 +41,18 @@ If no structure is defined as an input argument, the
.Ar tree
is read on the standard input.
.Pp
-By default, i.e. without options,
+The printed data is for the current node, which is by default the root
+of the tree.
+The caller can select another node by visiting the tree using the
+traversal options
+.Pq options Fl L , Fl l , Fl R No and Fl r .
+.Pp
+By default,
.Nm
-prints a general description of the acceleration structure provided as
-input.
+displays the description of the tree, such as the number of lines it
+structures, the total number of nodes, or the number of vertices used to
+mesh the hierarchical representation of the high-resolution spectrum it
+represents.
.Pp
The options are as follows:
.Bl -tag -width Ds
@@ -50,14 +60,53 @@ The options are as follows:
.It Fl h
Display short help and exit.
.\""""""""""""""""""""""""""""""""""
+.It Fl L Ar nlevels
+Descend the tree by visiting the left child of the nodes traversed up to
+.Ar nlevels
+times.
+The traversal of the tree stops when the number of levels traversed is
+reached or when one of the nodes visited is a leaf.
+The node at which the traversal stopped then becomes the current node.
+.\""""""""""""""""""""""""""""""""""
+.It Fl l
+Visit the left child of the current node.
+This is a shortcut for the
+.Fl L Ns Ar 1
+option.
+The current node then becomes this left child.
+If the node has no left child, i.e., if it is a leaf, then the current
+node is not updated.
+.\""""""""""""""""""""""""""""""""""
+.It Fl R Ar nlevels
+Descend the tree by visiting the right child of the nodes traversed up
+to
+.Ar nlevels
+times.
+This option behaves in the same way as the
+.Fl L
+option, but here for the right child of the nodes, instead of their left
+child.
+.\""""""""""""""""""""""""""""""""""
+.It Fl r
+Query the right child of the current node.
+This option behaves in the same way as the
+.Fl l
+option, but here for the right child of the node, instead of the left
+child.
+.\""""""""""""""""""""""""""""""""""
.It Fl m
-Print the root node mesh, i.e. the mesh representing the high resolution
-spectrum of all the lines it structures.
+Prints the mesh of the current node, i.e. the mesh representing the high
+resolution spectrum of all the lines it structures.
.Pp
The output data is a list of mesh vertices, in plain text, where each
line represents the two values of a mesh vertex, separated by a space:
its wavenumber in cm^-1, and its associated spectrum value.
.\""""""""""""""""""""""""""""""""""
+.It Fl n
+Displays the description of the current node, i.e., its level relative to
+the root, the number of lines it partitions, and the number of mesh
+vertices used to represent them at the node's hierarchical level.
+.\""""""""""""""""""""""""""""""""""
.It Fl v
Make
.Nm
@@ -71,5 +120,42 @@ The maximum is 3.
.Sh EXIT STATUS
.Ex -std
.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.Sh EXAMPLES
+Display the description of a tree:
+.Bd -literal -offset Ds
+sln-get tree.sln
+.Ed
+.\""""""""""""""""""""""""""""""""""
+.Pp
+Print the description of the child left of the tree root:
+.Bd -literal -offset Ds
+sln-get -ln tree.sln
+.Ed
+.\""""""""""""""""""""""""""""""""""
+.Pp
+Print the description of one of the grandchildren of the root of the
+tree, in this case the right child of its left child:
+.Bd -literal -offset Ds
+sln-get -lrn tree.sln
+.Ed
+.\""""""""""""""""""""""""""""""""""
+.Pp
+Descend the tree by first visiting the three left children of the first
+three levels of the tree
+.Pq option Fl L Ar 3 ,
+then the right child
+.Pq option Fl r ,
+and finally the left child
+.Pq option Fl l
+of the next two levels.
+Then output the mesh of the node reached
+.Pq option Fl m
+and save it to the
+.Pa mesh.txt
+file:
+.Bd -literal -offset Ds
+sln-get -L3 -rl -m tree.sln > mesh.txt
+.Ed
+.\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.Sh SEE ALSO
.Xr sln-build 1
diff --git a/src/sln_get.c b/src/sln_get.c
@@ -20,21 +20,28 @@
#include "sln.h"
+#include <rsys/cstr.h>
+#include <rsys/math.h>
#include <rsys/mem_allocator.h>
#include <unistd.h>
-/* Draft of the command sln-get # Default print information
- [-lr] # go to left/right
- [-L<n>] [-R<n>] # go to n times left/right
- [-m] # output mesh
- [-v] # verbosity
- [-h] # short help
- [tree] */
+/* The maximum depth of a tree is 63, which is greater than the number of
+ * lines that could actually be managed. Indeed, with such a depth, assuming
+ * that there is a maximum of one line per leaf, it would be possible to
+ * structure up to 2^63 lines. The problem would then be memory usage well
+ * before this maximum depth.
+ *
+ * In fact, this limit is dictated by the number of bits used to encode the
+ * descent masks, i.e., on a 64-bit integer. */
+#define MAX_DEPTH 64
+
+enum child { LEFT, RIGHT };
enum output_type {
- OUTPUT_DESCRIPTOR,
- OUTPUT_MESH,
+ OUTPUT_TREE_DESCRIPTOR,
+ OUTPUT_NODE_DESCRIPTOR,
+ OUTPUT_NODE_MESH,
OUTPUT_COUNT__
};
@@ -43,11 +50,23 @@ struct args {
enum output_type output_type;
+ /* Step of the tree descent.
+ *
+ * A bit set in one of the masks determines which side to descend to move from
+ * the current depth (which corresponds to the number of the set bit) to the
+ * next one. Since the tree is binary, only one of the two masks is needed, as
+ * the other can be deduced from the selected mask and the current depth.
+ * However, calculating both descent masks allows bugs in the analysis of
+ * arguments to be detected. */
+ uint64_t descent_lmask; /* Left mask */
+ uint64_t descent_rmask; /* Right mask */
+ unsigned depth; /* Current depth in the tree */
+
/* Miscellaneous */
int quit;
int verbose;
};
-#define ARGS_DEFAULT__ {NULL, OUTPUT_DESCRIPTOR, 0, 0}
+#define ARGS_DEFAULT__ {NULL, OUTPUT_TREE_DESCRIPTOR, 0, 0, 0, 0, 0}
static const struct args ARGS_DEFAULT = ARGS_DEFAULT__;
struct cmd {
@@ -67,12 +86,46 @@ static const struct cmd CMD_NULL = CMD_NULL__;
static void
usage(FILE* stream)
{
- fprintf(stream, "usage: sln-get [-hmv] [tree]\n");
+ fprintf(stream, "usage: sln-get [-hlmnrv] [tree]\n");
+}
+
+static void
+tree_descent
+ (struct args* args,
+ const enum child child, /* side of the tree descent */
+ const unsigned nlevels) /* Number of levels to descend in the tree */
+{
+ uint64_t mask = 0;
+ ASSERT(args);
+
+ if(nlevels == 0 || args->depth >= MAX_DEPTH) return; /* Nothing to descent */
+
+ if(nlevels > 64) {
+ /* The mask can encode up to 64 levels of descent. Thus, if the number of
+ * levels is greater than or equal to 64, all bits of the mask are
+ * activated. */
+ mask = ~0lu;
+ } else {
+ /* Set mask bits from bit 0 to the nlevels^th bit */
+ mask = BIT_U64(nlevels)-1;
+ }
+
+ /* Move the mask to the current depth */
+ mask <<= args->depth;
+
+ /* Here we go: descent the tree */
+ switch(child) {
+ case LEFT: args->descent_lmask |= mask; break;
+ case RIGHT: args->descent_rmask |= mask; break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ args->depth = MMIN(args->depth + nlevels, MAX_DEPTH);
}
static res_T
args_init(struct args* args, int argc, char** argv)
{
+ unsigned nlvls = 0;
int opt = 0;
res_T res = RES_OK;
@@ -80,13 +133,26 @@ args_init(struct args* args, int argc, char** argv)
*args = ARGS_DEFAULT;
- while((opt = getopt(argc, argv, "hmv")) != -1) {
+ while((opt = getopt(argc, argv, "hL:lmnR:rv")) != -1) {
switch(opt) {
case 'h':
usage(stdout);
args->quit = 1;
goto exit;
- case 'm': args->output_type = OUTPUT_MESH; break;
+ case 'L':
+ if((res = cstr_to_uint(optarg, &nlvls)) == RES_OK) {
+ tree_descent(args, LEFT, nlvls);
+ }
+ break;
+ case 'l': tree_descent(args, LEFT, 1); break;
+ case 'R':
+ if((res = cstr_to_uint(optarg, &nlvls)) == RES_OK) {
+ tree_descent(args, RIGHT, nlvls);
+ }
+ break;
+ case 'r': tree_descent(args, RIGHT, 1); break;
+ case 'm': args->output_type = OUTPUT_NODE_MESH; break;
+ case 'n': args->output_type = OUTPUT_NODE_DESCRIPTOR; break;
case 'v': args->verbose += (args->verbose < 3); break;
default: res = RES_BAD_ARG; break;
}
@@ -99,6 +165,14 @@ args_init(struct args* args, int argc, char** argv)
}
}
+#ifndef NDEBUG
+ {
+ const uint64_t descent_mask =
+ args->depth >= 64 ? ~0ul : BIT_U64(args->depth)-1;
+ ASSERT((args->descent_lmask | args->descent_rmask) == descent_mask);
+ }
+#endif
+
if(optind < argc) args->tree = argv[optind];
exit:
@@ -159,7 +233,7 @@ error:
}
static res_T
-print_descriptor(const struct cmd* cmd)
+print_tree_descriptor(const struct cmd* cmd)
{
struct sln_tree_desc desc = SLN_TREE_DESC_NULL;
res_T res = RES_OK;
@@ -170,7 +244,7 @@ print_descriptor(const struct cmd* cmd)
printf("#lines: %lu\n", (unsigned long)desc.nlines);
printf("#nodes: %lu\n", (unsigned long)desc.nnodes);
- printf("depth: %u\n", desc.depth);
+ printf("tree depth: %u\n", desc.depth);
printf("#vertices: %lu\n", (unsigned long)desc.nvertices);
printf("type: %s\n", sln_mesh_type_cstr(desc.mesh_type));
printf("decimation error: %.4e\n", desc.mesh_decimation_err);
@@ -183,6 +257,61 @@ error:
goto exit;
}
+static const struct sln_node* /* NULL <=> tree is empty */
+get_node(const struct cmd* cmd, unsigned* node_depth/*can be NULL*/)
+{
+ const struct sln_node* node = NULL;
+ unsigned depth = 0;
+ unsigned i = 0;
+ ASSERT(cmd);
+
+ node = sln_tree_get_root(cmd->tree);
+ if(node == NULL) goto exit; /* Tree is empty */
+
+ FOR_EACH(i, 0, cmd->args.depth) {
+ const uint64_t mask = BIT_U64(i);
+
+ if(sln_node_is_leaf(node)) break;
+
+ if(mask & cmd->args.descent_lmask) {
+ node = sln_node_get_child(node, 0);
+ } else if(mask & cmd->args.descent_rmask) {
+ node = sln_node_get_child(node, 1);
+ } else {
+ FATAL("Unreachable code\n");
+ }
+ ++depth;
+ }
+
+exit:
+ if(node_depth) *node_depth = depth;
+ return node;
+}
+
+static res_T
+print_node_descriptor(const struct cmd* cmd)
+{
+ const struct sln_node* node = NULL;
+ struct sln_node_desc desc = SLN_NODE_DESC_NULL;
+ unsigned depth = 0;
+ res_T res = RES_OK;
+ ASSERT(cmd);
+
+ if((node = get_node(cmd, &depth)) == NULL) goto exit; /* tree is empty */
+
+ res = sln_node_get_desc(node, &desc);
+ if(res != RES_OK) goto error;
+
+ printf("level: %u\n", depth);
+ printf("#lines: %lu\n", (unsigned long)desc.nlines);
+ printf("#vertices: %lu\n", (unsigned long)desc.nvertices);
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
static res_T
print_mesh(const struct cmd* cmd)
{
@@ -192,8 +321,7 @@ print_mesh(const struct cmd* cmd)
res_T res = RES_OK;
ASSERT(cmd);
- node = sln_tree_get_root(cmd->tree);
- if(node == NULL) goto exit; /* Tree is empty */
+ if((node = get_node(cmd, NULL)) == NULL) goto exit; /* tree is empty */
res = sln_node_get_mesh(cmd->tree, node, &mesh);
if(res != RES_OK) goto error;
@@ -216,10 +344,13 @@ cmd_run(const struct cmd* cmd)
res_T res = RES_OK;
switch(cmd->args.output_type) {
- case OUTPUT_DESCRIPTOR:
- res = print_descriptor(cmd);
+ case OUTPUT_TREE_DESCRIPTOR:
+ res = print_tree_descriptor(cmd);
+ break;
+ case OUTPUT_NODE_DESCRIPTOR:
+ res = print_node_descriptor(cmd);
break;
- case OUTPUT_MESH:
+ case OUTPUT_NODE_MESH:
res = print_mesh(cmd);
break;
default: FATAL("Unreachable code\n"); break;