rnatm

Load and structure data describing an atmosphere
git clone git://git.meso-star.fr/rnatm.git
Log | Files | Refs | README | LICENSE

commit 40319d1f7d4928eb0d566e6de29759077353a64c
parent 6219b46b707292484eb15ae7e46294749a1bb388
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 28 Nov 2022 18:45:48 +0100

Deep update of octree storage header

This commits changes both the descriptor and table of contents data.
The descriptor no longer stores a single signature but the signature of
the volumetric meshes and then a signature per band, i.e. the signature
of their radiative properties. Finally, it does not store the spectral
range but the corresponding band range.

Each entry in the table of contents now has the band index and
quadrature point of the octree in addition to the offset to the octree
data.

Thanks to the aforementioned changes, the same storage can be used in
more situations, that is, whenever the band range is included in the
band range use to build the storage. Whereas before, it could only
be reused when the same spectral range was used.

Diffstat:
Msrc/rnatm_octree.c | 20+++++++++++++-------
Msrc/rnatm_octrees_storage.c | 423+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/rnatm_octrees_storage.h | 36+++++++++++++++++++++++-------------
3 files changed, 341 insertions(+), 138 deletions(-)

diff --git a/src/rnatm_octree.c b/src/rnatm_octree.c @@ -1035,7 +1035,8 @@ vx_challenge_merge static res_T init_octrees_storage(struct rnatm* atm, const struct rnatm_create_args* args) { - struct octrees_storage_desc desc; + struct octrees_storage_desc challenge = OCTREES_STORAGE_DESC_NULL; + struct octrees_storage_desc reference = OCTREES_STORAGE_DESC_NULL; res_T res = RES_OK; ASSERT(atm && args); @@ -1044,21 +1045,24 @@ init_octrees_storage(struct rnatm* atm, const struct rnatm_create_args* args) /* Register the storage file */ atm->octrees_storage = args->octrees_storage; - res = setup_octrees_storage_desc(atm, &desc); + res = octrees_storage_desc_init(atm, &challenge); if(res != RES_OK) goto error; /* Save the computed descriptor in the storage */ if(!args->load_octrees_from_storage) { - res = write_octrees_storage_desc(atm, &desc, atm->octrees_storage); + res = write_octrees_storage_desc(atm, &challenge, atm->octrees_storage); if(res != RES_OK) goto error; /* The octrees must be loaded from storage. Verify that the data and settings - * used by the saved octrees are the same as the current parameters */ + * used by the saved octrees are compatibles with the current parameters */ } else { - struct octrees_storage_desc saved_desc; - res = read_octrees_storage_desc(atm, &saved_desc, args->octrees_storage); + + res = octrees_storage_desc_init_from_stream + (atm, &reference, args->octrees_storage); if(res != RES_OK) goto error; - if(!octrees_storage_desc_eq(&desc, &saved_desc)) { + + res = check_octrees_storage_compatibility(&reference, &challenge); + if(res != RES_OK) { log_err(atm, "unable to load octrees from the supplied storage. The saved " "octrees are built from different data\n"); @@ -1068,6 +1072,8 @@ init_octrees_storage(struct rnatm* atm, const struct rnatm_create_args* args) } exit: + octrees_storage_desc_release(&challenge); + octrees_storage_desc_release(&reference); return res; error: goto exit; diff --git a/src/rnatm_octrees_storage.c b/src/rnatm_octrees_storage.c @@ -18,6 +18,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#define _POSIX_C_SOURCE 200112L /* nextafter */ + #include "rnatm_c.h" #include "rnatm_log.h" #include "rnatm_octrees_storage.h" @@ -29,6 +31,8 @@ #include <rsys/cstr.h> +#include <math.h> /* nextafter */ + /******************************************************************************* * Helper functions ******************************************************************************/ @@ -48,35 +52,17 @@ cmp_hash256(const void* a, const void* b) return 0; } -static res_T -compute_gas_signature - (const struct gas* gas, - const size_t ibands[2], /* Limits are inclusive */ - hash256_T hash) +static INLINE res_T +compute_gas_mesh_signature(const struct gas* gas, hash256_T hash) { - struct sha256_ctx ctx; struct suvm_mesh_desc mesh; - size_t iband; res_T res = RES_OK; - ASSERT(gas && ibands && hash && ibands[0] <= ibands[1]); - - sha256_ctx_init(&ctx); + ASSERT(gas && hash); - /* Hash the mesh */ res = suvm_volume_get_mesh_desc(gas->volume, &mesh); if(res != RES_OK) goto error; res = suvm_mesh_desc_compute_hash(&mesh, hash); if(res != RES_OK) goto error; - sha256_ctx_update(&ctx, hash, sizeof(hash256_T)); - - /* Hash the radiative properties to consider */ - FOR_EACH(iband, ibands[0], ibands[1]+1) { - res = sck_band_compute_hash(gas->ck, iband, hash); - if(res != RES_OK) goto error; - sha256_ctx_update(&ctx, hash, sizeof(hash256_T)); - } - - sha256_ctx_finalize(&ctx, hash); exit: return res; @@ -84,35 +70,17 @@ error: goto exit; } -static res_T -compute_aerosol_signature - (const struct aerosol* aerosol, - const size_t ibands[2], /* Limits are inclusive */ - hash256_T hash) +static INLINE res_T +compute_aerosol_mesh_signature(const struct aerosol* aerosol, hash256_T hash) { - struct sha256_ctx ctx; struct suvm_mesh_desc mesh; - size_t iband; res_T res = RES_OK; - ASSERT(aerosol && ibands && hash && ibands[0] <= ibands[1]); - - sha256_ctx_init(&ctx); + ASSERT(aerosol &&hash); - /* Hash the mesh */ res = suvm_volume_get_mesh_desc(aerosol->volume, &mesh); if(res != RES_OK) goto error; res = suvm_mesh_desc_compute_hash(&mesh, hash); if(res != RES_OK) goto error; - sha256_ctx_update(&ctx, hash, sizeof(hash256_T)); - - /* Hash the radiative properties to consider */ - FOR_EACH(iband, ibands[0], ibands[1]+1) { - res = sars_band_compute_hash(aerosol->sars, iband, hash); - if(res != RES_OK) goto error; - sha256_ctx_update(&ctx, hash, sizeof(hash256_T)); - } - - sha256_ctx_finalize(&ctx, hash); exit: return res; @@ -121,26 +89,23 @@ error: } static res_T -compute_octrees_signature(const struct rnatm* atm, hash256_T signature) +compute_mesh_signature(const struct rnatm* atm, hash256_T mesh_signature) { hash256_T gas_signature; hash256_T* aerosol_signatures = NULL; size_t naerosols; - size_t ibands[2]; size_t i; res_T res = RES_OK; - ASSERT(atm && signature); + ASSERT(atm && mesh_signature); - /* Compute the gas signature for the considered spectral range */ - res = sck_find_bands(atm->gas.ck, atm->spectral_range, ibands); - if(res != RES_OK) goto error; - res = compute_gas_signature(&atm->gas, ibands, gas_signature); + /* Compute the mesh signature of the gas */ + res = compute_gas_mesh_signature(&atm->gas, gas_signature); if(res != RES_OK) goto error; naerosols = darray_aerosol_size_get(&atm->aerosols); if(!naerosols) { - memcpy(signature, gas_signature, sizeof(hash256_T)); + memcpy(mesh_signature, gas_signature, sizeof(hash256_T)); } else { struct sha256_ctx ctx; @@ -149,14 +114,109 @@ compute_octrees_signature(const struct rnatm* atm, hash256_T signature) FOR_EACH(i, 0, naerosols) { const struct aerosol* aerosol = darray_aerosol_cdata_get(&atm->aerosols)+i; - res = sars_find_bands(aerosol->sars, atm->spectral_range, ibands); + + res = compute_aerosol_mesh_signature(aerosol, aerosol_signatures[i]); + if(res != RES_OK) goto error; + } + + /* Pay attention to the order of how aerosol hashes are registered. + * Therefore, the same set of aerosols will have the same signature (i.e. the + * same hash), regardless of the order in which the aerosols were listed in + * the input arguments */ + qsort(aerosol_signatures, naerosols, sizeof(hash256_T), cmp_hash256); + + /* Build the signature for the dataset */ + sha256_ctx_init(&ctx); + sha256_ctx_update(&ctx, gas_signature, sizeof(hash256_T)); + FOR_EACH(i, 0, naerosols) { + sha256_ctx_update(&ctx, aerosol_signatures[i], sizeof(hash256_T)); + } + sha256_ctx_finalize(&ctx, mesh_signature); + } + +exit: + if(aerosol_signatures) MEM_RM(atm->allocator, aerosol_signatures); + return res; +error: + log_err(atm, "mesh signature calculation error -- %s\n", res_to_cstr(res)); + goto exit; +} + +static res_T +compute_aerosol_sars_signature + (const struct aerosol* aerosol, + const double range[2], /* In nm. Limits are inclusive */ + hash256_T hash) +{ + size_t ibands[2]; + res_T res = RES_OK; + ASSERT(aerosol && range && hash && range[0] < range[1]); + + res = sars_find_bands(aerosol->sars, range, ibands); + if(res != RES_OK) goto error; + + /* No band are covered by the spectral range */ + if(ibands[0] > ibands[1]) { + memset(hash, 0, sizeof(hash256_T)); + + /* Hash the data of the covered aerosol bands */ + } else { + struct sha256_ctx ctx; + size_t iband; + + sha256_ctx_init(&ctx); + FOR_EACH(iband, ibands[0], ibands[1]+1) { + res = sars_band_compute_hash(aerosol->sars, iband, hash); if(res != RES_OK) goto error; + sha256_ctx_update(&ctx, hash, sizeof(hash256_T)); + } + sha256_ctx_finalize(&ctx, hash); + } + +exit: + return res; +error: + goto exit; +} + +static res_T +compute_band_signature + (const struct rnatm* atm, + const size_t iband, + hash256_T band_signature) +{ + hash256_T gas_signature; + hash256_T* aerosol_signatures = NULL; + size_t naerosols = 0; + size_t i; + res_T res = RES_OK; + ASSERT(atm && band_signature); + + /* Compute the signature for the gas */ + res = sck_band_compute_hash(atm->gas.ck, iband, gas_signature); + if(res != RES_OK) goto error; + + naerosols = darray_aerosol_size_get(&atm->aerosols); + + if(!naerosols) { + memcpy(band_signature, gas_signature, sizeof(hash256_T)); + } else { + struct sha256_ctx ctx; + struct sck_band band = SCK_BAND_NULL; + double range[2]; - /* Note that if an aerosol had no data for the input spectral range, it - * was suppressed when setting the atmosphere properties */ - ASSERT(ibands[0] <= ibands[1]); + aerosol_signatures = MEM_CALLOC(atm->allocator, naerosols, sizeof(hash256_T)); + if(!aerosol_signatures) { res = RES_MEM_ERR; goto error; } + + /* Retrieve the spectral range of the band */ + SCK(get_band(atm->gas.ck, iband, &band)); + range[0] = band.lower; + range[1] = nextafter(band.upper, DBL_MAX); /* Make limit inclusive */ + + FOR_EACH(i, 0, naerosols) { + const struct aerosol* aerosol = darray_aerosol_cdata_get(&atm->aerosols)+i; - res = compute_aerosol_signature(aerosol, ibands, aerosol_signatures[i]); + res = compute_aerosol_sars_signature(aerosol, range, aerosol_signatures[i]); if(res != RES_OK) goto error; } @@ -172,101 +232,85 @@ compute_octrees_signature(const struct rnatm* atm, hash256_T signature) FOR_EACH(i, 0, naerosols) { sha256_ctx_update(&ctx, aerosol_signatures[i], sizeof(hash256_T)); } - sha256_ctx_finalize(&ctx, signature); + sha256_ctx_finalize(&ctx, band_signature); } exit: if(aerosol_signatures) MEM_RM(atm->allocator, aerosol_signatures); return res; error: - log_err(atm, "signature calculation error -- %s\n", res_to_cstr(res)); + log_err(atm, "signature calculation error for the band %lu -- %s\n", + (unsigned long)iband, res_to_cstr(res)); goto exit; } - /******************************************************************************* * Local functions ******************************************************************************/ res_T -setup_octrees_storage_desc +octrees_storage_desc_init (const struct rnatm* atm, struct octrees_storage_desc* desc) { + size_t iband; + size_t nbands; res_T res = RES_OK; ASSERT(atm && desc); + *desc = OCTREES_STORAGE_DESC_NULL; + desc->version = OCTREES_STORAGE_VERSION; - desc->spectral_range[0] = atm->spectral_range[0]; - desc->spectral_range[1] = atm->spectral_range[1]; + desc->ibands[0] = atm->ibands[0]; + desc->ibands[1] = atm->ibands[1]; desc->optical_thickness = atm->optical_thickness; desc->grid_definition[0] = atm->grid_definition[0]; desc->grid_definition[1] = atm->grid_definition[1]; desc->grid_definition[2] = atm->grid_definition[2]; + desc->allocator = atm->allocator; log_info(atm, "sign octree data\n"); - res = compute_octrees_signature(atm, desc->signature); - if(res != RES_OK) goto error; -exit: - return res; -error: - goto exit; -} + /* Calculate the signature of volumetric meshes */ + res = compute_mesh_signature(atm, desc->mesh_signature); + if(res != RES_OK) goto error; -res_T -write_octrees_storage_desc - (const struct rnatm* atm, - const struct octrees_storage_desc* desc, - FILE* stream) -{ - res_T res = RES_OK; - ASSERT(atm && desc && stream); + /* Allocate the list of band signatures */ + nbands = atm->ibands[1] - atm->ibands[0] + 1; + desc->band_signatures = MEM_CALLOC(desc->allocator, nbands, sizeof(hash256_T)); + if(!desc->band_signatures) { + log_err(atm, "error allocating signatures per band\n"); + res = RES_MEM_ERR; + goto error; + } - #define WRITE(Var, N) { \ - if(fwrite((Var), sizeof(*(Var)), (N), stream) != (N)) { \ - res = RES_IO_ERR; \ - goto error; \ - } \ - } (void)0 - WRITE(&desc->version, 1); - WRITE(desc->spectral_range, 2); - WRITE(&desc->optical_thickness, 1); - WRITE(desc->grid_definition, 3); - WRITE(desc->signature, sizeof(hash256_T)); - #undef WRITE + /* Calculate the signature of spectral bands */ + FOR_EACH(iband, atm->ibands[0], atm->ibands[1]+1) { + const size_t i = iband - atm->ibands[0]; + res = compute_band_signature(atm, iband, desc->band_signatures[i]); + if(res != RES_OK) goto error; + } exit: return res; error: - log_err(atm, "unable to write tree storage descriptor\n"); goto exit; } res_T -octrees_storage_desc_eq - (const struct octrees_storage_desc* desc0, - const struct octrees_storage_desc* desc1) -{ - ASSERT(desc0 && desc1); - return desc0->spectral_range[0] == desc1->spectral_range[0] - && desc0->spectral_range[1] == desc1->spectral_range[1] - && desc0->optical_thickness == desc1->optical_thickness - && desc0->grid_definition[0] == desc1->grid_definition[0] - && desc0->grid_definition[1] == desc1->grid_definition[1] - && desc0->grid_definition[2] == desc1->grid_definition[2] - && hash256_eq(desc0->signature, desc1->signature) - && desc0->version == desc1->version; -} - -res_T -read_octrees_storage_desc +octrees_storage_desc_init_from_stream (const struct rnatm* atm, struct octrees_storage_desc* desc, FILE* stream) { + size_t iband; + size_t nbands; res_T res = RES_OK; ASSERT(atm && desc && stream); + *desc = OCTREES_STORAGE_DESC_NULL; + + desc->allocator = atm->allocator; + #define READ(Var, N) { \ if(fread((Var), sizeof(*(Var)), (N), stream) != (N)) { \ if(feof(stream)) { \ @@ -284,10 +328,23 @@ read_octrees_storage_desc res = RES_BAD_ARG; goto error; } - READ(desc->spectral_range, 2); + READ(desc->ibands, 2); READ(&desc->optical_thickness, 1); READ(desc->grid_definition, 3); - READ(desc->signature, sizeof(hash256_T)); + READ(desc->mesh_signature, sizeof(hash256_T)); + + /* Allocate the list of band signatures */ + nbands = desc->ibands[1] - desc->ibands[0] + 1; + desc->band_signatures = MEM_CALLOC(desc->allocator, nbands, sizeof(hash256_T)); + if(!desc->band_signatures) { + log_err(atm, "error allocating signatures per band\n"); + res = RES_MEM_ERR; + goto error; + } + + FOR_EACH(iband, 0, nbands) { + READ(desc->band_signatures[iband], sizeof(hash256_T)); + } #undef READ exit: @@ -298,6 +355,94 @@ error: goto exit; } +void +octrees_storage_desc_release(struct octrees_storage_desc* desc) +{ + ASSERT(desc); + if(desc->band_signatures) MEM_RM(desc->allocator, desc->band_signatures); +} + +res_T +write_octrees_storage_desc + (const struct rnatm* atm, + const struct octrees_storage_desc* desc, + FILE* stream) +{ + size_t iband; + size_t nbands; + res_T res = RES_OK; + ASSERT(atm && desc && stream); + + nbands = desc->ibands[1] - desc->ibands[0] + 1; + + #define WRITE(Var, N) { \ + if(fwrite((Var), sizeof(*(Var)), (N), stream) != (N)) { \ + res = RES_IO_ERR; \ + goto error; \ + } \ + } (void)0 + WRITE(&desc->version, 1); + WRITE(desc->ibands, 2); + WRITE(&desc->optical_thickness, 1); + WRITE(desc->grid_definition, 3); + WRITE(desc->mesh_signature, sizeof(hash256_T)); + FOR_EACH(iband, 0, nbands) { + WRITE(desc->band_signatures[iband], sizeof(hash256_T)); + } + #undef WRITE + +exit: + return res; +error: + log_err(atm, "unable to write tree storage descriptor\n"); + goto exit; +} + +res_T +check_octrees_storage_compatibility + (const struct octrees_storage_desc* reference, + const struct octrees_storage_desc* challenge) +{ + size_t iband_challenge; + ASSERT(reference && challenge); + + /* Check version equality */ + if(reference->version != challenge->version) + return RES_BAD_ARG; + + /* Verify that the bands in the repository to be challenged are included in + * the bands in the reference repository */ + if(reference->ibands[0] > challenge->ibands[0] + || reference->ibands[1] < challenge->ibands[1]) + return RES_BAD_ARG; + + /* Check the equality of optical thicknesses */ + if(reference->optical_thickness != challenge->optical_thickness) + return RES_BAD_ARG; + + /* Check the equality of grid definitions */ + if(reference->grid_definition[0] != challenge->grid_definition[0] + || reference->grid_definition[1] != challenge->grid_definition[1] + || reference->grid_definition[2] != challenge->grid_definition[2]) + return RES_BAD_ARG; + + /* Check the equality of meshes */ + if(!hash256_eq(reference->mesh_signature, challenge->mesh_signature)) + return RES_BAD_ARG; + + /* Check the equality of the per band data */ + FOR_EACH(iband_challenge, challenge->ibands[0], challenge->ibands[1]+1) { + const size_t ichallenge = iband_challenge - challenge->ibands[0]; + const size_t ireference = iband_challenge - reference->ibands[0]; + hash256_T* band_challenge = challenge->band_signatures+ichallenge; + hash256_T* band_reference = reference->band_signatures+ireference; + if(!hash256_eq(*band_challenge, *band_reference)) + return RES_BAD_ARG; + } + + return RES_OK; +} + res_T reserve_octrees_storage_toc(const struct rnatm* atm, FILE* stream) { @@ -309,10 +454,10 @@ reserve_octrees_storage_toc(const struct rnatm* atm, FILE* stream) noctrees = darray_accel_struct_size_get(&atm->accel_structs); - /* Compute the header size */ + /* Compute the size of TOC */ sizeof_toc = sizeof(size_t)/*#octrees*/ - + sizeof(fpos_t)*noctrees; /* Table of content */ + + noctrees * (sizeof(size_t)/*iband*/+sizeof(size_t)/*iquad*/+sizeof(fpos_t)); ASSERT(sizeof_toc < LONG_MAX); /* Reserve the space for the table of contents */ @@ -348,6 +493,8 @@ write_octrees_storage_toc(const struct rnatm* atm, FILE* stream) FOR_EACH(ioctree, 0, noctrees) { const struct accel_struct* accel_struct = darray_accel_struct_cdata_get(&atm->accel_structs) + ioctree; + WRITE(&accel_struct->iband); + WRITE(&accel_struct->iquad_pt); WRITE(&accel_struct->fpos); } #undef WRITE @@ -363,8 +510,13 @@ error: res_T read_octrees_storage_toc(struct rnatm* atm, FILE* stream) { - size_t noctrees = 0; + struct accel_struct* accel_structs = NULL; size_t ioctree = 0; + size_t noctrees = 0; + size_t ioctree_stream = 0; + size_t noctrees_stream = 0; + size_t iband = 0; + size_t iquad = 0; res_T res = RES_OK; ASSERT(atm && stream); @@ -383,18 +535,53 @@ read_octrees_storage_toc(struct rnatm* atm, FILE* stream) } \ } (void)0 - /* Read the number of toc entries and check that it matches to expected one */ - READ(&noctrees); - if(noctrees != darray_accel_struct_size_get(&atm->accel_structs)) { + accel_structs = darray_accel_struct_data_get(&atm->accel_structs); + noctrees = darray_accel_struct_size_get(&atm->accel_structs); + + /* Read the number of octrees stored in the stream */ + READ(&noctrees_stream); + if(noctrees_stream < noctrees) { res = RES_BAD_ARG; goto error; } - FOR_EACH(ioctree, 0, noctrees) { - struct accel_struct* accel_struct = - darray_accel_struct_data_get(&atm->accel_structs) + ioctree; - READ(&accel_struct->fpos); + + /* Look for the first octree to load for the current atmosphere */ + FOR_EACH(ioctree_stream, 0, noctrees_stream) { + READ(&iband); + READ(&iquad); + READ(&accel_structs[0].fpos); + + if(accel_structs[0].iband == iband + && accel_structs[0].iquad_pt == iquad) + break; } + /* Cannot find the first octree */ + if(ioctree_stream >= noctrees_stream) { + res = RES_BAD_ARG; + goto error; + } + + /* Load the remaining offsets */ + FOR_EACH(ioctree, 1, noctrees) { + + /* No sufficient octrees in the stream */ + if(++ioctree_stream >= noctrees_stream) { + res = RES_BAD_ARG; + goto error; + } + + READ(&iband); + READ(&iquad); + READ(&accel_structs[ioctree].fpos); + + /* Unexpected octrees */ + if(accel_structs[ioctree].iband != iband + || accel_structs[ioctree].iquad_pt != iquad) { + res = RES_BAD_ARG; + goto error; + } + } #undef READ exit: diff --git a/src/rnatm_octrees_storage.h b/src/rnatm_octrees_storage.h @@ -28,38 +28,48 @@ struct rnatm; /* Storage version. It must be incremented and versioning must be performed on * serialized data when its memory layout is updated */ -static const int OCTREES_STORAGE_VERSION = 0; +static const int OCTREES_STORAGE_VERSION = 1; struct octrees_storage_desc { - double spectral_range[2]; /* In nm. Limits are inclusive */ + size_t ibands[2]; /* Indices of the band to handle. Limits are inclusive */ double optical_thickness; /* Threshold used during octree construction */ unsigned grid_definition[3]; - hash256_T signature; /* Signature of the data used to build the octrees */ + /* Signature of the meshes used to build the octrees */ + hash256_T mesh_signature; + hash256_T* band_signatures; /* Per band signature */ + struct mem_allocator* allocator; int version; }; +#define OCTREES_STORAGE_DESC_NULL__ {{0,0},0,{0,0,0},{0},NULL,NULL,0} +static const struct octrees_storage_desc OCTREES_STORAGE_DESC_NULL = + OCTREES_STORAGE_DESC_NULL__; extern LOCAL_SYM res_T -setup_octrees_storage_desc +octrees_storage_desc_init (const struct rnatm* atm, struct octrees_storage_desc* desc); -extern LOCAL_SYM int -octrees_storage_desc_eq - (const struct octrees_storage_desc* desc0, - const struct octrees_storage_desc* desc1); - extern LOCAL_SYM res_T -write_octrees_storage_desc +octrees_storage_desc_init_from_stream (const struct rnatm* atm, - const struct octrees_storage_desc* desc, + struct octrees_storage_desc* desc, FILE* stream); +extern LOCAL_SYM void +octrees_storage_desc_release + (struct octrees_storage_desc* desc); + extern LOCAL_SYM res_T -read_octrees_storage_desc +check_octrees_storage_compatibility + (const struct octrees_storage_desc* reference, + const struct octrees_storage_desc* challenge); + +extern LOCAL_SYM res_T +write_octrees_storage_desc (const struct rnatm* atm, - struct octrees_storage_desc* desc, + const struct octrees_storage_desc* desc, FILE* stream); extern LOCAL_SYM res_T