mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
perf(wifi-densepose-sar): incremental phasor rotation in backprojection (~4.4-4.5x, MEASURED)
focus_at_point called Complex64::from_polar (a sin/cos pair) once per (pose, frequency) term. FrequencySweep::frequencies() produces evenly spaced frequencies by construction, so the per-term phase is an arithmetic progression in the frequency index -- the phasor can be evaluated once per pose and advanced by a fixed complex-multiply step per frequency instead, turning K trig evaluations into 2. focus_at_point's signature changes from a raw &[f64] frequency slice to &FrequencySweep, so the evenly-spaced-frequencies precondition this optimization depends on is a type-level invariant rather than a caller-observed one -- an arbitrary non-uniform frequency list is no longer constructible through this API at all. MEASURED (criterion regression detection, p < 0.001): ~4.4-4.5x faster across 512/4096/32768-voxel grids (300us/1.97ms/14.5ms vs the prior 1.47ms/10.4ms/73.5ms). Proven equivalent, not just faster: a new test independently reimplements the direct per-frequency computation as a reference and checks the optimized path against it across four sweep sizes (incl. the n_steps=1 degenerate case) and both on-target and off-target points, to <1e-9 relative error. 25 tests (22 unit + 3 integration), 0 failed, clippy-clean.
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- **`wifi-densepose-sar` — coherent wideband RF tomography research crate (ADR-283).** New standalone leaf crate (the `nvsim` pattern; zero coupling to `wifi-densepose-hardware` or any real ingestion path) implementing the synthetic-aperture-radar reconstruction primitive a handheld through-wall RF imaging device would need — motivated by comparison against Applied Electrodynamics' "WaveSight" launch, and explicitly scoped below ADR-278's RISE/DiffRadar/GeRaF reproduction gates. Ships: (1) a stepped-frequency, multi-position complex forward measurement simulator (`y_{m,k} = Σ σ_j/R² · exp(-i·4π·f·R/c) + noise`, deterministic ChaCha20 seeding); (2) delay-and-sum backprojection reconstruction onto a 3D voxel grid, rayon-parallelized over voxels; (3) threshold + local-maximum point-cloud extraction; (4) closed-form range/cross-range resolution and antenna-pose coherence-budget formulas (`ΔR=c/2B`, `δ_CR≈λR/2L`, `Δp≤λ/8`) checked against the reconstruction's *actual* behavior in `tests/physics_validation.rs` rather than merely documented — forward-simulating two targets at controlled separations and proving they resolve or merge exactly where the formulas predict, and that reconstructed focus at a known target degrades as injected antenna-pose error grows. Every number is SYNTHETIC/L0 (ADR-282) — no real wideband RF hardware backs this crate; see the crate README and `docs/tutorials/coherent-rf-tomography-backprojection.md` for the full honesty boundary and a worked walkthrough. 24 tests (21 unit + 3 integration), 0 failed, clippy-clean; MEASURED backprojection throughput ~350-450K voxels/sec (criterion, 21 poses × 32 freq steps).
|
||||
- **`wifi-densepose-sar` — coherent wideband RF tomography research crate (ADR-283).** New standalone leaf crate (the `nvsim` pattern; zero coupling to `wifi-densepose-hardware` or any real ingestion path) implementing the synthetic-aperture-radar reconstruction primitive a handheld through-wall RF imaging device would need — motivated by comparison against Applied Electrodynamics' "WaveSight" launch, and explicitly scoped below ADR-278's RISE/DiffRadar/GeRaF reproduction gates. Ships: (1) a stepped-frequency, multi-position complex forward measurement simulator (`y_{m,k} = Σ σ_j/R² · exp(-i·4π·f·R/c) + noise`, deterministic ChaCha20 seeding); (2) delay-and-sum backprojection reconstruction onto a 3D voxel grid, rayon-parallelized over voxels; (3) threshold + local-maximum point-cloud extraction; (4) closed-form range/cross-range resolution and antenna-pose coherence-budget formulas (`ΔR=c/2B`, `δ_CR≈λR/2L`, `Δp≤λ/8`) checked against the reconstruction's *actual* behavior in `tests/physics_validation.rs` rather than merely documented — forward-simulating two targets at controlled separations and proving they resolve or merge exactly where the formulas predict, and that reconstructed focus at a known target degrades as injected antenna-pose error grows. Every number is SYNTHETIC/L0 (ADR-282) — no real wideband RF hardware backs this crate; see the crate README and `docs/tutorials/coherent-rf-tomography-backprojection.md` for the full honesty boundary and a worked walkthrough. `focus_at_point` exploits the evenly-spaced-by-construction frequency sweep (an arithmetic progression in per-term phase) to evaluate each pose's phasor once and advance it by a fixed complex-multiply step per frequency instead of one `sin`/`cos` pair per frequency — **MEASURED ~4.4-4.5x faster** (criterion regression detection, p < 0.001) than the first-shipped direct-computation version, proven equivalent (not just faster) to an independently reimplemented reference across four sweep sizes and on-/off-target points. 25 tests (22 unit + 3 integration), 0 failed, clippy-clean; MEASURED backprojection throughput ~1.7-2.3M voxels/sec (criterion, 21 poses × 32 freq steps).
|
||||
- **HOMECORE platform runtime completion — secure native/Wasmtime plugins, authenticated HAP IP, expanded Home Assistant APIs, durable restoration/migration, and voice protocols.** `homecore-server` now owns deterministic compiled-in native plugin registration plus explicitly configured, path-bounded, Ed25519 publisher-verified Wasm packages executed through Wasmtime with setup/state-change/teardown lifecycle; arbitrary native dynamic libraries remain intentionally unsupported. The optional HAP server implements persisted accessory identity and controller records, SRP-6a Pair-Setup M1–M6, X25519/Ed25519 Pair-Verify M1–M4, HKDF-SHA512/ChaCha20-Poly1305 record framing, authenticated/admin endpoint gates, replay/tamper closure, live entity synchronization, and paired-state `_hap._tcp` mDNS updates (45 focused tests; external Apple certification is not claimed). Startup restores device/entity registries and deterministic latest recorder states before plugins, and migration now atomically preserves forward-compatible device/config-entry fields. The HA-compatible surface adds events, templates, config checks, components, registries, history/logbook with SQL-enforced global response bounds, calendar/camera provider routes, and modern WebSocket negotiation while retaining a machine-readable limitations matrix for integration-specific behavior. Assist adds bounded PCM16, async STT/TTS contracts, an end-to-end speech pipeline, and an authenticated satellite session protocol; real deployments still provide the speech engines.
|
||||
- **`ruview-unified` increment 3 — Gaussian update-loop completion, separable delay-Doppler, and property-tested boundary hardening.** (1) `GaussianMap::merge_overlapping` (ADR-275 step 5: mutual-Mahalanobis + semantic-compatibility dedup catching drift the insert-time gate misses) and lifetime-aware decay (`τ_eff = τ·(1+ln(1+lifetime/τ))` — confirmed structures outlive transients at equal nominal τ). (2) `delay_doppler_map` reimplemented separably (`O(B²S+S²B)`), proven equivalent to the direct reference to <1e-10 and **measured 8.3× faster** (520 µs vs 4.34 ms at 56×8). (3) `tests/security_boundaries.rs` — 8 `proptest` properties over the boundary surfaces (arbitrary values incl. NaN/±inf via `f64::from_bits`) that found and fixed three input-controlled defects: a BLE-CS phase-unwrap infinite loop on non-finite phases and an ~1e299-iteration loop on finite-huge phases (now O(1) modular unwrap + plausibility bound), and a subnormal Gaussian scale overflowing `1/σ²` to NaN density (now physical σ/occupancy bounds). (4) New criterion benches for all increment-2 hot paths (`to_canonical` 38 µs, `ble_cs_range` 481 ns, AoI planner 647 ns/200 regions, coherent fusion 1.5 µs/32 members, factorized pose 521 ns). ruview-unified now 98 tests (87 lib + 3 acceptance + 8 security), 0 failed, clippy-clean.
|
||||
- **`ruview-unified` increment 2 — native frame contract + programmable perception (ADR-279..282).** (1) `RfFrameV2` becomes the authoritative RF record: native complex IQ with explicit validity masks, declared `PhaseState`, TX/RX poses + antenna geometry in one building frame, calibration/quality state, and a provenance rule enforced at construction — `Synthetic ⇒ L0Simulation` and `Measured ⇒ ≥ L1CapturedReplay` can never alias (the public L0–L5 evidence ladder is now a type); the 56-bin canonical tensor is demoted to a derived compatibility view (`to_canonical`, mask-aware gap-filling through the same normalization path as every adapter; native samples proven byte-untouched). (2) Active sensing control plane (`control.rs`): ETSI-ISAC-vocabulary `SensingTask` admission (raw export always refused; identity requires consent), `SensingAction`/`InformationGoal`, an age-of-information `ActiveSensingPlanner` (priority = uncertainty × change rate × criticality ÷ cost; **measured 95% sensing-traffic reduction** vs uniform refresh on a 20-region scenario), fail-closed `CoherentSensorGroup` fusion gates (time/phase/geometry bounds; five denial paths tested), policy-authorized RIS/movable-antenna actuation receipts, and purpose-scoped `TaskSufficientRepresentation` leakage validation. (3) New modality surfaces: BLE Channel Sounding adapter + `ble_cs_range` treating phase-slope and RTT as **separate cross-validated evidence** (exact distance recovery on synthetic tones; relay-style divergence flagged, never averaged), delay-Doppler-native `FieldAxis` + `delay_doppler_map` (unit-peak tone test), IEEE P3162 synthetic-aperture import profile. (4) RePos-factorized pose head (relative skeleton on the content representation, root on the geometry-conditioned one, calibrated per-joint uncertainties): held-out-room MPJPE 0.0003 m vs 0.2534 m for the monolithic baseline in the room-shortcut leakage experiment; ≤2% structured-adapter budget (740 params). (5) Age gate input now `log(1+age_ms)` per the age-aware-CSI recipe (gradient check re-proven); Gaussian primitives gained `first_seen_ns`/`doppler_variance`/bounded `source_receipts` lineage; `PartitionKey` gained a `session` dimension and `SplitManifest` certifies disjointness across all seven dimensions. 87 tests, 0 failed; crate clippy-clean. Docker images unaffected (no shipped binary consumes the crate yet); Python proof re-verified PASS.
|
||||
|
||||
@@ -53,4 +53,10 @@ This is deliberately scoped **one level below** ADR-278's RISE/DiffRadar/GeRaF r
|
||||
|
||||
- The workspace gains a real (if intentionally scoped-down) coherent-imaging primitive where before there was none — useful groundwork for ADR-278 if that research program proceeds, and a direct, honest answer to "could this repo build a WaveSight-like device" (no, not without the hardware program described in the motivating comparison; yes, this is the reconstruction-algorithm groundwork such a program would need).
|
||||
- Zero risk to the existing `wifi-densepose-signal::ruvsense::tomography` (RSS-based RTI) code path or any production pipeline — this crate is not referenced by any of them.
|
||||
- 24 tests (21 unit + 3 integration physics-validation), 0 failed, clippy-clean. Criterion bench: MEASURED 512/4096/32768-voxel backprojection reconstruction throughput (see crate README for the numbers as last recorded).
|
||||
- 25 tests (22 unit + 3 integration physics-validation), 0 failed, clippy-clean. Criterion bench: MEASURED 512/4096/32768-voxel backprojection reconstruction throughput (see crate README for the numbers as last recorded). The incremental-phasor-rotation optimization (§7) cut reconstruction time ~4.4-4.5x, proven equivalent to the direct per-frequency computation it replaced.
|
||||
|
||||
## 7. Follow-up optimization: incremental phasor rotation (2026-07-30, MEASURED)
|
||||
|
||||
`focus_at_point` originally called `Complex64::from_polar` (one `sin`/`cos` pair) per (pose, frequency) term. Since [`FrequencySweep::frequencies`](../../v2/crates/wifi-densepose-sar/src/measurement.rs) produces evenly-spaced frequencies by construction, the per-term phase is an arithmetic progression in the frequency index — so the phasor can be evaluated once per pose and advanced by a fixed complex-multiply step per frequency, replacing K trig evaluations with 2. `focus_at_point`'s signature changed from a raw `&[f64]` frequency slice to `&FrequencySweep`, making the evenly-spaced-frequencies precondition this optimization depends on a type-level invariant rather than a caller-observed one.
|
||||
|
||||
**MEASURED (criterion regression detection, p < 0.001): ~4.4-4.5x faster** across 512/4096/32768-voxel grids. **Proven equivalent**, not just faster: `reconstruct::tests::backprojection_incremental_rotation_matches_direct_per_frequency_computation` checks the optimized path against an independently reimplemented direct per-frequency reference, across four sweep sizes (including the `n_steps=1` degenerate case) and both on-target and off-target evaluation points, to <1e-9 relative error.
|
||||
|
||||
@@ -42,7 +42,7 @@ cargo bench -p wifi-densepose-sar
|
||||
`tests/physics_validation.rs` checks the reconstruction's actual behavior
|
||||
against the closed-form formulas in `resolution.rs` (range resolution,
|
||||
cross-range/synthetic-aperture resolution, and the antenna-pose coherence
|
||||
budget) rather than merely asserting them: 24 tests (21 unit + 3
|
||||
budget) rather than merely asserting them: 25 tests (22 unit + 3
|
||||
integration), 0 failed, clippy-clean.
|
||||
|
||||
## Performance (MEASURED)
|
||||
@@ -53,10 +53,22 @@ machine, release profile:
|
||||
|
||||
| Voxels | Median time | Throughput |
|
||||
|-------:|------------:|-----------:|
|
||||
| 512 | 1.47 ms | ~348,000 voxels/s |
|
||||
| 4,096 | 10.4 ms | ~394,000 voxels/s |
|
||||
| 32,768 | 73.5 ms | ~446,000 voxels/s |
|
||||
| 512 | 300 µs | ~1.71M voxels/s |
|
||||
| 4,096 | 1.97 ms | ~2.08M voxels/s |
|
||||
| 32,768 | 14.5 ms | ~2.26M voxels/s |
|
||||
|
||||
Scales as expected: each voxel's cost is independent (`O(poses × freqs)`
|
||||
per voxel, embarrassingly parallel), so throughput is roughly constant
|
||||
across grid sizes and total time scales linearly with voxel count.
|
||||
|
||||
**Optimization (MEASURED, criterion regression detection, p < 0.001): ~4.4-4.5x
|
||||
faster** than the first-shipped implementation, across all three grid
|
||||
sizes. Frequencies in a [`FrequencySweep`](src/measurement.rs) are evenly
|
||||
spaced by construction, so the per-(pose, frequency) phase term is an
|
||||
arithmetic progression; `focus_at_point` now evaluates the phasor once per
|
||||
pose and advances it by a fixed complex-multiply step per frequency,
|
||||
instead of one `sin`/`cos` pair (`Complex64::from_polar`) per frequency —
|
||||
K trig evaluations become 2. Proven equivalent (not just faster) to an
|
||||
independently-reimplemented direct per-frequency reference in
|
||||
`reconstruct::tests::backprojection_incremental_rotation_matches_direct_per_frequency_computation`,
|
||||
across several sweep sizes and both on-target and off-target points.
|
||||
|
||||
@@ -120,8 +120,7 @@ pub fn backproject(
|
||||
grid: &VoxelGrid,
|
||||
) -> ReflectivityImage {
|
||||
assert_eq!(poses.len(), measurement.n_poses, "pose count must match measurement");
|
||||
let freqs = sweep.frequencies();
|
||||
assert_eq!(freqs.len(), measurement.n_freqs, "frequency count must match measurement");
|
||||
assert_eq!(sweep.n_steps, measurement.n_freqs, "frequency count must match measurement");
|
||||
|
||||
let n = grid.len();
|
||||
|
||||
@@ -130,7 +129,7 @@ pub fn backproject(
|
||||
.map(|linear| {
|
||||
let (i, j, k) = grid.unflatten(linear);
|
||||
let voxel = grid.voxel_center(i, j, k);
|
||||
focus_at_point(measurement, poses, &freqs, &voxel)
|
||||
focus_at_point(measurement, poses, sweep, &voxel)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -143,11 +142,30 @@ pub fn backproject(
|
||||
/// (and tests) can measure focus quality exactly at a location of
|
||||
/// interest -- e.g. a known target position -- rather than only at
|
||||
/// whatever grid points happen to be sampled.
|
||||
pub fn focus_at_point(measurement: &Measurement, poses: &[AntennaPose], freqs: &[f64], point: &Point3) -> f64 {
|
||||
///
|
||||
/// Takes `sweep` rather than a raw frequency slice specifically so the
|
||||
/// evenly-spaced-frequencies guarantee ([`FrequencySweep::frequencies`])
|
||||
/// is a type-level invariant, not a caller-observed precondition: the
|
||||
/// implementation below relies on it (see the comment inside the pose
|
||||
/// loop). Passing an arbitrary non-uniform frequency list is not possible
|
||||
/// through this signature.
|
||||
pub fn focus_at_point(measurement: &Measurement, poses: &[AntennaPose], sweep: &FrequencySweep, point: &Point3) -> f64 {
|
||||
assert_eq!(poses.len(), measurement.n_poses, "pose count must match measurement");
|
||||
assert_eq!(freqs.len(), measurement.n_freqs, "frequency count must match measurement");
|
||||
assert_eq!(sweep.n_steps, measurement.n_freqs, "frequency count must match measurement");
|
||||
|
||||
let n_terms = (measurement.n_poses * measurement.n_freqs) as f64;
|
||||
let k = sweep.n_steps;
|
||||
// Frequencies are evenly spaced by construction: f_kf = start_hz + kf *
|
||||
// delta_f. That makes the per-term phase phase_kf = 4*pi*f_kf*r/c an
|
||||
// arithmetic progression in kf, so instead of K trig evaluations
|
||||
// (Complex64::from_polar per frequency step) the phasor is evaluated
|
||||
// once and advanced by a fixed per-step rotation -- one complex
|
||||
// multiply per step instead of a sin/cos pair. Proven equivalent to
|
||||
// the direct per-frequency computation (independently reimplemented,
|
||||
// not reusing this code) in
|
||||
// `backprojection_incremental_rotation_matches_direct_per_frequency_computation`.
|
||||
let delta_f = if k > 1 { (sweep.stop_hz - sweep.start_hz) / (k - 1) as f64 } else { 0.0 };
|
||||
|
||||
let mut acc = Complex64::new(0.0, 0.0);
|
||||
for (m, pose) in poses.iter().enumerate() {
|
||||
let r = pose.position.distance(point);
|
||||
@@ -155,9 +173,15 @@ pub fn focus_at_point(measurement: &Measurement, poses: &[AntennaPose], freqs: &
|
||||
continue;
|
||||
}
|
||||
let gain_compensation = r * r;
|
||||
for (kf, &f) in freqs.iter().enumerate() {
|
||||
let phase = 4.0 * PI * f * r / SPEED_OF_LIGHT_M_PER_S;
|
||||
acc += measurement.get(m, kf) * gain_compensation * Complex64::from_polar(1.0, phase);
|
||||
let base_phase = 4.0 * PI * sweep.start_hz * r / SPEED_OF_LIGHT_M_PER_S;
|
||||
let step_phase = 4.0 * PI * delta_f * r / SPEED_OF_LIGHT_M_PER_S;
|
||||
let step = Complex64::from_polar(1.0, step_phase);
|
||||
let mut rot = Complex64::from_polar(1.0, base_phase);
|
||||
for kf in 0..k {
|
||||
acc += measurement.get(m, kf) * gain_compensation * rot;
|
||||
if kf + 1 < k {
|
||||
rot *= step;
|
||||
}
|
||||
}
|
||||
}
|
||||
acc.norm() / n_terms
|
||||
@@ -211,4 +235,65 @@ mod tests {
|
||||
|
||||
assert!(peak_mag > mean_mag * 5.0, "coherent focus at the target must dominate the incoherent background: peak={peak_mag}, mean={mean_mag}");
|
||||
}
|
||||
|
||||
/// Independent reference: the direct per-frequency computation
|
||||
/// `focus_at_point` used before the incremental-phasor-rotation
|
||||
/// optimization (one `Complex64::from_polar` per (pose, frequency)
|
||||
/// term, no recurrence). Deliberately reimplemented here rather than
|
||||
/// calling any shared helper, so this test cannot pass by construction.
|
||||
fn focus_at_point_direct_reference(
|
||||
measurement: &Measurement,
|
||||
poses: &[AntennaPose],
|
||||
sweep: &FrequencySweep,
|
||||
point: &Point3,
|
||||
) -> f64 {
|
||||
let freqs = sweep.frequencies();
|
||||
let n_terms = (measurement.n_poses * measurement.n_freqs) as f64;
|
||||
let mut acc = Complex64::new(0.0, 0.0);
|
||||
for (m, pose) in poses.iter().enumerate() {
|
||||
let r = pose.position.distance(point);
|
||||
if r < 1e-6 {
|
||||
continue;
|
||||
}
|
||||
let gain_compensation = r * r;
|
||||
for (kf, &f) in freqs.iter().enumerate() {
|
||||
let phase = 4.0 * PI * f * r / SPEED_OF_LIGHT_M_PER_S;
|
||||
acc += measurement.get(m, kf) * gain_compensation * Complex64::from_polar(1.0, phase);
|
||||
}
|
||||
}
|
||||
acc.norm() / n_terms
|
||||
}
|
||||
|
||||
/// PERF PROOF: the incremental-phasor-rotation `focus_at_point` (2
|
||||
/// trig evaluations/pose instead of K) matches the direct
|
||||
/// per-frequency reference to within f64 rounding, across several
|
||||
/// sweep sizes, ranges, and off-axis points (not just the on-target
|
||||
/// case, where errors could cancel).
|
||||
#[test]
|
||||
fn backprojection_incremental_rotation_matches_direct_per_frequency_computation() {
|
||||
let poses = linear_aperture(Point3::new(-0.7, 0.0, 0.0), Point3::new(0.6, 0.1, 0.0), 17);
|
||||
let targets = vec![
|
||||
ScatteringTarget::new(Point3::new(0.1, 2.3, -0.2), 1.0),
|
||||
ScatteringTarget::new(Point3::new(-0.4, 1.9, 0.3), 0.6),
|
||||
];
|
||||
let test_points = [
|
||||
Point3::new(0.1, 2.3, -0.2), // on a target
|
||||
Point3::new(-0.4, 1.9, 0.3), // on the other target
|
||||
Point3::new(0.0, 2.0, 0.0), // off-target
|
||||
Point3::new(-0.55, 2.6, 0.4), // off-target, far corner
|
||||
];
|
||||
for &(n_steps, start_hz, stop_hz) in &[(1usize, 3.0e9, 3.0e9), (2, 2.0e9, 6.0e9), (8, 1.0e9, 9.0e9), (64, 2.4e9, 2.5e9)] {
|
||||
let sweep = FrequencySweep::new(start_hz, stop_hz, n_steps);
|
||||
let measurement = simulate_measurement(&poses, &sweep, &targets, 0.0, 42);
|
||||
for point in test_points {
|
||||
let fast = focus_at_point(&measurement, &poses, &sweep, &point);
|
||||
let reference = focus_at_point_direct_reference(&measurement, &poses, &sweep, &point);
|
||||
let scale = reference.max(1e-12);
|
||||
assert!(
|
||||
(fast - reference).abs() / scale < 1e-9,
|
||||
"incremental rotation diverged from the direct reference at n_steps={n_steps}, point={point:?}: fast={fast}, reference={reference}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@ fn phase_error_from_pose_jitter_degrades_focus_beyond_pose_budget() {
|
||||
.collect()
|
||||
};
|
||||
|
||||
let freqs = sweep.frequencies();
|
||||
let focus_at_epsilon = |epsilon: f64| -> f64 {
|
||||
let true_poses = jittered_poses_at(epsilon);
|
||||
// The measurement is recorded at the (jittered) TRUE antenna
|
||||
@@ -191,7 +190,7 @@ fn phase_error_from_pose_jitter_degrades_focus_beyond_pose_budget() {
|
||||
// happens to focus slightly better and mask the coherence loss
|
||||
// this test is measuring).
|
||||
let measurement = simulate_measurement(&true_poses, &sweep, &[target], 0.0, 1);
|
||||
focus_at_point(&measurement, &nominal_poses, &freqs, &target.position)
|
||||
focus_at_point(&measurement, &nominal_poses, &sweep, &target.position)
|
||||
};
|
||||
|
||||
let levels = [0.0, 0.5 * budget, budget, 2.0 * budget, 4.0 * budget];
|
||||
|
||||
Reference in New Issue
Block a user