fix(bem): fix windmill/helicopter branch discontinuity at lambda_climb boundary#8
Merged
Merged
Conversation
…b boundary Quasi-static BEM solver had a spurious discontinuous jump in thrust and torque right at lambda_climb == 0 (the hover/descent boundary). Root causes: 1. solve_bem_element was seeding from the descent branch when lambda_climb < 0, even in the near-hover band where that branch is physically wrong (no distinct windmill flow topology yet). 2. solve_bem_element_windmill activated for ANY lambda_climb < 0, including tiny values where its Brent bracket is ill-conditioned and the lam_local = omega*r/|v_climb| term diverges. Fix: - Add MIN_LAMBDA_CLIMB_WINDMILL = 0.02 threshold (tip-referenced). Below this, windmill solver returns None unconditionally so all elements/azimuths use the same (helicopter) solver branch together. - solve_bem_element always seeds and selects the climb/hover root, since it is only reached when windmill has declined. - Gate is rotor-level (uses tip speed) so every element switches branch together, preventing inboard/outboard inconsistency. Also: - VPM: add corrected Pedrizzetti relaxation that preserves |gamma| exactly via the normalization factor sqrt(b2). Previous blend was only first-order in r. - Cargo.toml: add steady_flight_simple to workspace members. - Bump version to 0.6.0.
…t boundary tests solve_bem_element previously changed to always seed the climb branch, which broke deep-descent physics (lambda_r should be negative when strong upward wind drives net upward inflow). Fix: restore conditional seeding based on whether v_climb is inside the near-hover band (|v_climb| < MIN_LAMBDA_CLIMB_WINDMILL * tip_speed). - Near hover: climb branch (fixes the discontinuity at v_climb = 0) - Deep descent: descent branch (physically correct; windmill solver handles this in the full pipeline, but solve_bem_element may be called as a fallback for azimuths where the Brent bracket is absent) Low-level element behavior tested in Rust (test_hover_boundary_continuity, test_deep_descent_negative_lambda_r). Python test_autorotation_lambda_r_negative reverted to its original physics-correct assertion (lambda_r < 0 for strong upward wind), since that belongs at the Python/aero-API layer.
…c API solve_bem_element is an internal implementation detail of the BEM element solver (raw helicopter momentum quadratic, no windmill routing). Exposing it at the Python layer created confusion about its contract and made it impossible to change the internal routing logic without breaking Python tests. Changes: - dynbem/src/lib.rs: remove PyBEMElementResult pyclass and solve_bem_element pyfunction; remove from module registration - dynbem/python/dynbem/bem.py: remove from re-exports and __all__ - tests/test_bem_components.py: remove TestBEMElementConvergence class (5 test methods) and associated solve_bem_element import - dynbem_rs/src/quasi_static_bem.rs: port all 5 element-level tests to Rust (test_momentum_balance_residual_hover_and_climb, test_zero_omega_gives_zero_forces, test_hover_lambda_r_positive, test_hover_thrust_matches_momentum_theory) alongside the existing boundary tests - verification/caradonna_tung_spanwise_cl.py: replace solve_bem_element call with a self-contained Python Newton BEM element solve for hover (v_climb=0); no internal API needed
… Python API These are internal BEM/Pitt-Peters implementation helpers with no user-facing purpose. Same pattern as the previous solve_bem_element removal. - dynbem/src/lib.rs: remove three pyfunctions and their module registrations - dynbem/python/dynbem/__init__.py: remove from _dynbem imports and __all__ - dynbem/python/dynbem/bem.py: remove prandtl re-exports - dynbem/python/dynbem/pitt_peters.py: remove vrs_lambda1 re-export - tests/test_bem_components.py: remove TestPrandtlTipLoss and TestPrandtlHubLoss - tests/test_pitt_peters.py: remove TestVRSPolynomial and vrs_lambda1 import - dynbem_rs/src/quasi_static_bem.rs: port Prandtl tests to Rust (test_prandtl_tip_loss_matches_formula, test_prandtl_tip_loss_boundary_cases, test_prandtl_hub_loss_matches_formula, test_prandtl_hub_loss_boundary_cases) - dynbem_rs/src/common.rs: add vrs_lambda1 Rust tests (hover boundary, WBS boundary, VRS peak)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a spurious discontinuous jump in thrust and torque at
lambda_climb == 0(the hover/descent boundary) in the quasi-static BEM solver.Root Causes
solve_bem_elementwas seeding from the descent branch whenlambda_climb < 0, even in the near-hover band where that branch is physically wrong — no distinct windmill flow topology exists yet.solve_bem_element_windmillwas activating for anylambda_climb < 0, including tiny values where its Brent bracket is ill-conditioned andlam_local = omega*r/|v_climb|diverges.Fix
MIN_LAMBDA_CLIMB_WINDMILL = 0.02threshold (tip-referenced inflow ratio). Below this, the windmill solver returnsNoneunconditionally, so all elements/azimuths use the same (helicopter) solver branch together.solve_bem_elementalways seeds and selects the climb/hover root, since it is only reached when the windmill solver has declined.omega*r) so every element switches branch together, preventing inboard/outboard inconsistency at the same rotor-levelv_climb.Additional Changes
relax_corrected_pedrizzetti) that preserves|gamma|exactly via the normalization factor1/sqrt(b2). The previous linear blend was only first-order inr.steady_flight_simpleto workspace members.