C++ and C¶
The library every language uses is a C++ core behind a C interface,
gamut/gamut.h. C++ programs use
gamut/gamut.hpp, a header over that
interface with the names of the Python package, exceptions for errors and
standard containers for data; C programs, and other languages that call C,
use gamut.h directly. Both are installed with the library
(installing); with CMake, find_package(gamut)
and gamut::gamut are all a project needs.
An example written in C++ has a C++ tab, quoted from a program in
examples/cpp; the examples say which are.
nearest.cpp
evaluates a model fitted in Python. Without CMake, one builds as
c++ -std=c++17 run_table.cpp -I<prefix>/include -L<prefix>/lib -lgamut
-o run_table, <prefix> being where the library is installed.
C++¶
The same calls¶
| Python | C++ |
|---|---|
gamut.create(path, writer=w, mesh="shared", per="fixed", ...) |
gamut::create(path, w, "shared", "fixed"), then declare_... calls |
study.add_run(values, fields=...) |
study.add_run({{"gamma", 0.5}}, {{"cp", cp}}) |
gamut.open(path, mode="a") |
gamut::open(path, "a") |
study.get_field("p", rows=r, nodes=n) |
study.get_field("p", r, n) |
study.get_parameter("alpha"), study.get_constant("area") |
study.get_parameter<double>("alpha"), study.get_constant<double>("area"): the type the value is stored as, int64_t for one declared int64 |
study.outside("alpha", rows=r) |
study.outside("alpha", r) |
study.related("cl") |
study.related("cl"): a gamut::Related, its std as std_dev |
study.select("mach > 0.7"), study.get_label(...), study.describe(...) |
the same names |
gamut.append, extract, write_copy, validate |
gamut::append, extract, write_copy, validate |
Model files, notes and private records are written and read through
gamut.h; study.handle() gives the open file to pass to it.
Conventions¶
create takes the path, the writer, the mesh ("" for a table of runs,
"shared" or "changing") and how often the geometry changes ("fixed",
"run" or "group:<label>"). Declarations come next, then the mesh, then
runs. points are nodes × axes in C order and cells a gamut::Cells in
VTK's convention, node numbers from 0. Each run gives its parameters and
scalars as numbers (a parameter is a double unless declared with
declare_parameter<int64_t>) and its labels by name, then its fields;
add_run writes it all or nothing and returns its id, and takes a
gamut::Geometry when the run brings its own points. add_runs writes
many runs at once from a column per name.
select returns positions counted from 0, which every get_ call takes
as rows: gamut::all, positions such as {12}, or
gamut::Selection::range(start, stop). A field comes back as a
gamut::Array<double>: the values in C order and their shape, with
block(run, k) for one value. gamut::open(path, "r", 4) decompresses
large reads in four threads.
A call that is refused throws gamut::Error, whose rule() names the rule
of the specification ("E7") when one applies and path() what it
concerns. A study closes when it goes out of scope. Call close() to
finish a file you write: it throws if the file cannot be finished (a
shared mesh never set), whereas leaving the scope then discards the file
silently. abandon() gives up on a file on purpose.
A model file¶
The built-in nearest model of models, fitted in Python, is evaluated here by the core, from the model's stored numbers:
const gamut::Study model = gamut::open("lift_nearest.gamut");
gamut_error err;
gamut_tree* tree = nullptr;
if (gamut_model_numbers(model.handle(), "fit", &tree, &err) != 0) throw gamut::Error(err);
const std::unique_ptr<gamut_tree, void (*)(gamut_tree*)> numbers(tree, gamut_tree_free);
const char* inputs[] = {"alpha"};
const double alpha[] = {2.2, 7.9};
const double* columns[] = {alpha};
const int64_t lengths[] = {2};
double lift[2];
if (gamut_nearest_call(numbers.get(), inputs, columns, lengths, 1, 2, "cl", lift, 2, &err) != 0)
throw gamut::Error(err);
the nearest run's lift at 2.2 and 7.9 degrees: 0.22 0.88
gamut_model_numbers gives any model's numbers, whatever its type, as a
tree read with the gamut_tree_* calls, for a program that computes the
model itself. gamut_nearest_call evaluates the built-in model on a
column of values for each named input, a value per row. It returns values
only: it neither checks the inputs against the parameters' bounds nor
writes a data file.
evaluate in Python, MATLAB and Julia is the core's gamut_evaluation_*
calls, which a C program can make itself. The core checks the inputs,
runs the built-in models, asks the caller for the answers of each model
of another type, and writes the data file; gamut.h describes the order
of the calls.
C¶
examples/c/one_mesh.c writes the same
runs in C, with no wall label, and prints the last line.
The conventions¶
- Every call returns 0, or returns nonzero and fills a
gamut_errorwith the rule of the specification it concerns (when one applies), the path of what it concerns, and a message. No call keeps a pointer it was given. - Handles are opaque:
gamut_filefor an open file,gamut_stringsfor a list of names. - Arrays are the caller's, in C order. Positions count from 0. Each array
comes with its length, and each array the core fills with its capacity,
so the core can check them:
gamut_geometry_pointstakes the count of coordinates as well as the nodes and axes they make.
static gamut_error err;
/* Stops at the first failure and says what it was. */
#define CHECK(call) \
do { \
if ((call) != 0) { \
fprintf(stderr, "%s%s%s\n", err.rule, *err.rule ? ": " : "", err.message); \
exit(1); \
} \
} while (0)
Writing¶
Declarations come first, then the mesh, then runs. A batch of one or more runs is staged and committed all or nothing:
CHECK(gamut_create("cylinder.gamut", "example sweep", "shared", "fixed", &f, &err));
CHECK(gamut_declare_coordinates(f, "m", GAMUT_FLOAT64, 2, "", "", &err));
CHECK(gamut_declare_parameter(f, "gamma", "design", "m2 s-1", GAMUT_FLOAT64, 0, 0, 0, "", &err));
CHECK(gamut_declare_result(f, "cp", "node", "1", GAMUT_FLOAT64, "", "", 0, 0, "", "", "", "", -1,
"", "", &err));
CHECK(gamut_geometry_begin(f, &err));
CHECK(gamut_geometry_points(f, GAMUT_FLOAT64, points, 2 * N_NODES, N_NODES, 2, &err));
CHECK(gamut_geometry_cells(f, types, N_CELLS, offsets, N_CELLS + 1, connectivity, 4 * N_CELLS,
&err));
CHECK(gamut_set_mesh(f, &err));
static void add_run(gamut_file* f, double gamma) {
static double cp[N_NODES];
solve(gamma, cp);
CHECK(gamut_batch_begin(f, 1, &err));
CHECK(gamut_batch_values(f, "gamma", GAMUT_FLOAT64, &gamma, 0, 1, &err));
CHECK(gamut_batch_values(f, "cp", GAMUT_FLOAT64, cp, 0, N_NODES, &err));
CHECK(gamut_batch_commit(f, NULL, &err));
}
gamut_open_append opens an existing file, and runs are added to it the
same way.
gamut_declare_result takes every attribute a result can have; an empty
string leaves a text attribute out, has_q = 0 the quantile and
round_bits = -1 the rounding. The header comments each argument.
Reading¶
CHECK(gamut_open("cylinder.gamut", &f, &err));
int64_t node = 12;
const gamut_sel one_node = {GAMUT_SEL_INDEX, 0, 0, &node, 1};
double cp[9]; /* runs x nodes */
CHECK(gamut_read(f, "cp", NULL, &one_node, GAMUT_FLOAT64, cp, sizeof cp, &err));
CHECK(gamut_close(f, &err));
A selection is everything (GAMUT_SEL_ALL, or NULL), a range
(GAMUT_SEL_RANGE, from start up to stop), or a strictly increasing list
of positions (GAMUT_SEL_INDEX). gamut_shape gives the shape a read will
have; gamut_read converts to the type asked for and fills the buffer from
the start. It prints the same values Python reads for that node in every
run:
cp at the top of the cylinder, run by run: -3.00 -2.69 -2.39 -2.10 -1.83 -1.57 -1.32 -1.08 -0.86
Notes¶
gamut_closefinishes a file. If it refuses (a shared mesh never set), the handle stays open to fix and close again;gamut_abandongives up, and can delete a file the handle created.gamut_validate,gamut_append,gamut_extractandgamut_write_copywork on paths, as in every language.- Only the
gamut_functions leave the library. A build withGAMUT_STATIC_HDF5carries its own HDF5, so a program that also links HDF5 never meets a second copy.
Build it: cc one_mesh.c -I<prefix>/include -L<prefix>/lib -lgamut -lm.
The full program is examples/c/one_mesh.c.
Next: Rust.