Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
9cd8b6d
feat: add optimal P estimation with physics-aware recommendations
nerdCopter Jan 21, 2026
dcaeb47
refine: clarify optimal P estimation behavior and relationship to P:D…
nerdCopter Jan 21, 2026
f585ff1
docs: add optimal P estimation to README, OVERVIEW, and help text
nerdCopter Jan 21, 2026
580acbe
feat: extend frame class support to 1-13 inch props
nerdCopter Jan 21, 2026
603729f
feat: add optimal P estimation to step-response PNG
nerdCopter Jan 21, 2026
9016df1
fix: correct physics theory, tolerance values, and divide-by-zero issues
nerdCopter Jan 21, 2026
6eafe94
refactor: consolidate TD target constants into single data structure
nerdCopter Jan 21, 2026
31b586b
refactor: consolidate TD targets and improve optimal P estimation
nerdCopter Jan 21, 2026
4727baa
feat: implement D-term noise analysis for optimal P estimation
nerdCopter Jan 22, 2026
8518729
refactor: compact optimal P estimation console output
nerdCopter Jan 22, 2026
321c9a1
docs: clarify frame-class matches prop size, not frame size
nerdCopter Jan 22, 2026
48e1d38
refactor: rename --frame-class to --prop-size and fix low-noise logic
nerdCopter Jan 22, 2026
afd9425
docs: add validation methodology and update implementation status
nerdCopter Jan 22, 2026
13b2718
feat: add noise and consistency info to step response PNG legends
nerdCopter Jan 22, 2026
4fdffaf
refactor: use text-based warnings and spell out Recommendation fully
nerdCopter Jan 22, 2026
63b00d3
feat: add D recommendations for optimal P changes and standardize format
nerdCopter Jan 22, 2026
9db66a8
fix: use recommended P:D ratio for D calculations and use approx symbol
nerdCopter Jan 22, 2026
4b7ff39
docs: improve consistency warning clarity in PNG legends
nerdCopter Jan 22, 2026
477bc08
refactor: simplify optimal-P to show only conservative recommendation
nerdCopter Jan 22, 2026
16c9c34
refactor: simplify to conservative-only recommendations
nerdCopter Jan 22, 2026
c67abad
refactor: remove truly dead code from optimal P analysis
nerdCopter Jan 22, 2026
1dd3a41
fix: restore Conservative label in recommendations
nerdCopter Jan 22, 2026
b346cb8
fix: correct flag name in help text and documentation (CodeRabbit)
nerdCopter Jan 22, 2026
4d1b4ce
fix: add division-by-zero guards for D gain calculations (CodeRabbit)
nerdCopter Jan 22, 2026
ccecf74
refactor: move hardcoded constants to constants.rs (CodeRabbit)
nerdCopter Jan 22, 2026
c008b3d
fix: address CodeRabbit nitpicks (constant naming and consistency check)
nerdCopter Jan 22, 2026
a5d969a
fix: replace hardcoded axis range with constant (CodeRabbit)
nerdCopter Jan 22, 2026
1fd9b4d
feat: extend prop size range from 13 to 15 inches
nerdCopter Jan 23, 2026
e776643
Update prop size error message range from 1-13 to 1-15
nerdCopter Jan 24, 2026
209e6e0
feat: add optimal P estimation with empirical frame-class targets
nerdCopter Jan 27, 2026
4da8784
Merge feature/remove-unused-physics-params into feature/optimal-p-est…
nerdCopter Jan 27, 2026
a805d64
fix: add explicit error logging in calculate_hf_energy_ratio
nerdCopter Jan 27, 2026
6cca6f8
docs: explain non-monotonic TD_TARGETS rationale
nerdCopter Jan 27, 2026
0045c0f
docs: explain asymmetric Td deviation thresholds
nerdCopter Jan 27, 2026
5d4bd55
docs: fix grammar in plot flags usage sentence
nerdCopter Jan 27, 2026
81eeb5a
docs: reorganize theory foundation into three clear sections
nerdCopter Jan 27, 2026
7281392
docs: use durable pointer for TD_TARGETS in OVERVIEW
nerdCopter Jan 27, 2026
44e5b0c
docs: clarify relationship between user acceptance ranges and validat…
nerdCopter Jan 27, 2026
330fbc7
refactor: remove trivial seconds->ms constant and inline 1000.0
nerdCopter Jan 27, 2026
786f08f
style: import FrameClass and simplify AnalysisOptions type
nerdCopter Jan 27, 2026
affddfa
refactor: extract common PdRecommendations struct
nerdCopter Jan 27, 2026
1c90fb5
docs: add justification for too_many_arguments allow
nerdCopter Jan 27, 2026
b253313
fix: include current P value in Optimal recommendation output
nerdCopter Jan 27, 2026
40948ee
docs: correct Y-axis padding comment (0.55 -> ~5% per side, 10% total)
nerdCopter Jan 27, 2026
baebbdb
refactor: extract D recommendation calculation into helper closure
nerdCopter Jan 27, 2026
d5b597a
refactor: make FrameClass::td_target return Option and handle missing…
nerdCopter Jan 27, 2026
820a72d
refactor: remove unused std_dev_ms field from TdStatistics
nerdCopter Jan 27, 2026
621d507
refactor: add safe_scaled_p helper to prevent u32 overflow in P calcu…
nerdCopter Jan 27, 2026
9ef2d2b
refactor: use safe_scaled_p in SignificantlyFaster+Low case
nerdCopter Jan 27, 2026
874fe43
refactor: use AXIS_NAMES constant in axis_name function
nerdCopter Jan 27, 2026
a5dadc9
test: add TdTargetSpec valid range unit test
nerdCopter Jan 27, 2026
25b7c4a
docs: clarify --prop-size is only used with --estimate-optimal-p
nerdCopter Jan 27, 2026
eeed531
refactor: add derives and type safety to plot_step_response structs
nerdCopter Jan 27, 2026
bdd9ced
refactor: add warning when --prop-size doesn't map to valid frame class
nerdCopter Jan 27, 2026
8a435d8
refactor: store actual Td target/tolerance in OptimalPAnalysis
nerdCopter Jan 27, 2026
eea2fa6
refactor: eliminate duplicate process_response call for combined resp…
nerdCopter Jan 27, 2026
8ca422a
fix: explicit NaN handling in safe_scaled_p
nerdCopter Jan 27, 2026
f1e36cc
refactor: change coefficient_of_variation to Option<f64>
nerdCopter Jan 27, 2026
9cdecaa
refactor: replace stderr with Result<T, AnalysisError> in analyze()
nerdCopter Jan 27, 2026
c36f249
fix: remove unused frame_class field from AnalysisError
nerdCopter Jan 27, 2026
d2aeae0
docs: update README example to use float for --prop-size
nerdCopter Jan 27, 2026
d2d7048
refactor: change --prop-size from float to integer only
nerdCopter Jan 27, 2026
f8ca68c
docs: clarify that --prop-size requires --estimate-optimal-p to take …
nerdCopter Jan 27, 2026
0d3a77b
docs: sync --help and OVERVIEW wording with integer-only prop-size an…
nerdCopter Jan 27, 2026
4691b99
docs: clarify --prop-size accepts integers only; decimals rejected (n…
nerdCopter Jan 27, 2026
2305c9d
refactor: simplify consistency calculation with map_or
nerdCopter Jan 27, 2026
074a803
fix: rename _td_tolerance_ms to td_tolerance_ms
nerdCopter Jan 27, 2026
273bc6a
refactor: require --prop-size when --estimate-optimal-p is used
nerdCopter Jan 27, 2026
dd771cd
fix: remove dead code typical_weight_g from TdTargetSpec
nerdCopter Jan 27, 2026
c65460c
docs: rephrase Td formula as heuristic with explicit torque definition
nerdCopter Jan 27, 2026
a57225c
refactor: implement Deref for ConservativeRecommendations and Moderat…
nerdCopter Jan 27, 2026
c1c4209
docs: clarify acronyms Td and CV in README
nerdCopter Jan 27, 2026
150bdf0
docs: simplify --prop-size help and docs; remove redundant 'decimals …
nerdCopter Jan 27, 2026
84a56e2
refactor: centralize domain constants to src/constants.rs
nerdCopter Jan 28, 2026
cc34292
fix(help): normalize README and CLI help section labels and layout
nerdCopter Apr 1, 2026
dc753b6
fix: add optimal P estimation options to help text only
nerdCopter Apr 2, 2026
e8894ab
Merge branch 'master' into feature/optimal-p-estimation
nerdCopter Apr 2, 2026
92b9dec
Merge branch 'master' into feature/optimal-p-estimation
nerdCopter May 11, 2026
4f91686
fix: address final CodeRabbit review feedback (help text, constants, …
nerdCopter May 11, 2026
45a2dba
fix: clarify prop-size message (required, not optional with default)
nerdCopter May 11, 2026
0dea308
fix: De Morgan condition, test constants, README example, condense OV…
nerdCopter May 11, 2026
5cfcdfb
fix: explicit NaN guard in hf_cutoff validation (De Morgan + is_nan)
nerdCopter May 11, 2026
3606bcf
refactor: purge dead code and transition to physics-derived Td targets
nerdCopter May 18, 2026
cea9980
feat: add achievability factor and improve skip-reason reporting in p…
nerdCopter May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Implementation Details](#implementation-details)
- [Filter Response Curves](#filter-response-curves)
- [Bode Plot Analysis (Optional)](#bode-plot-analysis-optional)
- [Optimal P Estimation (Optional, Experimental)](#optimal-p-estimation-optional-experimental)
- [Step-Response Comparison with Other Analysis Tools](#step-response-comparison-with-other-analysis-tools)
- [Compared to PIDtoolbox/Matlab (PTstepcalc.m)](#compared-to-pidtoolboxmatlab-ptstepcalcm)
- [Compared to PlasmaTree/Python (PID-Analyzer.py)](#compared-to-plasmatreepython-pid-analyzerpy)
Expand Down Expand Up @@ -189,6 +190,66 @@ The system provides intelligent P:D tuning recommendations based on step-respons
- Shows recommendations only when the step response needs improvement (skips optimal peak 0.95–1.04)
- **Note:** Peak value measures the first maximum after crossing the setpoint; the initial transient dip is normal system behavior

#### Optimal P Estimation (Optional, Experimental)

Physics-aware P gain optimization based on response timing analysis:

- **Activation:** Disabled by default; enable with `--estimate-optimal-p` flag
- **⚠️ Status:** This feature is **experimental**. Frame-class Td targets are provisional empirical estimates requiring flight validation. Use as initial guidelines only; validation data collection is ongoing.
- **Prop Size Selection:** Use `--prop-size <size>` to specify **propeller diameter** in inches (1-15, integer values only). **This flag is required when `--estimate-optimal-p` is used.**
- **Critical:** Must match your actual prop size exactly (e.g., 6" frame with 5" props → use `--prop-size 5`)
- Supports whole numbers 1 through 15 only
- **No default is assumed** — you must explicitly specify the prop size. This prevents misleading recommendations when the wrong default is used.
- Prop size is a proxy for rotational inertia (I ∝ mass × radius²) which directly affects response time
- Each prop size has empirically-derived Td (time to 50%) targets based on observed flight data
- **Theory Foundation:** Based on BrianWhite's (PIDtoolbox author) insight that optimal response timing is aircraft-specific, not universal.
- **Theoretical Principle:** The relationship between response time and rotational inertia is approximate heuristic expressed as **Td ∝ √(I/τ)**, where I is the total rotational inertia (moment of inertia) of the airframe and τ is the available motor torque at the operating point (accounting for battery voltage, propeller aerodynamic load, and ESC response characteristics). This heuristic suggests that faster-rotating airframes (lower I) or higher available torque achieve quicker response times, while heavier/larger frames (higher I) or lower available torque naturally respond more slowly.
- **Real-World Factors:** In practice, Td is modified by many physical parameters beyond this simplified heuristic: mass distribution across the frame, motors, battery, and propeller placement; motor torque characteristics and efficiency across throttle range; propeller aerodynamic loading and blade pitch; battery voltage and sag during maneuvers; and ESC throttle response lag. Rotational inertia (influenced by mass × radius²) and propeller size both contribute significantly to these variations.
- **Empirical Approach:** The frame-class targets below are **empirical estimates derived from flight data**, not pure physics calculations. Propeller size is used as a practical proxy for rotational inertia because it correlates strongly with frame mass and arm length. Targets must be validated against actual flight logs for each specific build configuration, as the theoretical model cannot account for all real-world complexities.
- **Frame-Class Targets (Provisional — `TD_TARGETS` in `src/constants.rs` is authoritative):**
- Targets below are ±25% acceptance bands for pilots. If measured Td falls within target ± tolerance, the tune is acceptable. These are NOT measurement uncertainty values.

| Prop Size | Frame Type | Target Td | Tolerance |
|-----------|------------|-----------|-----------|
| 1" | tiny whoop | 40 ms | ± 10.0 ms |
| 2" | micro | 35 ms | ± 8.75 ms |
| 3" | toothpick/cinewhoop | 30 ms | ± 7.5 ms |
| 4" | racing | 25 ms | ± 6.25 ms |
| 5" | freestyle/racing | 20 ms | ± 5.0 ms |
| 6" | long-range | 28 ms | ± 7.0 ms |
| 7" | long-range | 37.5 ms | ± 9.375 ms |
| 8" | long-range | 47 ms | ± 11.75 ms |
| 9" | cinelifter | 56 ms | ± 14.0 ms |
| 10" | cinelifter | 65 ms | ± 16.25 ms |
| 11" | heavy-lift | 75 ms | ± 18.75 ms |
| 12" | heavy-lift | 85 ms | ± 21.25 ms |
| 13" | heavy-lift | 95 ms | ± 23.75 ms |
| 14" | heavy-lift | 105 ms | ± 26.25 ms |
| 15" | heavy-lift | 115 ms | ± 28.75 ms |

- **Reading Td deviations:** Faster than target + low noise = headroom for P increase; slower + high noise = mechanical issues or wrong prop size; within target + high noise = P at physical limits.
- **Developer validation:** A stricter ±10% statistical criterion (across ≥10 flights per frame class) is used to confirm that TD_TARGETS predictions match actual measurements. See GitHub issues for current validation status.
- **Analysis Components:**
- Collects individual Td measurements from all valid step response windows
- Calculates response consistency metrics (mean, std dev, coefficient of variation)
- Compares measured Td against frame-class targets
- Classifies Td deviation (significantly slower, moderately slower, within target, faster)
- Provides P gain recommendations based on deviation and noise levels
- **Recommendation Types:**
- **P Increase:** When Td is slower than target with acceptable noise levels
- **Optimal:** When Td is within target range or at physical limits
- **P Decrease:** When Td is faster than target with high noise (rare)
- **Investigate:** When measurements suggest mechanical issues or incorrect frame class
- **Output Format:** Detailed console report with:
- Current P gain and measured Td statistics
- Frame class comparison and deviation percentage
- Physical limit indicators (response speed, noise level, consistency)
- Clear recommendation with reasoning
- **Relationship to P:D Recommendations:**
- P:D ratio recommendations (existing feature): Analyze peak overshoot → adjust D-term
- Optimal P estimation (new feature): Analyze response timing → adjust P magnitude
- Both features are complementary and can run simultaneously for complete tuning guidance

### Step-Response Comparison with Other Analysis Tools

This implementation provides detailed and configurable analysis of flight controller performance. The modular design and centralized configuration system make it adaptable for various analysis requirements.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Usage: ./BlackBox_CSV_Render <input1> [<input2> ...] [OPTIONS]

--butterworth: Show Butterworth PT1 cutoffs on gyro/D-term spectrum plots.
--dps <value>: Deg/s threshold for detailed step response plots (positive number).
--estimate-optimal-p: Enable optimal P estimation with frame-class targets (requires --prop-size).
--prop-size <size>: Propeller diameter in inches (1-15, whole-number only). Required with --estimate-optimal-p.

=== GENERAL ===

Expand Down Expand Up @@ -76,6 +78,9 @@ Arguments can be in any order. Wildcards (e.g., *.csv) are shell-expanded and wo
```shell
./target/release/BlackBox_CSV_Render path/to/ --step --setpoint --motor --output-dir ./all-selective
```
```shell
./target/release/BlackBox_CSV_Render path/to/BTFL_Log.csv --step --estimate-optimal-p --prop-size 5
```

### Output

Expand Down
20 changes: 12 additions & 8 deletions src/axis_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,29 @@
/// Panics if index is greater than 2
#[allow(dead_code)]
pub fn axis_name(index: usize) -> &'static str {
match index {
0 => "Roll",
1 => "Pitch",
2 => "Yaw",
_ => panic!(
AXIS_NAMES.get(index).copied().unwrap_or_else(|| {
panic!(
"Invalid axis index: {}. Expected 0 (Roll), 1 (Pitch), or 2 (Yaw)",
index
),
}
)
})
}

/// Number of axes (Roll, Pitch, Yaw)
pub const AXIS_COUNT: usize = 3;

/// Number of primary control axes (Roll, Pitch only - excludes Yaw)
pub const ROLL_PITCH_AXIS_COUNT: usize = 2;

/// Get all axis names as a static array
pub const AXIS_NAMES: [&str; AXIS_COUNT] = ["Roll", "Pitch", "Yaw"];

// Compile-time check to prevent drift between AXIS_COUNT and AXIS_NAMES.len()
// Compile-time assertions to prevent invariant drift
const _: [(); AXIS_COUNT] = [(); AXIS_NAMES.len()];
const _: () = assert!(
ROLL_PITCH_AXIS_COUNT > 0 && ROLL_PITCH_AXIS_COUNT < AXIS_COUNT,
"ROLL_PITCH_AXIS_COUNT must be > 0 and < AXIS_COUNT"
);

#[cfg(test)]
mod tests {
Expand Down
85 changes: 84 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,87 @@ pub const PSD_EPSILON: f64 = 1e-12; // Guard against division by zero for PSD va
pub const MAGNITUDE_PLOT_MARGIN_DB: f64 = 10.0; // Padding above/below magnitude data for plot range
pub const PHASE_PLOT_MARGIN_DEG: f64 = 30.0; // Padding above/below phase data for plot range

// src/constants.rs
// High-frequency noise analysis for P headroom estimation
// D-term energy above this frequency threshold indicates noise constraints
pub const DTERM_HF_CUTOFF_HZ: f64 = 200.0; // Frequency above which high-frequency noise is measured
pub const DTERM_HF_ENERGY_THRESHOLD: f64 = 0.15; // 15% of total D-term energy (high noise level)
pub const DTERM_HF_ENERGY_MODERATE: f64 = 0.10; // 10% of total D-term energy (moderate noise level)

// Response consistency quality control
// Ensures Td measurements are reliable across multiple step responses
pub const TD_CONSISTENCY_MIN_THRESHOLD: f64 = 0.85; // 85% of responses should be within ±1 std dev
pub const TD_COEFFICIENT_OF_VARIATION_MAX: f64 = 0.20; // 20% CV (std/mean) is acceptable

// P headroom estimation multipliers
// Conservative approach for users who want safe incremental improvements
pub const P_HEADROOM_CONSERVATIVE_MULTIPLIER: f64 = 1.05; // +5% from current P
// Moderate approach for experienced pilots
pub const P_HEADROOM_MODERATE_MULTIPLIER: f64 = 1.10; // +10% from current P
// Aggressive approach for optimization (use with caution)
#[allow(dead_code)]
pub const P_HEADROOM_AGGRESSIVE_MULTIPLIER: f64 = 1.15; // +15% from current P (reserved for future use)

// P reduction multipliers (when Td is too fast or noise is too high)
pub const P_REDUCTION_MODERATE_MULTIPLIER: f64 = 0.95; // -5% from current P
#[allow(dead_code)]
pub const P_REDUCTION_AGGRESSIVE_MULTIPLIER: f64 = 0.90; // -10% from current P

// Td statistics computation constants
pub const MIN_TD_MS: f64 = 0.1; // Minimum valid Td (time to 50%) in milliseconds (domain-appropriate threshold)
pub const TD_MEAN_EPSILON: f64 = 1e-12; // Threshold for near-zero mean values (avoid division by zero)
pub const TD_SAMPLES_MIN_FOR_STDDEV: usize = 2; // Minimum samples needed for std dev calculation

// Td deviation thresholds (percentage deviation from target)
// Deviation thresholds for classifying Td behavior
// Note: The thresholds are intentionally asymmetric — there is no separate
// 'moderately faster' threshold. Faster-than-target deviations are treated
// more strictly because they often indicate potential oscillation or unsafe
// aggressive tuning. Therefore any significant speed-up beyond
// TD_DEVIATION_SIGNIFICANTLY_FASTER_THRESHOLD is flagged immediately. Slower
// deviations are given two thresholds (moderate and significant) to allow
// finer-grained handling when Td is lagging behind the target.
pub const TD_DEVIATION_SIGNIFICANTLY_SLOWER_THRESHOLD: f64 = 30.0; // > 30% slower
pub const TD_DEVIATION_MODERATELY_SLOWER_THRESHOLD: f64 = 15.0; // > 15% slower
pub const TD_DEVIATION_SIGNIFICANTLY_FASTER_THRESHOLD: f64 = -15.0; // < -15% faster

// Optimal P estimation data collection thresholds
pub const OPTIMAL_P_MIN_DTERM_SAMPLES: usize = 100; // Minimum D-term samples for noise analysis
pub const OPTIMAL_P_SECONDS_TO_MS_MULTIPLIER: f64 = 1000.0; // Convert seconds to milliseconds

// Torque-Inertia Profiler constants
// Used by torque_inertia_profiler.rs to derive aircraft-specific Td targets from
// throttle-punch events in flight logs, replacing the empirical frame-class table.

/// Minimum throttle increase (in 0–1000 units) to qualify as a throttle punch.
pub const THROTTLE_PUNCH_MIN_DELTA: f64 = 200.0;

/// Time window (ms) within which the throttle increase must occur to count as a punch.
pub const THROTTLE_PUNCH_WINDOW_MS: f64 = 50.0;

/// Window (ms) after punch onset in which to measure peak angular acceleration.
pub const THROTTLE_RESPONSE_WINDOW_MS: f64 = 150.0;

/// Minimum number of throttle-punch events required for a reliable Td target.
/// Analysis is skipped (with a console warning) when this threshold is not met.
pub const TORQUE_PROFILER_MIN_EVENTS: usize = 5;

/// Minimum normalised command delta (0–1) for a punch event to be valid.
pub const TORQUE_PROFILER_MIN_CMD_DELTA_NORMALIZED: f64 = 0.10;

/// Samples to skip at the start of the response window (ESC + motor latency).
pub const TORQUE_PROFILER_SETTLE_SAMPLES: usize = 3;

/// Numerator constant for Td calculation: K = π × 1000 / 2
/// Td_ms = K / sqrt((P / P_SCALE) × torque_inertia_ratio)
pub const TORQUE_PROFILER_TD_CALC_K: f64 = 1_570.796_326_794_896_6;

/// Betaflight/EmuFlight P gain scaling factor.
/// Converts raw firmware P gain (e.g. 45) to an effective physical gain.
/// This constant may require empirical calibration; starting value: 100.0.
pub const TORQUE_PROFILER_P_SCALE: f64 = 100.0;

/// Real-world achievability factor for physics-derived Td targets.
/// Bridges the gap between theoretical torque capacity and actual flight performance
/// (accounts for ESC lag, motor startup, prop-wash efficiency, etc).
/// Higher values = more relaxed targets. Starting value: 2.50.
pub const TORQUE_PROFILER_ACHIEVABILITY_FACTOR: f64 = 2.50;
2 changes: 2 additions & 0 deletions src/data_analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ pub mod derivative;
pub mod fft_utils;
pub mod filter_delay;
pub mod filter_response;
pub mod optimal_p_estimation;
pub mod spectral_analysis;
pub mod torque_inertia_profiler;
pub mod transfer_function_estimation;

// src/data_analysis/mod.rs
Loading
Loading