diff --git a/parcel.h b/parcel.h index 2f37f57..bd2fce0 100644 --- a/parcel.h +++ b/parcel.h @@ -5133,7 +5133,7 @@ static pmd_status read_record_generic(hid_t group_id, const char *name, } } - /* Validate that value is scalar, not an array */ + /* Validate that value holds exactly one element */ hid_t attr_space = H5Aget_space(attr_id); if (attr_space < 0) { H5Aclose(attr_id); @@ -5141,12 +5141,14 @@ static pmd_status read_record_generic(hid_t group_id, const char *name, return PMD_ERROR_HDF5; } - H5S_class_t space_type = H5Sget_simple_extent_type(attr_space); + hssize_t value_points = H5Sget_simple_extent_npoints(attr_space); H5Sclose(attr_space); - if (space_type != H5S_SCALAR) { - /* value must be a scalar, not an array */ - pmd_log(PMD_LOG_ERROR, "read_record_generic: Constant record '%s' has array 'value', expected scalar", name); + /* Parcel supports reading either a scalar dataspace or a single element array (output + * by some codes). Reject anything with none or more than one element here. */ + if (value_points != 1) { + pmd_log(PMD_LOG_ERROR, "read_record_generic: Constant record '%s' has 'value' with %lld elements, expected 1", + name, (long long)value_points); H5Aclose(attr_id); H5Gclose(group_id_local); return PMD_ERROR_FILE_FORMAT; diff --git a/tests/test_read.c b/tests/test_read.c index 8a3c2fc..2c1a320 100644 --- a/tests/test_read.c +++ b/tests/test_read.c @@ -1158,6 +1158,8 @@ void test_padding_mismatch_file_based_root_group(void) { void test_read_bmad_dump(void) { pmd_series *series; pmd_iteration *iter; + pmd_particle_group *pg; + pmd_particle_group_read_info read_info; pmd_status result; int64_t *iterations; int num_iterations; @@ -1182,7 +1184,59 @@ void test_read_bmad_dump(void) { TEST_ASSERT_EQUAL_INT(PMD_SUCCESS, result); TEST_ASSERT_EQUAL_INT64(1000, particle_count); + /* Read the whole electron particle group */ + result = pmd_allocate_particle_group(iter, "electron", &pg); + TEST_ASSERT_EQUAL_INT(PMD_SUCCESS, result); + TEST_ASSERT_NOT_NULL(pg); + TEST_ASSERT_EQUAL_INT64(1000, pg->num_particles); + + result = pmd_read_particle_group(iter, "electron", pg, &read_info); + TEST_ASSERT_EQUAL_INT(PMD_SUCCESS, result); + + /* Positions come straight from the datasets (unitSI is 1.0, metres) */ + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(7.394850528355765e-06, pg->x[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(-0.00026090528338340325, pg->x[999]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(-3.0302666148159694e-05, pg->y[0]); + + /* position/z is a constant record sitting alongside the x and y datasets, so this also + * covers a record group that mixes the two forms. Bmad writes the constants with a one + * element array 'value' rather than a scalar. */ + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(0.0, pg->z[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(0.0, pg->z[999]); + + /* Momenta are scaled by the file's unitSI into eV/c. The values land ~7e-9 above the + * raw file numbers because parcel's CLIGHT is 299792456 rather than 299792458, which is + * why these need the relative tolerance rather than an exact comparison. */ + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(2029.7952043592982, pg->px[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(33557.29782907889, pg->py[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(5002038554.641738, pg->pz[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(5001545000.86001, pg->pz[999]); + + /* The remaining constant records, expanded across every particle */ + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(1.0000000000000002e-12, pg->weight[0]); + TEST_ASSERT_DOUBLE_CLOSE_DEFAULT(1.0000000000000002e-12, pg->weight[999]); + TEST_ASSERT_EQUAL_INT64(1, pg->status[0]); + TEST_ASSERT_EQUAL_INT64(1, pg->status[999]); + + /* pg->t is read but deliberately not asserted: parcel returns the 'time' record on its + * own, while EXT_BeamPhysics.md defines absolute time as time + timeOffset and this + * file carries the reference time in timeOffset. */ + + /* Every optional record the file carries was found */ + TEST_ASSERT_TRUE(read_info.t_present); + TEST_ASSERT_TRUE(read_info.px_present); + TEST_ASSERT_TRUE(read_info.py_present); + TEST_ASSERT_TRUE(read_info.pz_present); + TEST_ASSERT_TRUE(read_info.weight_present); + TEST_ASSERT_TRUE(read_info.status_present); + + /* The file carries no id record, so the index fallback fills it */ + TEST_ASSERT_FALSE(read_info.id_present); + TEST_ASSERT_EQUAL_INT64(0, pg->id[0]); + TEST_ASSERT_EQUAL_INT64(999, pg->id[999]); + /* Clean up */ + pmd_free_particle_group(pg); pmd_close_iteration(iter); free(iterations); pmd_close_series(series);