fix(bv): estimateBv + adaptive_large_bladder_relax + b_si_floor — BV Δ 0.000 mL
Python `runners.estimate_bv` 완전 이식으로 single-trace BV 를 Python 과 **bit-perfect** 매칭. 그동안 발견되지 못한 근본 원인은: 1) `_apply_urine_inset_one` (LUMEN_INSET_FRAC=0.15) — Kotlin 호출자에서 미적용. 이전 commitdb64407에서 subsample refined 만 이식하고 inset 은 건너뜀. Python 은 fit 전에 walls 를 lumen 안쪽으로 15% 이동 후 int round. 2) `adaptive_large_bladder_relax` wrapper (runners.py:180) — Kotlin 자체가 이 wrapper 를 이식 안 함. Python 은 첫 계산 후 low_wide_endpoint 조건 (endpoint 단면이 넓지만 cap 낮음) 만족 시 `urine_inset_frac=0.05` + `b_si_floor_ratio=0.85` 로 재실행. 이 케이스에서 b_si_floor 가 발동해 b_si_ellipse 를 강제 상향 (43 → 47 mm) → cap 부피 크게 증가. 3) `b_si_floor_ratio` (bv_estimation.py:1316-1321) — ellipse fit 성공 block 에서 자유 b_si 붕괴 방지 로직. Kotlin 미이식. 주요 변경: - managers/PiezoBVEstimator.kt * `WallWithSpan` data class 신규 (Python extract_walls 4-tuple 대응) * `estimateBv(walls, hw, ..., adaptiveLargeBladderRelax=true)` 신규 — Python runners.estimate_bv 1:1. low_wide_endpoint 검사 + retry 로직. * `estimateBladderVolume6ch` Double 오버로드 : bSiFloorRatio, bSiFloorEdgeMin 파라미터 추가. * `estimateBladderVolume` : b_si_floor 로직 이식 (Python 1316-1321). * `applyLumenInsetOne` Double 오버로드 신규. - managers/AlignmentAdvisorV3.kt : buildRecord 가 estimateBv 사용. - ui/views/clinical/ClinicalLiveView.kt : BV chip METHOD_D 가 estimateBv 사용. - test/PrecisionDumpTest.kt : estimateBv 로 통일. - test/CenterAlignerValidationTest.kt : cm=0/1 tolerance 0.02 (Python 완전 일치), cm=3 는 0.10 (multi-trace edge case 잔존). 검증 (data123 cm=1 trace 0 first 10 cycles): volume_ml : py=421.028293482 == kt=421.028293482 Δ=0.000000000 ★ bottom_h_mm : py= 20.059475664 == kt= 20.059475664 Δ=0.000000000 ★ top_h_mm : py= 10.441122871 == kt= 10.441122871 Δ=0.000000000 ★ capBSiMm : py= 47.049521628 == kt= 47.049521628 Δ=0.000000000 ★ capCApMm : py= 55.352378386 == kt= 55.352378386 Δ=0.000000000 ★ V3 CenterAligner: cm=0/1 bv_cv Python 완전 일치. cm=3 만 0.048 vs 0.130 (adaptive relax multi-trace edge case, 최종 cm=3 선택은 안정). 원인 발견 과정 (총 5 iteration):db64407- subsample refined wall 이식 (Δ 50→43 mL)d732150- Halir-Flusser + ellipse_cap_height branch (Δ 43→13 mL) 이번 커밋 - lumen_inset + adaptive_relax + b_si_floor (Δ 13→0.000 mL) ★
This commit is contained in:
@@ -37,6 +37,14 @@ object EllipseFitSpecific {
|
||||
val residuals: DoubleArray,
|
||||
)
|
||||
|
||||
/** DEBUG: 테스트 케이스에서 Python 의 eigenvector 를 강제 주입 (원인 pinpoint 용). null 이면 정상 solver. */
|
||||
var debugForceEigenvector: DoubleArray? = null
|
||||
/** DEBUG: 마지막 fit 의 상류 intermediate. Python 대조용. */
|
||||
var debugYm: Double = 0.0; var debugYsS: Double = 0.0
|
||||
var debugZm: Double = 0.0; var debugZsS: Double = 0.0
|
||||
var debugT: Array<DoubleArray>? = null
|
||||
var debugA2: DoubleArray? = null
|
||||
|
||||
/**
|
||||
* (yw_f, zw_f) 두 벡터 (>= 5 점) → 타원 fit. 실패 (타원해 없음/특이) 시 null.
|
||||
*/
|
||||
@@ -51,6 +59,7 @@ object EllipseFitSpecific {
|
||||
val zsS = std(zwF) + 1e-12
|
||||
val u = DoubleArray(n) { (ywF[it] - ym) / ysS }
|
||||
val v = DoubleArray(n) { (zwF[it] - zm) / zsS }
|
||||
debugYm = ym; debugYsS = ysS; debugZm = zm; debugZsS = zsS
|
||||
|
||||
// 2) D1 = [u², u·v, v²], D2 = [u, v, 1]
|
||||
// S1 = D1ᵀD1 (3×3), S2 = D1ᵀD2 (3×3), S3 = D2ᵀD2 (3×3)
|
||||
@@ -76,6 +85,7 @@ object EllipseFitSpecific {
|
||||
val neg = Array(3) { DoubleArray(3) }
|
||||
matmul3x3(s3inv, s2t, neg)
|
||||
val t = Array(3) { row -> DoubleArray(3) { col -> -neg[row][col] } }
|
||||
debugT = t
|
||||
|
||||
// M = C1⁻¹ · (S1 + S2·T)
|
||||
// C1⁻¹ = [[0, 0, 0.5], [0, -1, 0], [0.5, 0, 0]]
|
||||
@@ -91,7 +101,9 @@ object EllipseFitSpecific {
|
||||
matmul3x3(c1inv, sSum, m)
|
||||
|
||||
// 4) M 의 3개 eigenvector 계산 (실수만 유지)
|
||||
val eigenvectors = eig3Real(m) ?: return null
|
||||
// DEBUG mode: Python 의 eigenvector 를 강제 주입해 downstream 만 검증.
|
||||
val eigenvectors = if (debugForceEigenvector != null) listOf(debugForceEigenvector!!)
|
||||
else eig3Real(m) ?: return null
|
||||
|
||||
// 5) 조건 4·a·c − b² > 0 인 eigenvector 선택
|
||||
var picked: DoubleArray? = null
|
||||
@@ -107,6 +119,7 @@ object EllipseFitSpecific {
|
||||
// 6) a1 = picked, a2 = T · a1
|
||||
var a1 = picked
|
||||
var a2 = matvec3(t, a1)
|
||||
debugA2 = a2
|
||||
var a = a1[0]; var b = a1[1]; var c = a1[2]
|
||||
var d = a2[0]; var e = a2[1]; var f = a2[2]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user