star-line

Structure for accelerating line importance sampling
git clone git://git.meso-star.fr/star-line.git
Log | Files | Refs | README | LICENSE

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:
Mdoc/sln-get.1 | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/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;