26 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
27 24, 25, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
28 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
44 {{0.3535533906f, 0.4903926402f, 0.4619397663f, 0.4157348062f, 0.3535533906f, 0.2777851165f, 0.1913417162f, 0.0975451610f}},
45 {{0.3535533906f, 0.4157348062f, 0.1913417162f, -0.0975451610f, -0.3535533906f, -0.4903926402f, -0.4619397663f, -0.2777851165f}},
46 {{0.3535533906f, 0.2777851165f, -0.1913417162f, -0.4903926402f, -0.3535533906f, 0.0975451610f, 0.4619397663f, 0.4157348062f}},
47 {{0.3535533906f, 0.0975451610f, -0.4619397663f, -0.2777851165f, 0.3535533906f, 0.4157348062f, -0.1913417162f, -0.4903926402f}},
48 {{0.3535533906f, -0.0975451610f, -0.4619397663f, 0.2777851165f, 0.3535533906f, -0.4157348062f, -0.1913417162f, 0.4903926402f}},
49 {{0.3535533906f, -0.2777851165f, -0.1913417162f, 0.4903926402f, -0.3535533906f, -0.0975451610f, 0.4619397663f, -0.4157348062f}},
50 {{0.3535533906f, -0.4157348062f, 0.1913417162f, 0.0975451610f, -0.3535533906f, 0.4903926402f, -0.4619397663f, 0.2777851165f}},
51 {{0.3535533906f, -0.4903926402f, 0.4619397663f, -0.4157348062f, 0.3535533906f, -0.2777851165f, 0.1913417162f, -0.0975451610f}},
79 current_profile_count_ = 0U;
81 has_current_bin_ =
false;
82 has_previous_raw_ =
false;
83 previous_raw_.fill(0);
84 for (
auto& bin : bins_) {
87 bin.kendall_order = 0U;
88 bin.kendall_valid = 0U;
90 for (
auto& profile : current_profiles_) {
96 uint64_t timestamp_us,
97 const float* subcarrier_energies =
nullptr,
98 uint8_t subcarrier_count = 0U) {
99 if (!enabled_ || csi_data ==
nullptr || csi_len <
HT20_CSI_LEN) {
102 if (has_previous_raw_ &&
103 std::memcmp(previous_raw_.data(), csi_data,
HT20_CSI_LEN) == 0) {
106 std::memcpy(previous_raw_.data(), csi_data,
HT20_CSI_LEN);
107 has_previous_raw_ =
true;
110 if (!has_current_bin_) {
111 current_bin_ = bin_index;
112 has_current_bin_ =
true;
113 }
else if (bin_index != current_bin_) {
114 finalize_current_bin_();
115 current_bin_ = bin_index;
116 current_profile_count_ = 0U;
122 fill_profile_(csi_data, subcarrier_energies, subcarrier_count,
123 current_profiles_[current_profile_count_]);
124 current_profile_count_++;
130 float& kendall_lag_excess)
const {
134 kendall_lag_excess = 0.0f;
135 std::array<PathPoint, CHANNEL_SHAPE_WINDOW_BINS + 1U> path{};
136 const uint8_t count = build_path_(path);
141 Profile spread_energy{};
142 for (uint8_t i = 1U; i < count; i++) {
143 if (path[i].index - path[i - 1U].index != 1U)
continue;
144 for (uint8_t subband = 0U;
147 for (uint8_t mode = 0U;
149 delta += (path[i].modes[mode] - path[i - 1U].modes[mode]) *
152 spread_energy[subband] += delta * delta;
157 if (count < 3U)
return;
161 uint8_t innovation_count = 0U;
162 uint8_t excess_count = 0U;
163 Profile first_modes = path[0].modes;
164 Profile middle_modes = path[1].modes;
165 for (uint8_t i = 2U; i < count; i++) {
166 const Profile last_modes = path[i].modes;
167 const uint64_t previous_dt = path[i - 1U].index - path[i - 2U].index;
168 const uint64_t current_dt = path[i].index - path[i - 1U].index;
169 float first_norm_squared = 0.0f;
170 float second_norm_squared = 0.0f;
171 float chord_norm_squared = 0.0f;
172 float first_high_squared = 0.0f;
173 float second_high_squared = 0.0f;
174 float chord_high_squared = 0.0f;
175 float innovation_low_squared = 0.0f;
176 float innovation_high_squared = 0.0f;
177 const float ratio = previous_dt > 0U
178 ?
static_cast<float>(current_dt) /
179 static_cast<float>(previous_dt)
182 const float first_delta = middle_modes[j] - first_modes[j];
183 const float second_delta = last_modes[j] - middle_modes[j];
184 const float chord_delta = last_modes[j] - first_modes[j];
185 first_norm_squared += first_delta * first_delta;
186 second_norm_squared += second_delta * second_delta;
187 chord_norm_squared += chord_delta * chord_delta;
189 first_high_squared += first_delta * first_delta;
190 second_high_squared += second_delta * second_delta;
191 chord_high_squared += chord_delta * chord_delta;
193 if (previous_dt > 0U && current_dt > 0U && j > 0U) {
194 const float residual = second_delta - ratio * first_delta;
196 innovation_low_squared += residual * residual;
198 innovation_high_squared += residual * residual;
202 if (previous_dt > 0U && current_dt > 0U) {
203 innovation_samples[innovation_count++] = std::max(
204 0.0f, innovation_low_squared - innovation_high_squared);
207 const float raw_excess = std::sqrt(first_norm_squared) +
208 std::sqrt(second_norm_squared) -
209 std::sqrt(chord_norm_squared);
210 const float high_excess = std::sqrt(first_high_squared) +
211 std::sqrt(second_high_squared) -
212 std::sqrt(chord_high_squared);
213 excess_samples[excess_count++] =
214 std::max(0.0f, raw_excess - std::max(0.0f, high_excess));
215 first_modes = middle_modes;
216 middle_modes = last_modes;
219 median_(innovation_samples.data(), innovation_count);
220 excess_path = median_(excess_samples.data(), excess_count);
223 uint8_t kendall_count = 0U;
224 for (uint8_t i = 3U; i < count; i++) {
225 if (path[i].index - path[i - 3U].index != 3U)
continue;
226 float long_distance = 0.0f;
227 if (!kendall_distance_(path[i], path[i - 3U], long_distance))
continue;
228 float local_sum = 0.0f;
229 bool local_ok =
true;
230 for (uint8_t lag = 0U; lag < 3U; lag++) {
231 float local_distance = 0.0f;
232 if (!kendall_distance_(
233 path[i - lag], path[i - lag - 1U], local_distance)) {
237 local_sum += local_distance;
239 if (!local_ok)
continue;
240 kendall_samples[kendall_count++] =
241 std::max(0.0f, long_distance - local_sum / 3.0f);
243 kendall_lag_excess = median_(kendall_samples.data(), kendall_count);
249 float kendall = 0.0f;
257 float kendall = 0.0f;
263 float innovation = 0.0f;
270 float innovation = 0.0f;
277 float innovation = 0.0f;
285 float innovation = 0.0f;
288 float kendall = 0.0f;
294 using Profile = std::array<float, CHANNEL_SHAPE_SUBBAND_COUNT>;
298 uint32_t kendall_order{0U};
299 uint32_t kendall_valid{0U};
302 static uint8_t popcount32_(uint32_t value) {
304 while (value != 0U) {
305 count =
static_cast<uint8_t
>(count + (value & 1U));
311 static void kendall_signature_(
const Profile& profile, uint32_t& order,
315 float maximum = 0.0f;
316 for (
float value : profile) {
317 if (value > maximum) maximum = value;
319 if (maximum <= 0.0f)
return;
320 const float threshold =
326 const float difference = profile[left] - profile[right];
327 const uint32_t flag = 1U << bit;
328 if (std::fabs(difference) > threshold) {
330 if (difference > 0.0f) order |= flag;
337 static bool kendall_distance_(
const PathPoint& current,
338 const PathPoint& reference,
float& distance) {
339 const uint32_t common = current.kendall_valid & reference.kendall_valid;
340 const uint8_t comparable = popcount32_(common);
345 const uint8_t discordant = popcount32_(
346 (current.kendall_order ^ reference.kendall_order) & common);
347 distance =
static_cast<float>(discordant) /
static_cast<float>(comparable);
351 static float median_(
float* values, uint8_t count) {
352 if (count == 0U)
return 0.0f;
353 std::sort(values, values + count);
354 const uint8_t middle = count / 2U;
355 return count % 2U == 0U ? 0.5f * (values[middle - 1U] + values[middle])
359 static float norm_(
const Profile& values, uint8_t start) {
362 total += values[i] * values[i];
364 return std::sqrt(total);
367 static Profile dct_modes_(
const Profile& values) {
377 static void fill_profile_(
const int8_t* csi_data,
378 const float* subcarrier_energies,
379 uint8_t subcarrier_count,
386 if (subcarrier_energies !=
nullptr && subcarrier < subcarrier_count) {
387 energy = subcarrier_energies[subcarrier];
389 const float imag =
static_cast<float>(csi_data[subcarrier * 2U]);
390 const float real =
static_cast<float>(csi_data[subcarrier * 2U + 1U]);
391 energy = real * real + imag * imag;
396 if (total <= 0.0f)
return;
397 for (
float& value : out) value = std::sqrt(value / total);
400 Profile median_current_profile_()
const {
402 if (current_profile_count_ == 0U)
return result;
405 std::array<float, CHANNEL_SHAPE_MAX_PROFILES_PER_BIN> values{};
406 for (uint8_t i = 0U; i < current_profile_count_; i++) {
407 values[i] = current_profiles_[i][dimension];
409 result[dimension] = median_(values.data(), current_profile_count_);
411 const float length = norm_(result, 0U);
413 for (
float& value : result) value /= length;
418 void finalize_current_bin_() {
419 if (!has_current_bin_ || current_profile_count_ == 0U)
return;
421 for (uint8_t i = 1U; i < bin_count_; i++) bins_[i - 1U] = bins_[i];
424 bins_[bin_count_].index = current_bin_;
425 const Profile profile = median_current_profile_();
426 kendall_signature_(profile, bins_[bin_count_].kendall_order,
427 bins_[bin_count_].kendall_valid);
428 bins_[bin_count_].modes = dct_modes_(profile);
432 void trim_(uint64_t current_bin) {
437 while (first < bin_count_ && bins_[first].index < first_bin) first++;
438 if (first == 0U)
return;
439 for (uint8_t i = first; i < bin_count_; i++) bins_[i - first] = bins_[i];
440 bin_count_ =
static_cast<uint8_t
>(bin_count_ - first);
444 std::array<PathPoint, CHANNEL_SHAPE_WINDOW_BINS + 1U>& path)
const {
445 for (uint8_t i = 0U; i < bin_count_; i++) path[i] = bins_[i];
446 uint8_t count = bin_count_;
447 if (current_profile_count_ > 0U && count < path.size()) {
448 const Profile profile = median_current_profile_();
449 path[count].index = current_bin_;
450 kendall_signature_(profile, path[count].kendall_order,
451 path[count].kendall_valid);
452 path[count].modes = dct_modes_(profile);
458 bool enabled_{
false};
459 std::array<PathPoint, CHANNEL_SHAPE_WINDOW_BINS> bins_{};
460 uint8_t bin_count_{0U};
461 uint64_t current_bin_{0U};
462 bool has_current_bin_{
false};
463 std::array<Profile, CHANNEL_SHAPE_MAX_PROFILES_PER_BIN> current_profiles_{};
464 uint8_t current_profile_count_{0U};
465 std::array<int8_t, HT20_CSI_LEN> previous_raw_{};
466 bool has_previous_raw_{
false};