diff --git a/include/schaapcommon/h5parm/soltab.h b/include/schaapcommon/h5parm/soltab.h index 4860ae623f4d190ce492edff681522913222d7fa..bf93ed6ed29bea007f07758a06e812a1f0428620 100644 --- a/include/schaapcommon/h5parm/soltab.h +++ b/include/schaapcommon/h5parm/soltab.h @@ -29,6 +29,30 @@ struct TimesAndFrequencies { size_t num_freqs; }; +/** + * Determine the axis values (e.g. times) that fall within a requested + * range. The unit tests give a number of examples which may help + * understanding. + * @param axis a sorted non-empty vector with axis values. This vector is + * reused as return value to avoid an allocation. + * @param requested_start First axis value that is needed. + * @param requested_end Last axis value that is needed. + * @param nearest If @c true, the requested values are rounded to the nearest + * axis values. If @c false, it is assumed that interpolation is required. + * This makes sure that there are samples available at the beginning and the + * end of the result for interpolation. Particularly, the first selected + * value is equal or lower than @p requested_start, and the last + * selected value is equal or higher than @p requested_end, if such values + * exist. Otherwise, the most extreme values are returned. + * @param [out] start_index index of the first axis value returned. + * @returns sorted vector with axis values that correspond with the requested + * selection. + */ +std::vector<double> SelectAxisValues(std::vector<double>&& axis, + double requested_start, + double requested_end, bool nearest, + size_t& start_index); + /// @brief SolTab is a solution table as defined in the H5Parm standard. It /// contains one solution, e.g. all TEC values, with different axes /// for that solution (e.g. time, freq, pol). diff --git a/src/h5parm/jonesparameters.cc b/src/h5parm/jonesparameters.cc index 438f94f0aca2327048c562ec289d52fba2cfd81b..119c4c8a5077bd21f961771c97069eb31b202fff 100644 --- a/src/h5parm/jonesparameters.cc +++ b/src/h5parm/jonesparameters.cc @@ -79,6 +79,7 @@ JonesParameters::JonesParameters( if (parm_size == 0U) { parm_size = GetNParmValues(gain_type); } + // Indexed as parm_values[parameter][antenna][time-frequency index] std::vector<std::vector<std::vector<double>>> parm_values(parm_size); for (auto& parm_values : parm_values) { parm_values.resize(antenna_names.size()); diff --git a/src/h5parm/soltab.cc b/src/h5parm/soltab.cc index 0826c840518a91c82d34749a195c3635f7da3e8b..36e568316b88ba2b85599585c78deb8ed2bc8eca 100644 --- a/src/h5parm/soltab.cc +++ b/src/h5parm/soltab.cc @@ -39,6 +39,51 @@ std::vector<std::string> Tokenize(const std::string& str, } } // namespace +std::vector<double> SelectAxisValues(std::vector<double>&& axis, + double requested_start, + double requested_end, bool nearest, + size_t& start_index) { + assert(!axis.empty()); + assert(std::is_sorted(axis.cbegin(), axis.cend())); + assert(requested_start <= requested_end); + // ReadAxes() already checked that the time axis is sorted. + std::vector<double>::const_iterator lower = + std::lower_bound(axis.cbegin(), axis.cend(), requested_start); + std::vector<double>::const_iterator upper; + if (nearest) { + upper = std::upper_bound(lower, axis.cend(), requested_end); + // move upper to nearest element + if (upper > axis.cbegin() && upper != axis.cend()) { + if (requested_end - *(upper - 1) < *upper - requested_end) { + --upper; + } + } + // move lower to the nearest element + if (lower > axis.cbegin()) { + if (lower == axis.cend() || + requested_start - *(lower - 1) < *lower - requested_start) { + --lower; + } + } + } else { + upper = std::lower_bound(lower, axis.cend(), requested_end); + // lower may be larger than the requested value; if this case move lower + // one value back. + if (lower > axis.cbegin() && + (lower == axis.cend() || *lower > requested_start)) { + --lower; + } + } + // upper must point to the element after the element that is nearest/bounded: + if (upper != axis.cend()) { + ++upper; + } + start_index = lower - axis.cbegin(); + axis.assign(lower, upper); + assert(!axis.empty()); + return std::move(axis); +} + SolTab::SolTab(H5::Group group, const std::string& type, const std::vector<AxisInfo>& axes) : H5::Group(group), type_(type), axes_(axes) { @@ -333,64 +378,25 @@ TimesAndFrequencies SolTab::GetTimesAndFrequencies( assert(!times.empty()); assert(!frequencies.empty()); - TimesAndFrequencies times_and_frequencies; - times_and_frequencies.time_axis = {0.0}; - times_and_frequencies.start_time_index = 0; - times_and_frequencies.num_times = 1; - times_and_frequencies.freq_axis = {0.0}; - times_and_frequencies.start_freq_index = 0; - times_and_frequencies.num_freqs = 1; + TimesAndFrequencies tf; + tf.time_axis = {0.0}; + tf.start_time_index = 0; + tf.freq_axis = {0.0}; + tf.start_freq_index = 0; + tf.num_freqs = 1; if (HasAxis("time")) { - times_and_frequencies.time_axis = GetRealAxis("time"); - if (times.size() > 1) { - times_and_frequencies.num_times = times_and_frequencies.time_axis.size(); - } else { - // When only one time is requested, find the nearest H5 time indices - // and only read H5 data for those indices. - - // ReadAxes() already checked that the "time" axis is sorted. - const auto lower = std::lower_bound( - times_and_frequencies.time_axis.begin(), - times_and_frequencies.time_axis.end(), times.front()); - if (lower == times_and_frequencies.time_axis.begin()) { - // times_and_frequencies.start_time_index remains 0 - times_and_frequencies.time_axis.resize(1); // Keep the first item only. - } else if (lower == times_and_frequencies.time_axis.end()) { - times_and_frequencies.start_time_index = - times_and_frequencies.time_axis.size() - 1; - times_and_frequencies.time_axis = { - times_and_frequencies.time_axis.back()}; - } else { - // The time lies between *(lower-1) and *lower. - times_and_frequencies.start_time_index = - std::distance(times_and_frequencies.time_axis.begin(), lower) - 1; - if (nearest) { - // Only use the nearest entry. - if (times.front() - *(lower - 1) > *lower - times.front()) { - ++times_and_frequencies.start_time_index; - } - times_and_frequencies.time_axis = { - times_and_frequencies - .time_axis[times_and_frequencies.start_time_index]}; - } else { - // Only load the two entries around the entry. - times_and_frequencies.num_times = 2; - times_and_frequencies.time_axis = {*(lower - 1), *lower}; - } - } - } + tf.time_axis = SelectAxisValues(GetRealAxis("time"), times.front(), + times.back(), nearest, tf.start_time_index); } + tf.num_times = tf.time_axis.size(); if (HasAxis("freq")) { std::vector<double> full_freq_axis_h5 = GetRealAxis("freq"); - times_and_frequencies.start_freq_index = GetFreqIndex(frequencies.front()); - times_and_frequencies.num_freqs = GetFreqIndex(frequencies.back()) - - times_and_frequencies.start_freq_index + - 1; - times_and_frequencies.freq_axis = std::vector<double>( - full_freq_axis_h5.begin() + times_and_frequencies.start_freq_index, - full_freq_axis_h5.begin() + times_and_frequencies.start_freq_index + - times_and_frequencies.num_freqs); + tf.start_freq_index = GetFreqIndex(frequencies.front()); + tf.num_freqs = GetFreqIndex(frequencies.back()) - tf.start_freq_index + 1; + tf.freq_axis = std::vector<double>( + full_freq_axis_h5.begin() + tf.start_freq_index, + full_freq_axis_h5.begin() + tf.start_freq_index + tf.num_freqs); } if (HasAxis("pol")) { const size_t num_pol_h5 = GetAxis("pol").size; @@ -402,7 +408,7 @@ TimesAndFrequencies SolTab::GetTimesAndFrequencies( } } - return times_and_frequencies; + return tf; } std::vector<double> SolTab::GetSubArray( diff --git a/src/h5parm/test/tsoltab.cc b/src/h5parm/test/tsoltab.cc index bf78cc4c39779945f5a29624edcdbe5e998f84e0..6bb1035e4fa4b9fae0f455a1198751333620a769 100644 --- a/src/h5parm/test/tsoltab.cc +++ b/src/h5parm/test/tsoltab.cc @@ -10,6 +10,7 @@ #include <H5Cpp.h> using schaapcommon::h5parm::AxisInfo; +using schaapcommon::h5parm::SelectAxisValues; using schaapcommon::h5parm::SolTab; namespace { @@ -188,4 +189,141 @@ BOOST_FIXTURE_TEST_CASE(set_antennas, H5Fixture) { BOOST_TEST(soltab.GetAntIndex(kNewNames[0]) == 0); } +BOOST_AUTO_TEST_CASE(select_axis_with_nearest_single_value) { + auto CheckSingle = [](double search_value, double expected_value, + size_t expected_index) { + size_t start_index; + constexpr bool kNearest = true; + const std::vector<double> result = + SelectAxisValues({4.0, 8.0, 12.0, 16.0, 20.0}, search_value, + search_value, kNearest, start_index); + BOOST_TEST(result == std::vector<double>{expected_value}, + boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(start_index, expected_index); + }; + + // Exact matches + CheckSingle(4.0, 4.0, 0); + CheckSingle(16.0, 16.0, 3); + CheckSingle(20.0, 20.0, 4); + + // Rounding + CheckSingle(5.0, 4.0, 0); + CheckSingle(14.1, 16.0, 3); + CheckSingle(17.9, 16.0, 3); + CheckSingle(19.0, 20.0, 4); + + // Boundaries + CheckSingle(3.0, 4.0, 0); + CheckSingle(21.0, 20.0, 4); +} + +BOOST_AUTO_TEST_CASE(select_axis_with_surrounding_single_value) { + auto CheckSingle = [](double search_value, + const std::vector<double>& expected_values, + size_t expected_index) { + size_t start_index; + constexpr bool kNearest = false; + const std::vector<double> result = + SelectAxisValues({4.0, 8.0, 12.0, 16.0, 20.0}, search_value, + search_value, kNearest, start_index); + BOOST_TEST(result == expected_values, boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(start_index, expected_index); + }; + + // Exact matches + CheckSingle(4.0, {4.0}, 0); + CheckSingle(16.0, {16.0}, 3); + CheckSingle(20.0, {20.0}, 4); + + // Not exact matches + CheckSingle(5.0, {4.0, 8.0}, 0); + CheckSingle(14.1, {12.0, 16.0}, 2); + CheckSingle(17.9, {16.0, 20.0}, 3); + CheckSingle(19.0, {16.0, 20.0}, 3); + + // Boundaries + CheckSingle(3.0, {4.0}, 0); + CheckSingle(21.0, {20.0}, 4); +} + +BOOST_AUTO_TEST_CASE(select_axis_with_nearest_multiple_values) { + auto Check = [](double requested_start, double requested_end, + const std::vector<double>& expected_values, + size_t expected_index) { + size_t start_index; + constexpr bool kNearest = true; + const std::vector<double> result = + SelectAxisValues({4.0, 8.0, 12.0, 16.0, 20.0}, requested_start, + requested_end, kNearest, start_index); + BOOST_TEST(result == expected_values, boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(start_index, expected_index); + }; + + // Exact matches + Check(4.0, 8.0, {4.0, 8.0}, 0); + Check(8.0, 16.0, {8.0, 12.0, 16.0}, 1); + Check(16.0, 20.0, {16.0, 20.0}, 3); + Check(4.0, 20.0, {4.0, 8.0, 12.0, 16.0, 20.0}, 0); + + // Rounding + // Both from lower + Check(7.0, 7.5, {8.0}, 1); + Check(7.0, 11.0, {8.0, 12.0}, 1); + // First from lower, last from higher + Check(7.0, 17.0, {8.0, 12.0, 16.0}, 1); + Check(7.0, 9.0, {8.0}, 1); + Check(7.0, 17.0, {8.0, 12.0, 16.0}, 1); + // First from higher, last from lower + Check(9.0, 15.0, {8.0, 12.0, 16.0}, 1); + Check(5.0, 17.0, {4.0, 8.0, 12.0, 16.0}, 0); + // First from higher, last from higher + Check(13.0, 17.0, {12.0, 16.0}, 2); + Check(5.0, 17.0, {4.0, 8.0, 12.0, 16.0}, 0); + + // Boundaries + Check(3.0, 21.0, {4.0, 8.0, 12.0, 16.0, 20.0}, 0); + Check(3.0, 7.0, {4.0, 8.0}, 0); + Check(3.0, 4.0, {4.0}, 0); + Check(16.0, 21.0, {16.0, 20.0}, 3); + Check(17.0, 21.0, {16.0, 20.0}, 3); + Check(20.0, 21.0, {20.0}, 4); +} + +BOOST_AUTO_TEST_CASE(select_axis_with_surrounding_multiple_values) { + auto Check = [](double requested_start, double requested_end, + const std::vector<double>& expected_values, + size_t expected_index) { + size_t start_index; + constexpr bool kNearest = false; + const std::vector<double> result = + SelectAxisValues({4.0, 8.0, 12.0, 16.0, 20.0}, requested_start, + requested_end, kNearest, start_index); + BOOST_TEST(result == expected_values, boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(start_index, expected_index); + }; + + // Exact matches + Check(4.0, 8.0, {4.0, 8.0}, 0); + Check(8.0, 16.0, {8.0, 12.0, 16.0}, 1); + Check(16.0, 20.0, {16.0, 20.0}, 3); + Check(4.0, 20.0, {4.0, 8.0, 12.0, 16.0, 20.0}, 0); + + // Not exact matches + Check(7.0, 7.5, {4.0, 8.0}, 0); + Check(7.0, 11.0, {4.0, 8.0, 12.0}, 0); + Check(7.0, 17.0, {4.0, 8.0, 12.0, 16.0, 20.0}, 0); + Check(9.0, 15.0, {8.0, 12.0, 16.0}, 1); + Check(13.0, 17.0, {12.0, 16.0, 20.0}, 2); + Check(17.0, 19.0, {16.0, 20.0}, 3); + + // Boundaries + Check(3.0, 21.0, {4.0, 8.0, 12.0, 16.0, 20.0}, 0); + Check(3.0, 7.0, {4.0, 8.0}, 0); + Check(3.0, 4.0, {4.0}, 0); + Check(16.0, 21.0, {16.0, 20.0}, 3); + Check(17.0, 21.0, {16.0, 20.0}, 3); + Check(20.0, 21.0, {20.0}, 4); +} + BOOST_AUTO_TEST_SUITE_END()