From 43a70c9888b867fcb7a477996903459214baca68 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Thu, 8 Oct 2026 09:59:03 +0800 Subject: [PATCH] =?UTF-8?q?refactor(numerical):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8B=9F=E5=90=88=E7=95=8C=E9=9D=A2=E4=B8=8E?= =?UTF-8?q?=20LM=20=E8=BF=AD=E4=BB=A3=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整窗口布局、参数展示和拟合日志 - 默认勾选可用参数,移除 PSO 加速选项 - LM 共用最大迭代次数,改善门槛改为 max(0.01, 基准误差×2%) --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 152000 -> 154092 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 156 +++++- .../nmCalculation/nmCalculationAutoFitLM.h | 2 + Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h | 7 +- .../nmSubWxs/nmWxAutomaticFittingStart.h | 32 +- .../nmCalculation/nmCalculationAutoFitLM.cpp | 119 +++-- Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp | 201 ++++---- .../nmSubWxs/nmWxAutomaticFittingStart.cpp | 451 ++++++++++++------ 8 files changed, 646 insertions(+), 322 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index c234a7d0a9056a13814602450e07215e20761d6d..ee2939dce4d2d8a0e3380101cd2b20015efdb95a 100644 GIT binary patch delta 15900 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 delta 14716 zcmaKz1ymK?`|qDU=ggVY#{?5GFtAX;#sW-41u+m%5L66Mz`!~dii#MhU<-nZji}fK z1_oevASx!d*xc`#{k#ABTkEcS-?iTNGc%l-*?T|z?0Mg1Xijg@EUREv6VU*o`}4s; zL`C78-|EX`PQIMmE^p8!9%4ANloZAJ?XS+ckJogYdidgq?;5cHw-#DiQ z%M{Gzy5b;===lcD@3t~o+bcxMX~arqgA0gmWq?b;mz4mEgmSacbpnK z&NBP~LV0I?Zgn2)L-cCAOje;UXT4C+pIBeZKmv$0CVQg zftZQieoi-$vrjY#9l7HNLPzezgP7Qz93n3p%ow`l6+^5~I;TGd==Bfhg^<7HU}!TU z@AFtHEUot+V)bWmb{)!@0l5#}gq6Yo24jWpL3sw>BYFyvfUN0aPVXovJrwV$k4)C| z7pM0t&S6+-pKxL^P-Y*?{Y%aA0c(Z_-~t;^i)hF&Vr!ld4b6f~lV!3BSh7)bh_%G; zN9_i^K>Yo3Fz54noZqI)WUX|Z+uIY3&V&d(h{j{(T4Bi}pm+s|GFfZ$K^&CF1w72c z=ObhaRDxUhONBRXIe; ztr*n9Tbw^O%4F5DNx40V1PQ-88%l!N%9WJ)cEqMGA?1yc#9SV77NyD**nCpnjUbk2 zB;`8?VmpI4KfaJDs8dN)<`J8hN1{_Cv5Ch>>;%)jQwp9Uy6eFCWT8x^Zp2yFAG`qh z-whzKyA!c`O3uVnoLd;@uHhsOa3i+s8Ho@9?^~H@S?ltK+B81gBPG1^+-%S z2N6!;T#w&d&Qq>(rZ?csNVX96|I#2|5R4%tCc)%7HQ{`j%lT!XOx6@H!rz&92IsJo zoT+M=f?^(tJBJYy9&?7iB5|J+(cf<}nK*&78=iyf>{WHp>_md{H;EZBL^t=#WG%{& zcuoml-HS5-Bek5T;z1T(z>4HuAfZYDi4PmWC0F5mqmjv4Km@ohsGLb~msByc zp|m?mwPrc7?K4TW#iE2QNM*(WYk8Mc7giCw)14&MWMZwYNiwx0_ID{s*6;;`T9Q=V z4KJD{leGq0^@anQKvMWZ+?PXA)F+((085CqvmnOl)QycqzvqzQydAMkW61a?9Ukv3nFgj1I~Pc0E|n#!-<2w^eoV~d zO%?BAX}wR8_4N)!1MR8uh+T*g?o@5f1!5bnbKdYKyHN?mo+neoi4g71PSmJ%95Js6 z)EMuf%UjL-;Dc5stNxT4f4fA2=?yhKKbL4`6glYL5F7iA9G&2DD~jak8bE?^HMOLh zL@nBKM$eXPD5Y;Xv<^bLYfSw?Q%;Xg{gQCIgf#0J9QPHsbNq4^R& zxZ0GuFN8h636m+Pj#JNuxZ&<%>e&*a38}=ncMfOH9nO1QIG+_z&pOUJIspQEy=@a#zyNK9B6%AYlo!GFJ^M(}- zigqM3Ym!WZ;-3=TXf2a9^`^n6{1Msea?X7~gTL><$~cja@QYZ%5b_y`6^WTcLq@cK z6$FuQ->Jmbn#gzVL1J4SIHAz;`LmAXyVe_yZ6o=02_@$Ki2N3vCD!eSg|PpOJo3wG zL3BP}CX2P=yrbhRs?S-{o&0`phtkyLEPTWH6%WF7=ESeF$;bHlwixnX6Hl!81&v(3 z9xgYVMoq#1oWIa0i);Q>pF+x+4HAN?55R+ODE4 zn$yId&ZgA;3ZgG}WU^-6XqCEVaB~PRLXiQ+&Xu6fZhS-m-bT3AW;MI{HYEy~5^`s{^EN1(O3T@+vGF{E| z_CZIuUVE9W`8@iRu%FoQt@J$)j_6zoqx^b!c+shRR^xO5mb4zLd88Fm zvKg#i)Ahvm?qT-#Ac|F=S)*~^h!U2v#tK)WUX570BiG}%3`Cog%C@NVx!gnh&m|Qj1Be}KrJ@2 zEZkl5gDm>Q9-_}rWwJ&W*n(4gVE-u*Y;pU8L_bfn*eysfUc|9PpI~CytJs=JkkR2- zwy7`7rdtoTX?J6yu?Z|`6RscJ!;(K$B%!h!OX&=4A2^z&ZV5zi36RNJyRsdth}geu zwtF~!zkVv)>x-38d}A4USdEa#PTJWapHt+s?8=$M&fZ|xVmlE#63cFXIZ14G94lBG zOH7)~o+`{0#|Wj-Yf6 zCCZQlO&K2&%A6JSn6V(X5e%)r5~eR0N11U!*F!LU#}aDC3syabAt8wnY7ZPwZ0U2M zVSE%k-E*PIhK(4Ywb0^QIFa)%!6_dp*R_>`B?z&84nnJqxzKu@(Aph3@uY%GR(mgJ zZ*NZjnL=xiZPHLH7sIUodI;uOFY%yIS7Gi4WU*1(h3H-0MBlsqhleVIW)P5Pk!rgwwM24TjgUct04!jc{??EbN zY9TzEokMJNy6`+pL*x`HlW9A0+OOgCD&ZVv%@)1|VMbk^3BM;{0Q(|2|Jf^qRX#`<+!d-MPqJ>(llnas8d=R9|X#UsN1j*|~q?O;W@ zILzo~flSt-v%;zia=t<1IE&76etRR6wP~oZaxF_jx%~>OLC~Exn-x}m(2+`)6;{K_ z;{2h)YWQ$O%~*xiWLQt7g$k=@9>ff@6xNN(qhcDXsNHBT;{S^MiaPe$_+d8ZyJnmv zgJrU6C7kbSD(bUwsOPFH>OXi7(Tr8tM?z#HT^06kQ1@MaCX-ceuV|!0X;o>Y!a?Ij zY)dDFLj#2GOB*;pJ(9`lD-;gB&Dq3`{Zq7<1-o9*QsH>yA6)8VMf(>pj}^}q9b)ZJ z11c3A%e^CZ+C^dU;Ven1=-e7wy>m6^k!}hXRc$z~8wyv;MBaYmEU{I%t#QOmjo@(5 zS<%&!rdKv7x?ADBnTd+-=1WjM$6Jc-7W11IujsJ}j^T2&qGv3$G|5BJ=OdJlzRF~Z z7)4*i1a{$~!n1Q_V!!SwymsJxv83q#7b#fZikw4-D+Zm1M@&gk48Dbl)hbr_o=75g zvqTZ#zn>^psR&pGNA<2qVIFQdcz<0H{40lO>k~y-3pj}1brj(TdJqY@iq-M{M8`c8 z3DKBgR)HdE9Xw-UvLd-pDtbX(6x$zdftij|>{KD~INDK>o&_xr>7zJs9iDV~9mU~Y zw}@Wfk;(SWP@MP>O3e6Laq3Gh>WEWj#p#~`#CE?^oJoUr|8rBE*%1#eRGjmJvKYe^ z7tSpw<~Cn(ao2G)Cb}sur6v=S0L7KCc;s~76*q1|x%wSZ$T8u4g5qt5Vq)+9DBj7EXmXcjN0+*;$O4qQoPwkYN21pn- zJ1h0x?!-P7%M{f4O5^NwqAN)P`m+*a8+>Kw5#F-jL}xPEiR45iB&C{@LY%KpyiT&2!Z_J5BX`@UBC z^u7mIc~|L|qeUK&q4c}%kCp1896B6JXZ=_?+zR(qGAbjR-$#;pRXN)iGaOYz8SRWP zJZpn;^)WwU3C)y=J+N{;dvp4&=Zxqt)661U@C#E~b52{#IcGWN!rz=rj&ddhajyN( z^-WFqdD>UbeLFe#M{-`A%X#${XP!T2;baS8|F1jp3vbtOmPW}GXey^vo732q(`3zA zu7tDVOl9KNf5dzqE7woM%H?fTZj8$&w$Vzt>BLOLgwe_^rw+heA1PBFZAN%+sZ8Bk zA03w$%G9Ukl|7};@_@=yfYaEB^^81Nxa~Qoy{@*LNi~&On#IH(X_Z+I$!_cbA+?|V{oMFnmNh{HK%vI*MJ&TU2r!wCgL8!Ka(){FQ zJ{l0V%IDb~kkj>2zVx?4_$;q1a)8<7cU8Xq`;`PMLHX{rBT?=sAhZR zm(puQZgZ61z2T^Ce3Z$Wom2iB5RL9wuBe!S&f2+tGTCBRQE9A0`&IYq6u3w1} z9p`BG4ht3anGUlYY`7HjlGrD9$#+WO8z z3{Z%+=jUOGzKRVdJVSoaT5LEQo~*c&*r@LdVm5(dQYhVy}(!i552zJ>8ois_hYn1!9F()fY$og%V9VE{1f11KQh7rl7AThD?A??HDbN z&4d-4_##FK2)AkRBJuX9HH*E`AwG~&6qU>{`lGD#+Dw30KW zxfs(MO7vv4xX>AWg6o+wS(AKmVK3;w;9p{F*K@=!zLqHn3F4CB#Ykjch)Z2M6RoKv zE_3<`5!lOQb(f2)`(kDLMT)C`T|xd|t%;bp6Uw%1fw;Mw9pdyG&M)?2GHXkM;*Yqc zzZ+JhR3>X^BW}I83d(j$CUaOQ?qq+kv@zn&qJ6}EMu{0Suc9HbS58&@_=Vtc>_!W-}tI63&Ug z#UCLq#J;>1{~-ToopWRg+7Bvv5`jkM92KkA29-^7m3kPIc6=k1Mnv@Ns#obYqdhn9 zsjA$1BqDD`Rk@S?L`NU1%oYAXS?Y9ARm(?MUD}OveW1#w%p;<{vs5*WknQQ|s(NEX ziH!N2F53Yp<3Ju zGCe;`CR@B+wRk;TZDs>i+`VF=tZu61M^U_XU8-6U5l57^N3~+XV^mUMs`ztth{Z1F zJhM=>vhf>qSYkOxTB}y8k#Cr5JXNhu%|R`9hV#i0nN0hav!M^Cw~}*M56;PY)!GLg zpk%dF>)z%-X&Z2E&rxj{Rulb#_MGECs5YH|2;Tduwuml7{YR=&FZD%C8KT;%S`Pbn zEmCc@R}#D5N43@GB(asVRok8pAU1iqYEMpEV)Iw2_T@uclYglW9XL#^#T)PlF~|C< zBT25r!vCp`eZ}9sKC6yrCJ{9(uR7TZ${7}-I*G0sTYX1$x-KJHdqH(Z(;B(u3sv?m z1iP!pEJXaTx?XkjT?9Ior#UANQ$1L71C@=BOu=wn_2{6bEdQ)}JSq;N>7;tzdOuO3 zmFnf-4QNQTR~0=+c+MRqlhqJZ?;9S)R!yGjV|p-@PE>vT)C9hMuIkH>5)_@YBvluP z*slg?#z9~ue&N+DN%~a_-K@=$)n>@Dr$MSRygc>;U&$28DW!TVjl>4klA3nk0pI;t za!8&{tk+PfSrDGnW~9_?gf$8oZ>jk@+`m6wax4pLNFO3OhCx>jJ(1ctL;HUvNbRON z!$G-79nFp{P*yjUT&luOC8gw&+l1&?zT_7333h*kbK-HS=lc~zHp!AZ>H^9M;(Ydx z^JAn;R;!`pIS;a5+g0-FXhVX&ujCaPhb(!TG-w_~yRfkoya!?2_oFm2uLOlnfn*M; z*8r6ZNg)k~!hEVpqnktKJ?cxN+hA$O2+|m*esIwjIj3u-G1s@i6)%y=8l+3(a&96R zEtW!)t02+HmL^o|Of-L*G^s6GwKt<=vgWrWOVFX0e>uPQl*t@Nn5CIBVZTo=NahsC z_VhVv7WG7-QHC=oQJUQr+WVlHv|ulks-34~2~zA;y0qXG97yOMY0-2eQndh?tnw)- zHZK9Ld7iYyj2ZVVmX?MhkEl|EGfI?JB&WdT7fCC}+hC>5e$s|tFQARZGFjDYQu3T! zqVfq++B`hiV~?~m+65U*Rhewjb^3SQufT|sMB9a=cLof;|5kZ6RE#I`g-;=QQ0-p4-;OzCr|oO#UK5@vTo9k*7$?VM$XNN zYQ=|eqKy~TQV-0mUs-iUts6Qb3U$TYxd^WZ)KzOCO5RUYS39_t*z`zs4fQRE;I2$o zrLEeQqOqbCWU?w#)OAL|h5rjv*P8_wJ+7U){u_LkbdW+>wSpColN)a&%Rr`qxQ zGNMJ})J{(&v{-(t+hoK1{wBy|S`+7_XPmEhtJ`LrBpTXS?e=gLQJj&Lr;hC_jR@~CuF!EHEqS*xd5G8pv<^^^%P z$3Dx|Q_z59>jtZ*+^`|mK1v<+vo7*|Q9ZL9T=2I#YV%20MW0scx$C=N`|S+p(7S50 zeBt~)b@U;~$aNiO(gyYX#!#*k2h@w>{vw-AP%r8E1)b2I>Xq%G0|h$uDh9{(=Z-pI z>J6ec|I`}`exhLUQYW9-Mzm6=-qtu4A}yodHfl1gCq^dI0g2>rkEr5a`aJS2(k z8kNm)qPn{{XFt-YqFxg_?xoSJ6)>=?8vT?)Bo+l4!zGJHoT;g}7yjaTp{8bKBqZG= zO|901T=KrA&Q~KgF3)M~@*ktfjMX$<)Sd*Bm!?(2!9>HaYC2}QVN1uX>5{z&_J4Jv z#$^gbFnXlMZO&&z!yTHw^(qt9Tg5rYSu@}tR^mVd&7g2Q>~N0eEcqjoHL=wUT8KKJ zSker&-a_m{6V0$BXCi&PCQt?I${8Y4P@d9^xRQe&aFJ$gPdK<~%`{_^51L8P%+Q2Y z#Em&NG6h9vXc ze0GxNTD&iEJvYtWle zZ?xuemf-Tfn$|*A!=1C(s4ZvtAzkMDdRtq$4VJpiSZ(#in2|16+aNiO=;=?Hti^O~ z;YHP zE?p$Rev`G!22Ccm?}>JGhaL!{H??bj=OV{@qurQR9{EAIHYIZ=L}sN;+qVaml%F;Y zs>5E~)b5Tr0Ilz--7_0jHZotkcLB^WpkC&p-xtcP#Ec}RQuyggnRp*`o= z79Q^qr@yoIlCdn2eY*B?EhnN!O3ue|oIkF}WHviE-|yC5ZJGiHkCdzh=e9-K;?ej-OX;Zn;0$X}RM38GP=T1;W9?@(bf8m|_Ir&sL|X#1C0lU4 zu8sCbR3SDt-L=1e*bp@`>S%#EooIEuj;%RDRQa?{k@_CVWnZ1TdS^7Btaa*+;P|yN z*{DFB?v)meiUFLLB6KFPHY%DuI@5Mnh-A9Xbg>?~W~I6^I=rB{r>;yO2F$MNDmJ-* zN@<<0a*b>_B7a>?Y(i0GH(gD$O;vGCgknSw4;*P`=& z610iBR)c5aGny1#yZi8Dk77B0ddg(=oOG^Z;b;

s%97Vvp#PuGg3>C|_Nfta>Y* z`*K**hQXZqnL4v)J2;AwnL5u`&#(jXRyTM$mMG3vH*Dx=_~@6qfSIAF@2ltnbFl69 zb(T!#n5-Lq7V-a}HD_s0T@Zuvoy^jWURsEqlODRTiW7*+IB|wu*G+5($K~CQbJ#51 zj34kPr=85YSwkUXZ$I6fskYb&S;RTNscsHl$hLmeMMq$(^~Pph^ru#^*BP8|zQ|-X zZFNhYtRPx=T({z;BQcGuF8)C-GNw@7%HEMk$9w1!6gP-18m3E(&ccR7o^D-Tc-(k* z-Nt%nkv*H^benNNpM7OA`|l1&rAXonY)Kx*nkyCdZQPwc0xO@oL>C|D=>3`UfU9~pHW?}tJ)tG(P_PIw&N`}$V(<`4_^ z(zj0QiIPgxw_COax#3uS`?|1(xu5kNZ({|HmDhJs#-b5vk}2rk>bnejju$5BT|EzD zQ(MxzIfkO*(dxUcxQUibC%t<&5%zz+wcb5;BD&XAIj0}wJnYR`nyUBM^%Q0FYQ4u^ zBr5yd^?e>3KvZ+pdm{d`J!@qOx`leLSHsYfeWD-WlZ}GLLGO3=9#S<2eW1%g5-Q)) z56{DjejKbHRp&ZHd zCXUrlQ#L_7f2E)02N49;(9hn2ns3QVee}=>SpFlX`)XscmIcK*Il2O3}qSeUcdIwaX5$u`lOT)Y!d7BTWo!y z^#?iMHk8R~`g4AmqEGQi!KV~~`jqql^pFQ~?x?TdYAnERSAssR7+U^(oIZWl4Mf8N z{juA-V73qRXM>cmmKAz)ma0F@r=LD64vyj65q)-N5iQbL&-*{8p#$oaXd{(22`Ha6JkZ=6Lml_qlr&)45Xub9Q&)Zc51+VII+ z{k<2#*q|6{)<3XZcyxyI=X{w=*O;@(aL#XL{iEerx*%VDL1rZ3AAOPk7kuFmqc6JT zj+JYrf9Gt(F1Vool!0RjpxSAs$O+fk85)1Ai49CLwAcjesr}Z_)$1hs17!@oUU}eSl#vE^g&$JARtC=* z&0zoY9~*{e-A6CR&k*KS2gPHkVS>|2d}tnSm=J?>{NNJ9L?<|mFdM^E=Uk$bZid-+ zuA(&)W?0zQ8mZoN&g{FKw=Wr%7-r%dkh+{FcN$hR4>T!G2;kBj{7)#6LQON#Ro4Q5@IM=1JRI|;dudG zoL69Y_i6)C-}8p|o&90|K2r=I!XeYT{S2Q*7h|u-!|*u^LFi|DL&?E7wA;=Yew;ZC z`_DD}nOlTNc+@io|GO+Oqp(+bP^sgVv2F{e0(aYUuc~3V-L2?w;E%HONirf#w9~s zkRx_AF5l})H1(5lo#$F48Xt`tQYvELw~agV@!-t&#{DO4&<98}?$6(d&u@#0-Gs<)Y?>3rtT35=JL6jt^L$g;V~-Jb@0+aV;KQMjUM8FG6NxTH zbAC80li6-I*`MoxW|fu6{uwf%oMW7YUYs9~%Vf2pOirurVAm_b)G8cWe=biZ(?*!u z6s93sCYd_m`E2u8Q|FIZ5p$VCCbtAT1eYhKt{0A?H`Bn>eXIwv*N>cE>dF)}KTX|# zJx12L%+wPDU`bm|9(Pc7KeXp8IU$oZzH1s#3tvWP4x7eYyh&`;TvNDrA2b~PnkM}$ zk0Q3aX~yGdgzpBXS$EHnS*WnaG}}gtK7D1*%UPz`Q!&%(H94mTndV(Tg{=3WY1KS9 zruJt!m;5%ZS^(GmqorwWlt0??Crrs!E~p8!WwLTvoXr`hZ&}WOQJibiOvwuz(f^CS zY}(RhIZ@TKrmbm^<)#AD?t5OS2Y#9M#43Grp#mM=!QF+ zj?^B7_F5a6tl=Hg@y@6Rs#G?e5iMpo+H~n@Z`2c0O_%MUwDuiMSIjWW1B*?!&lDgv z%Q8KBV%a92alurmzlG}ehUsOe+L#H-$tB;g-?vmItH0A!v=mWKm~AS0G98~_%#tZ+ zJWL;ADiHNbHI*1(uAPESC6QOL-(PI{=jefiBi2;vRTgc#3#QT$0qCgKFqOu1Kok1_ z{^yaZ8{v|uT!yJth+W1t-w0gd%;{lg*y#x{X<4uMhOeI5}&SFvNcGt!W8lOLo+( z)EED4Uh2E{uK(7>AN-0-tKhr^+16Yk|J_k-iKHt0Tep9BNWfTIyJoi2ZS9)lKiz{v zMvjWK4Gs?v3vX*{*TS}O2U|PG_)qO@D?0x7^T&2sSU9-Z61(^gVGXR^|LZAUVPhu+ zhue+_2pJoHtlfZ&x^ufKdvtIpGp&&aS*B82#WJYGd9ezIvZVET6RQZ047FZnidayp zAFlb|1NN*Fyn95`{&k0^dfjml{%5$o0z<}zL`K<$ zg^rDCXB#klc<}g0+rTK>QU5iO_zA;mvYzqHVyd+s8W?`a@?y(tJ^wA!c%2O%Z+Wrh zx@D#QA20TZ9~x89;58~>e6ZhtUZ3!E1FK;D->;0Y{FyJ^|Gcq|!~gxRVeS4uZ|aXX z)s+69?})TW{{OrqV{=T(kIV(LnLkVTy^6J2J&&!-yfcrTVVTe7vyQ5Sp-b4r%nEU= zjw#{92Iilza3lMdd3h6KLV|rVYn8b!nFX>0T`Ieiuz4#J6KZT_9TLWGV*?XvZ)YnL zI;64a%)e=DH_Hs(Ww~kR9=0iA%U*UsQ)O7YS=PbdORFTb*}$q+vX*tyHZo*fF!ZLG X?YNLoi$+Bx1Z-xtGvDuHrQ81(>6W9A diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 4aee9b73..45d2ab6c 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -677,22 +677,16 @@ Reason: %1 【初始参数】 - [Stage 1: Height alignment] -Adjust permeability only; accept by height error. - 【第一阶段:高度预调】 -只调整渗透率,按高度误差判断是否接受。 + [Stage 1: Height alignment] + 【第一阶段:高度预调】 - [Stage 2: Joint LM - Shape] -Adjust selected parameters; accept by shape error. - 【第二阶段:联合 LM · 形状】 -调整勾选参数,按形状误差判断是否接受。 + [Stage 2: Joint LM - Shape] + 【第二阶段:联合 LM · 形状】 - [Stage 2: Joint LM - Total error] -Adjust selected parameters; accept by total error. - 【第二阶段:联合 LM · 总误差】 -调整勾选参数,按总误差判断是否接受。 + [Stage 2: Joint LM - Total error] + 【第二阶段:联合 LM · 总误差】 %1 | %2 | %3 | Improvement: %4% @@ -759,12 +753,8 @@ No valid error was returned; keep the last accepted result and reduce the step.< mD·m - Trial %1 | %2 | Height error: %3 -> %4 - 试算 %1|%2|高度误差:%3 → %4 - - - Trial %1 | Solver failed; keep the last accepted result and reduce the step. - 试算 %1|求解失败,保留上次已接受结果并缩小步长。 + Height error: %1 -> %2 + 高度误差:%1 → %2 Height error did not decrease; keep the last accepted result and reduce the step. @@ -816,7 +806,7 @@ No valid error was returned; keep the last accepted result and reduce the step.< Iteration or evaluation budget reached - 达到本阶段迭代次数或试算次数上限 + 达到总迭代上限或本段试算次数上限 Final total error did not improve; restoring the initial result. @@ -1091,8 +1081,8 @@ No valid error was returned; keep the last accepted result and reduce the step.< === LM 自动拟合 - 局部最优 === - Total-stage budget reached. Best error: %1, Cumulative iterations: %2 - 整体阶段预算已用尽。最佳误差:%1,累计迭代次数:%2 + Iteration or evaluation budget reached. Best error: %1, Cumulative iterations: %2 + 达到总迭代上限或本段试算次数上限。最佳误差:%1,累计迭代次数:%2 === LM AUTOMATIC FITTING - MAX ITERATIONS === @@ -4467,6 +4457,30 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We Gas pseudo-pressure data is unavailable or invalid. 气体拟压力数据不可用或无效。 + + Iteration limit: + 迭代上限: + + + Maximum iterations: + 最大迭代次数: + + + Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times. + 高度、形状和总误差调整共用此迭代上限;一次迭代可能调用多次求解器。 + + + Start fitting + 开始拟合 + + + Target error: + 目标误差: + + + Fitting parameters + 拟合参数 + nmWxAutomaticfitting @@ -4562,8 +4576,24 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We nmWxAutomaticfittingStart - Total time: %1 min %2 s - 总耗时:%1 分 %2 秒 + Iterations: + 迭代数: + + + Fitting process + 拟合过程 + + + Total time: %1 + 总耗时:%1 + + + %1 s + %1 秒 + + + %1 min %2 s + %1 分 %2 秒 Auto Fitting Progress @@ -4757,6 +4787,86 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We Pre-adjustment 预调整中 + + %1 | Iteration %2 / limit %3 + %1 · 迭代 %2 / 上限 %3 + + + Adjustment log + 调整记录 + + + Calculating initial error... + 正在计算初始误差…… + + + Current parameters + 当前参数 + + + Current value + 当前值 + + + Fitting ended + 拟合结束 + + + Fitting failed + 拟合失败 + + + Generating final result... + 正在生成最终结果…… + + + Height alignment + 高度预调 + + + Shape adjustment + 形状调整 + + + Total-error adjustment + 总误差调整 + + + Collapse + 收起 + + + Expand + 展开 + + + Fitted + 拟合 + + + Fitting in progress + 拟合中 + + + Observed + 实测 + + + Solver calls: %1 | Total time: %2 + 求解器调用 %1 次 · 总耗时 %2 + + + Target error: + 目标误差: + + + Total error: %1 -> %2 + 总误差:%1 → %2 + + + Current error: %1 + 当前误差:%1 + nmWxChangeAnal diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h index d7ff03aa..06350bc9 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h @@ -118,6 +118,8 @@ public: void setTargetWellName(const QString& wellName); signals: + // 阶段仅用于界面显示:0 初始求解,1 高度,2 形状,3 总误差。 + void fittingStageChanged(int stage); void progressUpdated(int iteration, double bestFitness); void fittingFinished(bool success, const QString& message); void bestCurveUpdated(QVector > targetData, diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index 9482bdd4..4620b8f6 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -4,6 +4,7 @@ #include "iDlgBase.h" #include #include +#include #include #include #include @@ -33,6 +34,7 @@ class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase public: nmWxAutomaticFitting(QWidget *parent = 0); ~nmWxAutomaticFitting(); + virtual QSize sizeHint() const; private slots: void onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); @@ -73,16 +75,15 @@ private: private: // UI组件 QVBoxLayout* m_mainLayout; - QVBoxLayout* m_controlLayout; + QGridLayout* m_controlLayout; QTableWidget* m_parameterTable; // 控制面板组件 QLineEdit* m_iterationEdit; + QLabel* m_iterationLabel; QLineEdit* m_errorLimitEdit; QComboBox* m_targetWellCombo; QComboBox* m_algorithmCombo; - QLabel* m_surrogateLabel; - QComboBox* m_surrogateCombo; // 参数复选框 QCheckBox* m_kCheckBox; // 渗透率 diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h index 0697b6f1..e09b291f 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h @@ -7,13 +7,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -75,6 +73,7 @@ class nmWxAutomaticfittingStart : public iDlgBase public: explicit nmWxAutomaticfittingStart(QWidget *parent = 0); ~nmWxAutomaticfittingStart(); + virtual QSize sizeHint() const; // 算法接口 void setAutoFitter(nmCalculationAutoFitPSO* autoFitter); @@ -83,7 +82,7 @@ public: // 通用设置接口 void setFittingParameters(int maxIterations, double targetError, const QString& wellName); - void setSelectedParameters(const QStringList& parameterNames); + void setSelectedParameters(const QStringList& parameterNames, const QStringList& parameterUnits); void setTargetLogLogData(const QVector >& targetData); void setPseudoPressureMode(bool enabled); @@ -92,6 +91,8 @@ public: public slots: void onFittingProgress(int iteration, double fitness); + void onLMStageChanged(int stage); + void onToggleLog(); void onFittingFinished(bool success, const QString& message); void onFinalizingStarted(); void onStopButtonClicked(); @@ -107,24 +108,22 @@ protected: private: void setupUI(); void setupParameterArea(); - void setupTableArea(); + void setupParameterStrip(); void setupChartArea(); void setupLogArea(); void setupControlArea(); - void updateParameterTable(); + void updateAlgorithmDisplay(); + void updateParameterValues(); + void updateCurrentError(double fitness); void addLogMessage(const QString& message); QString formatScientific(double value); // UI组件 - QHBoxLayout* mainLayout; - QVBoxLayout* leftLayout; - QVBoxLayout* rightLayout; QSplitter* rightSplitter; // 参数显示区域 QGroupBox* parameterGroup; QLabel* currentIterationLabel; - QLabel* maxIterationLabel; QLabel* currentComfortLabel; QLabel* bestComfortLabel; QLabel* targetPrecisionLabel; @@ -132,25 +131,31 @@ private: QLabel* algorithmTypeLabel; QLabel* currentIterationValue; - QLabel* maxIterationValue; QLabel* currentComfortValue; QLabel* bestComfortValue; QLabel* targetPrecisionValue; QLabel* targetWellValue; QLabel* algorithmTypeValue; - // 参数表格区域 - QGroupBox* tableGroup; - QTableWidget* tableWidget; + // 曲线和日志下方的整行参数,名称和单位在上、数值在下,用竖线分隔。 + QWidget* parameterStrip; + QGridLayout* parameterLayout; + QVector parameterValues; // 日志区域 QGroupBox* chartGroup; nmAutomaticFittingCurveChart* curveChart; QGroupBox* logGroup; QTextEdit* logTextEdit; + QPushButton* logToggle; + QList expandedLogSizes; // 控制区域 QProgressBar* progressBar; + QLabel* stageStatus; + QLabel* stateBadge; + QLabel* resultSummary; + QLabel* runSummary; QPushButton* stopButton; // 算法实例 @@ -162,7 +167,6 @@ private: int m_maxIterations; double m_targetError; QString m_wellName; - QStringList m_selectedParameters; bool m_isFinished; bool m_isFinalizing; double m_bestFitnessEver; diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 58fadf72..afcb16b1 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -688,7 +688,7 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) , m_isRunning(false) , m_shouldStop(false) , m_isFinalizing(false) - , m_currentIteration(0) + , m_currentIteration(-1) , m_globalBestFitness(1e10) , m_comparisonTimeMin(0.0) , m_comparisonTimeMax(0.0) @@ -884,7 +884,7 @@ void nmCalculationAutoFitLM::resetOptimizer() m_userInitialObjectiveBreakdown = AutoFitObjectiveBreakdownLM(); m_comparisonTimeMin = 0.0; m_comparisonTimeMax = 0.0; - m_currentIteration = 0; + m_currentIteration = -1; // 尚未执行迭代;对外日志使用索引加一。 m_totalEvaluations = 0; m_solverCallCount = 0; m_successfulEvaluations = 0; @@ -1042,17 +1042,17 @@ void nmCalculationAutoFitLM::writeTraceMetaFile() QTextStream out(&metaFile); out << "{\n"; - out << " \"schema_version\": 52,\n"; + out << " \"schema_version\": 53,\n"; out << " \"strategy\": \"permeability_height_then_joint_lm_shape_then_total\",\n"; out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n"; out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n"; out << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n"; out << " \"shape_acceptance\": \"strict_full_shape_loss_decrease; no_total_loss_constraint\",\n"; out << " \"lm_step_policy\": \"same_joint_LM_damping_trust_radius_secant_updates_and_sensitivity_rebuilds_in_both_phases\",\n"; - out << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_budget_exhausted_then_total; user_stop_and_consecutive_solver_failure_abort\",\n"; - out << " \"lm_phase_budget\": \"each_phase_has_max_iterations_and_max(dimensions+2,20,3*max_iterations)_evaluations; independent_of_height_prealignment\",\n"; - out << " \"lm_effective_improvement\": \"max(0.00001,0.002*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n"; - out << " \"iteration_count_scope\": \"max_iterations_applies_independently_to_each_LM_phase; trace_iteration_is_cumulative\",\n"; + out << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_evaluation_budget_exhausted_then_total_if_iterations_remain; shared_iteration_limit_or_user_stop_or_consecutive_solver_failure_abort\",\n"; + out << " \"lm_phase_budget\": \"shared_max_iterations_including_height; each_LM_phase_retains_max(dimensions+2,20,3*max_iterations)_evaluations\",\n"; + out << " \"lm_effective_improvement\": \"max(0.01,0.02*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n"; + out << " \"iteration_count_scope\": \"height_shape_and_total_share_max_iterations; rejected_and_failed_candidates_count; sensitivity_evaluations_are_not_iterations; trace_iteration_is_zero_based_cumulative\",\n"; out << " \"total_stage_shape_constraint\": false,\n"; out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n"; out << " \"skin_difference_policy\": \"local_scale_fraction_0.02_in_both_LM_phases\",\n"; @@ -1193,6 +1193,14 @@ static QString lmLogError(double value) ? QString::number(value, 'f', 6) : QString("--"); } +// 改善率统一相对上次已接受误差计算;起点为零时不作除法。 +static QString lmLogImprovement(double beforeError, double afterError) +{ + return beforeError > 0.0 + ? QString::number((beforeError - afterError) / beforeError * 100.0, 'f', 2) + : QString("--"); +} + void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReason) { // 最终只汇总总误差,耗时由进程窗口在收到结束信号后追加。 @@ -1220,12 +1228,8 @@ void nmCalculationAutoFitLM::emitTrialLog(const QString& title, const QString objective = (shapeStage ? tr("Shape error: %1 -> %2") : tr("Total error: %1 -> %2")) .arg(lmLogError(beforeError)).arg(lmLogError(afterError)); - // 起点为零时相对改善率无定义,避免除零。 - const QString improvement = beforeError > 0.0 - ? QString::number((beforeError - afterError) / beforeError * 100.0, 'f', 2) - : QString("--"); QString message = tr("%1 | %2 | %3 | Improvement: %4%") - .arg(title).arg(status).arg(objective).arg(improvement); + .arg(title).arg(status).arg(objective).arg(lmLogImprovement(beforeError, afterError)); if(!accepted) message += tr("\n Objective error did not decrease; keep the last accepted result and reduce the step."); emit logMessageGenerated(message); } @@ -1435,7 +1439,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() m_isRunning = true; m_shouldStop = false; m_isFinalizing = false; - m_currentIteration = 0; + m_currentIteration = -1; m_consecutiveFailures = 0; m_initialValues = savedInitialValues; initializeTraceFile(); @@ -1443,6 +1447,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() emit logMessageGenerated(tr("========== LM fitting started ==========")); emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName)); emit logMessageGenerated(tr("Target error: %1").arg(lmLogError(m_targetError))); + emit fittingStageChanged(0); // 先用真实求解器评价用户当前模型,供最终精英保护使用。 if(!savedInitialValues.isEmpty()) { @@ -1653,7 +1658,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - LOCAL OPTIMUM ===")); } else if(finalReason == LM_MAX_ITERATIONS) { success = true; - message = QString(tr("Total-stage budget reached. Best error: %1, Cumulative iterations: %2")) + message = QString(tr("Iteration or evaluation budget reached. Best error: %1, Cumulative iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ===")); @@ -1825,22 +1830,23 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return LM_OPTIMIZATION_FAILED; } - // 形状与总误差共用原联合 LM,每段独立使用原迭代和求解额度。 + // 高度、形状、总误差共用迭代上限;灵敏度求解仍使用原来的各段评价额度。 const int phaseEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3)); int maximumEvaluations = 0; int phaseIterations = 0; int shapeIterations = 0; int totalIterations = 0; + int completedIterations = 0; // 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径 // 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。 const double sensitivityStep = 0.04; const double minimumCoordinateStep = 1.0e-5; const double minimumTrustRadius = 2.0e-3; const double maximumTrustRadius = 0.30; - // 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。 + // 误差下降至少达到绝对 0.01 且相对当前有效基准 2% 才算有效改善。 // 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。 - const double effectiveRelativeImprovement = 2.0e-3; - const double effectiveAbsoluteImprovement = 1.0e-5; + const double effectiveRelativeImprovement = 2.0e-2; + const double effectiveAbsoluteImprovement = 1.0e-2; const int maximumIneffectiveSteps = 3; // damping 是 LM 阻尼;拒绝或预测失准时增大,真实下降与预测一致时减小。 @@ -1962,7 +1968,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } restoreEvaluationState(current); - emit progressUpdated(-1, current.fitness); + emit progressUpdated(0, current.fitness); DEBUG_OUT(tr("=== Starting LM Main Loop ===")); DEBUG_OUT( tr("LM starting point error: %1; evaluation budget per LM phase: %2") @@ -1978,9 +1984,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() double heightSlope = -1.0; int heightTrials = 0; if(permeabilityColumn >= 0) { - emit logMessageGenerated(tr("[Stage 1: Height alignment]\nAdjust permeability only; accept by height error.")); - // 不限制试算次数,由高度对齐、改善停滞及可行步长决定何时结束。 - while(processPauseAndStop()) { + emit fittingStageChanged(1); + emit logMessageGenerated(tr("[Stage 1: Height alignment]")); + // 高度调整也占用总迭代额度,仍保留原来的对齐、停滞和可行步长判断。 + while(completedIterations < m_maxIterations && processPauseAndStop()) { const double bias = current.breakdown.verticalCommonBias; if(!current.breakdown.verticalReliable || qAbs(bias) <= 0.01 || heightStagnation >= 2) break; const double k = current.parameters[permeabilityColumn]; @@ -1988,13 +1995,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() const double logRange = qLn(m_parameterUpper[0]) - qLn(m_parameterLower[0]); const double change = qBound(-0.30 * logRange, -bias / heightSlope * heightScale, 0.30 * logRange); const AutoFitObjectiveBreakdownLM previous = current.breakdown; - ++heightTrials; TrustRegionEvaluation candidate; candidate.parameters = current.parameters; candidate.parameters[permeabilityColumn] = qBound(m_parameterLower[0], k * qExp(change), m_parameterUpper[0]); candidate.coordinates = coordinatesFromParameters(candidate.parameters); const double actualStep = qLn(candidate.parameters[permeabilityColumn] / k); if(qAbs(actualStep) < 1.0e-5) break; + m_currentIteration = completedIterations++; + ++heightTrials; + emit progressUpdated(completedIterations, current.fitness); candidate.valid = evaluateTrustRegionPoint(candidate.parameters, &candidate.fitness, &candidate.breakdown, &candidate.curve, &candidate.elapsedMs); // 第一阶段只按上下偏移改善验收,不限制形状;仍防止两条曲线一高一低互相抵消。 @@ -2004,7 +2013,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() const double slope = (candidate.breakdown.verticalCommonBias - bias) / actualStep; if(slope < -0.05 && isFiniteNumber(slope)) heightSlope = slope; } - writeTraceRow(-1, 0, "permeability_height", candidate.parameters, candidate.fitness, + writeTraceRow(m_currentIteration, 0, "permeability_height", candidate.parameters, candidate.fitness, candidate.valid, candidate.elapsedMs, accepted ? "accepted_height" : "rejected_height", candidate.valid ? &candidate.breakdown : nullptr); if(accepted) { @@ -2019,18 +2028,24 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() heightStagnation = 0; } else ++heightStagnation; if(candidate.valid) { - emit logMessageGenerated(tr("Trial %1 | %2 | Height error: %3 -> %4") - .arg(heightTrials).arg(accepted ? tr("accepted") : tr("rejected")) - .arg(lmLogError(previous.verticalLoss)).arg(lmLogError(candidate.breakdown.verticalLoss))); + const QString objective = tr("Height error: %1 -> %2") + .arg(lmLogError(previous.verticalLoss)).arg(lmLogError(candidate.breakdown.verticalLoss)); + emit logMessageGenerated(tr("%1 | %2 | %3 | Improvement: %4%") + .arg(tr("Iteration %1").arg(completedIterations)) + .arg(accepted ? tr("accepted") : tr("rejected")).arg(objective) + .arg(lmLogImprovement(previous.verticalLoss, candidate.breakdown.verticalLoss))); if(!accepted) emit logMessageGenerated(candidate.breakdown.verticalReliable ? tr("Height error did not decrease; keep the last accepted result and reduce the step.") : tr("Pressure and derivative height directions conflict; keep the last accepted result and reduce the step.")); } else { - emit logMessageGenerated(tr("Trial %1 | Solver failed; keep the last accepted result and reduce the step.").arg(heightTrials)); + emit logMessageGenerated(tr("%1 | Solver failed\nNo valid error was returned; keep the last accepted result and reduce the step.") + .arg(tr("Iteration %1").arg(completedIterations))); } + emit progressUpdated(completedIterations, current.fitness); } QString heightReason = tr("no feasible permeability step or step too small"); if(m_shouldStop) heightReason = tr("stopped by user"); + else if(completedIterations >= m_maxIterations) heightReason = getStopReasonDescription(LM_MAX_ITERATIONS); else if(!current.breakdown.verticalReliable) heightReason = tr("pressure and derivative height directions conflict"); else if(current.breakdown.verticalLoss <= 0.01) heightReason = tr("curve height is approximately aligned"); else if(heightStagnation >= 2) heightReason = tr("2 consecutive steps without effective height improvement"); @@ -2041,6 +2056,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(permeabilityColumn < 0) emit logMessageGenerated(tr("Height alignment skipped: permeability is not selected.")); if(m_shouldStop) return LM_USER_STOPPED; + if(completedIterations >= m_maxIterations) return LM_MAX_ITERATIONS; // 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。 bool shapeStage = true; @@ -2288,8 +2304,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return true; }; - int completedIterations = 0; - for(int phase = 0; phase < 2 && !m_shouldStop; ++phase) { + for(int phase = 0; phase < 2 && !m_shouldStop && completedIterations < m_maxIterations; ++phase) { shapeStage = phase == 0; phaseIterations = 0; maximumEvaluations = m_totalEvaluations + phaseEvaluationBudget; @@ -2309,20 +2324,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() rebuildRequested = true; rebuildReason = shapeStage ? QT_TR_NOOP("full sensitivity at shape LM entry") : QT_TR_NOOP("full sensitivity at total-stage entry"); - emit progressUpdated(0, current.fitness); + emit fittingStageChanged(shapeStage ? 2 : 3); + emit progressUpdated(completedIterations, current.fitness); emit logMessageGenerated(shapeStage - ? tr("[Stage 2: Joint LM - Shape]\nAdjust selected parameters; accept by shape error.") - : tr("[Stage 2: Joint LM - Total error]\nAdjust selected parameters; accept by total error.")); - DEBUG_OUT(tr("LM phase budget: %1 iterations, %2 evaluations.") - .arg(m_maxIterations).arg(phaseEvaluationBudget)); + ? tr("[Stage 2: Joint LM - Shape]") + : tr("[Stage 2: Joint LM - Total error]")); + DEBUG_OUT(tr("LM phase budget: %1 remaining shared iterations, %2 evaluations.") + .arg(m_maxIterations - completedIterations).arg(phaseEvaluationBudget)); writeTraceRow(completedIterations, -1, "stage_switch", current.parameters, current.fitness, true, 0, shapeStage ? "height_to_shape_lm" : "shape_to_total", ¤t.breakdown); - for(int iteration = completedIterations; - !m_shouldStop && phaseIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations; - ++iteration) { - m_currentIteration = iteration; - completedIterations = iteration + 1; + while(!m_shouldStop && completedIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations) { + m_currentIteration = completedIterations; if(!processPauseAndStop()) break; if(stageError(current) < m_targetError) { stopReason = LM_TARGET_ACHIEVED; @@ -2350,8 +2363,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() registerEffectiveImprovement(stageError(current)); } - // 两段分别计数;无可行方向也消耗一次尝试,避免无限缩步重建。 + // 全程累计迭代;无可行方向、拒绝和求解失败也占用一次额度。 ++phaseIterations; + ++completedIterations; + emit progressUpdated(completedIterations, current.fitness); const QVector objectiveResidual = shapeStage ? current.breakdown.shapeResiduals : current.breakdown.residualVector; const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0; @@ -2386,7 +2401,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 if(selectedColumns.isEmpty()) { - emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(phaseIterations)); + emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(completedIterations)); if(trustRadius <= minimumTrustRadius * 1.01 && modelRebuiltAtMinimumRadius) { stopReason = LM_LOCAL_OPTIMUM; @@ -2433,7 +2448,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } if(!candidate.valid) { - emitTrialLog(tr("Iteration %1").arg(phaseIterations), current.breakdown, candidate.breakdown, + emitTrialLog(tr("Iteration %1").arg(completedIterations), current.breakdown, candidate.breakdown, false, false, shapeStage); // 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和 // 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。 @@ -2549,9 +2564,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() : "rejected_" + selectionName, &candidate.breakdown); - emitTrialLog(tr("Iteration %1").arg(phaseIterations), oldBreakdown, candidate.breakdown, + emitTrialLog(tr("Iteration %1").arg(completedIterations), oldBreakdown, candidate.breakdown, true, accepted, shapeStage); - emit progressUpdated(phaseIterations, m_globalBestFitness); + emit progressUpdated(completedIterations, m_globalBestFitness); const bool effectiveImprovement = registerEffectiveImprovement(stageError(current)); if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM; @@ -2576,7 +2591,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() else totalIterations = phaseIterations; if(m_shouldStop || stopReason == LM_CONSECUTIVE_FAILURES || stopReason == LM_OPTIMIZATION_FAILED) break; - // 形状达标、确认停滞或用完额度后,保留当前参数进入总误差段。 + // 形状部分结束后仅在总迭代额度有剩余时进入总误差部分。 if(shapeStage) { emit logMessageGenerated(tr("Shape phase ended: %1").arg(getStopReasonDescription(stopReason))); writeTraceRow(m_currentIteration, -1, "lm_phase_end", current.parameters, current.fitness, @@ -2586,12 +2601,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } } - if(completedIterations > 0) { - m_currentIteration = completedIterations - 1; - } + // 灵敏度建模或初始误差已达标时可能没有候选迭代,不多计一次。 + m_currentIteration = completedIterations - 1; restoreEvaluationState(current); - DEBUG_OUT(tr("Adaptive fitting counts: %1 shape LM iterations, %2 total LM iterations, %3 total evaluations.") - .arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations)); + DEBUG_OUT(tr("Adaptive fitting counts: %1 height iterations, %2 shape LM iterations, %3 total LM iterations, %4 total evaluations.") + .arg(heightTrials).arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations)); if(m_shouldStop) { return LM_USER_STOPPED; @@ -2599,6 +2613,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(!shapeStage && current.fitness < m_targetError) { return LM_TARGET_ACHIEVED; } + if(completedIterations >= m_maxIterations && stopReason != LM_CONSECUTIVE_FAILURES && stopReason != LM_OPTIMIZATION_FAILED) { + return LM_MAX_ITERATIONS; + } if(stopReason == LM_CONSECUTIVE_FAILURES || stopReason == LM_LOCAL_OPTIMUM || stopReason == LM_OPTIMIZATION_FAILED) { diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index 899254a4..3ffa3c27 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -15,6 +15,7 @@ #pragma comment(lib, "mGuiAnal.lib") #include +#include #include #include #include @@ -66,13 +67,14 @@ bool nmAutoFitReadPhysicalRange(const char* parameterName, } -// 设置某一行参数是否显示。隐藏时同步取消勾选并禁用,避免隐藏参数参与自动拟合。 +// 隐藏参数取消勾选并禁用;新显示的参数默认勾选,保留已显示参数的手工选择。 void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, bool visible) { if(!table || row < 0 || row >= table->rowCount()) { return; } + const bool wasHidden = table->isRowHidden(row); table->setRowHidden(row, !visible); QCheckBox* checkBox = qobject_cast(table->cellWidget(row, 1)); @@ -80,6 +82,8 @@ void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, checkBox->setEnabled(visible); if(!visible) { checkBox->setChecked(false); + } else if(wasHidden) { + checkBox->setChecked(true); } } } @@ -115,7 +119,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL return; } - for(int row = 0; row < table->rowCount(); ++row) { + // 裂缝参数直接按井型设置,避免先显示再隐藏造成选择状态重置。 + for(int row = 0; row < 4; ++row) { setParameterRowVisible(table, row, true); } @@ -426,7 +431,8 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) setupUI(); setWindowTitle(tr("Automatic fitting")); setModal(true); - resize(800, 480); + // 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。 + resize(sizeHint()); if(m_targetWellCombo->count() > 0) { onWellSelected(0); // 默认选中第一口井 @@ -438,6 +444,15 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent) DEBUG_UI("AutoFitting Constructor completed"); } +QSize nmWxAutomaticFitting::sizeHint() const +{ + // 沿用项目的可用屏幕尺寸计算方式,优先取父窗口所在屏幕,支持多显示器。 + QDesktopWidget* desktop = QApplication::desktop(); + if(desktop == nullptr) return iDlgBase::sizeHint(); + const QRect available = desktop->availableGeometry(parentWidget() ? parentWidget()->window() : this); + return QSize(available.width() * 45 / 100, available.height() * 55 / 100); +} + nmWxAutomaticFitting::~nmWxAutomaticFitting() { DEBUG_UI(QString("AutoFitting Destructor: this=0x%1").arg((quintptr)this, 0, 16)); @@ -461,35 +476,56 @@ nmWxAutomaticFitting::~nmWxAutomaticFitting() void nmWxAutomaticFitting::setupUI() { + // 用浅底色区分白色设置区和参数区,避免所有内容铺在同一平面。 + setObjectName("autoFittingSettings"); + setFont(QFont("Microsoft YaHei", 9)); + setStyleSheet( + "#autoFittingSettings { background: #f3f5f8; color: #273449; }" + "#autoFittingSettings QFrame#fittingControls, #autoFittingSettings QFrame#fittingParameters { background: #ffffff; border: 1px solid #dce2ea; border-radius: 7px; }" + "#autoFittingSettings QLabel { color: #667487; background: transparent; border: none; }" + "#autoFittingSettings QLabel#parameterHeading { color: #273449; font-weight: bold; }" + "#autoFittingSettings QLineEdit, #autoFittingSettings QComboBox { background: #ffffff; color: #273449; border: 1px solid #cfd7e2; border-radius: 4px; padding: 5px 8px; min-height: 20px; selection-background-color: #dceafb; selection-color: #273449; }" + "#autoFittingSettings QLineEdit:focus, #autoFittingSettings QComboBox:focus { border-color: #3978c6; }" + "#autoFittingSettings QComboBox::drop-down { border: none; width: 24px; }" + "#autoFittingSettings QComboBox QAbstractItemView { background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }" + "#autoFittingSettings QTableWidget { border: none; background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }" + "#autoFittingSettings QHeaderView::section { background: #f5f7fa; color: #667487; border: none; border-bottom: 1px solid #e5eaf1; padding: 7px; }" + "#autoFittingSettings QTableWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 8px; }" + "#autoFittingSettings QCheckBox { color: #273449; background: transparent; padding-left: 10px; spacing: 8px; }" + "#autoFittingSettings QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 4px; padding: 6px 18px; min-width: 64px; min-height: 20px; }" + "#autoFittingSettings QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }" + "#autoFittingSettings QPushButton:pressed { background: #dce8f8; }" + "#autoFittingSettings QPushButton#startFitting { background: #3978c6; color: #ffffff; border-color: #3978c6; }" + "#autoFittingSettings QPushButton#startFitting:hover { background: #306bb5; }" + "#autoFittingSettings QPushButton#startFitting:pressed { background: #285b9c; }"); m_mainLayout = new QVBoxLayout(this); - - // 创建主要内容的水平布局 - QHBoxLayout* contentLayout = new QHBoxLayout(); - - // 添加左侧间距 - contentLayout->addSpacing(20); - - setupControlPanel(); // 左侧控制面板 - contentLayout->addLayout(m_controlLayout); - - // 添加控制面板和表格之间的间距 - contentLayout->addSpacing(30); - - setupParameterTable(); // 右侧参数表格 - contentLayout->addWidget(m_parameterTable, 1); // 设置拉伸因子为1,使表格可以扩展 - - // 添加右侧间距 - contentLayout->addSpacing(20); - - m_mainLayout->addLayout(contentLayout); - setupButtons(); // 底部按钮 + m_mainLayout->setContentsMargins(16, 16, 16, 14); + m_mainLayout->setSpacing(12); + setupControlPanel(); + QFrame* controlsPanel = new QFrame(this); + controlsPanel->setObjectName("fittingControls"); + m_controlLayout->setContentsMargins(16, 14, 16, 14); + controlsPanel->setLayout(m_controlLayout); + m_mainLayout->addWidget(controlsPanel); + setupParameterTable(); + QFrame* tablePanel = new QFrame(this); + tablePanel->setObjectName("fittingParameters"); + QVBoxLayout* tableLayout = new QVBoxLayout(tablePanel); + tableLayout->setContentsMargins(12, 12, 12, 8); + tableLayout->setSpacing(10); + QLabel* tableHeading = new QLabel(tr("Fitting parameters"), tablePanel); + tableHeading->setObjectName("parameterHeading"); + tableLayout->addWidget(tableHeading); + tableLayout->addWidget(m_parameterTable, 1); + m_mainLayout->addWidget(tablePanel, 1); + setupButtons(); setLayout(m_mainLayout); } void nmWxAutomaticFitting::setupParameterTable() { - // 创建表格 + // 每次打开时默认勾选全部参数,再按井型隐藏不可用参数。 m_parameterTable = new QTableWidget(7, 6, this); // 设置表头 @@ -499,7 +535,10 @@ void nmWxAutomaticFitting::setupParameterTable() // 设置表格属性 m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems); - m_parameterTable->setAlternatingRowColors(true); + m_parameterTable->setAlternatingRowColors(false); + m_parameterTable->setShowGrid(false); + m_parameterTable->verticalHeader()->setDefaultSectionSize(36); + m_parameterTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_parameterTable->verticalHeader()->setVisible(false); // 设置列宽 @@ -513,8 +552,11 @@ void nmWxAutomaticFitting::setupParameterTable() // 设置固定列的宽度 m_parameterTable->setColumnWidth(0, 40); // 序号列宽度 - m_parameterTable->setColumnWidth(1, 120); // 参数列宽度 + m_parameterTable->setColumnWidth(1, 180); // 参数名称与复选框保留足够空间 m_parameterTable->setColumnWidth(5, 80); // 单位列宽度 + // 只改变视觉顺序,保留数据读写使用的原列索引。 + m_parameterTable->setColumnHidden(0, true); + header->moveSection(header->visualIndex(3), header->visualIndex(2)); // 设置表格的大小策略 m_parameterTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -522,7 +564,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 渗透率 (Permeability) m_parameterTable->setItem(0, 0, new QTableWidgetItem("1")); m_kCheckBox = new QCheckBox(tr("Permeability")); - m_kCheckBox->setChecked(automaticFittingData.getPermeabilitySelected()); + m_kCheckBox->setChecked(true); m_parameterTable->setCellWidget(0, 1, m_kCheckBox); m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble()))); m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble()))); @@ -532,7 +574,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 表皮系数 (Skin) m_parameterTable->setItem(1, 0, new QTableWidgetItem("2")); m_sCheckBox = new QCheckBox(tr("Skin")); - m_sCheckBox->setChecked(automaticFittingData.getSkinSelected()); + m_sCheckBox->setChecked(true); m_parameterTable->setCellWidget(1, 1, m_sCheckBox); m_parameterTable->setItem(1, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMin().getValue().toDouble()))); m_parameterTable->setItem(1, 3, new QTableWidgetItem()); @@ -542,7 +584,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 井筒储集系数 (Wellbore storage) m_parameterTable->setItem(2, 0, new QTableWidgetItem("3")); m_cCheckBox = new QCheckBox(tr("Wellbore storage")); - m_cCheckBox->setChecked(automaticFittingData.getWellboreStorageSelected()); + m_cCheckBox->setChecked(true); m_parameterTable->setCellWidget(2, 1, m_cCheckBox); m_parameterTable->setItem(2, 2, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMin().getValue().toDouble()))); m_parameterTable->setItem(2, 3, new QTableWidgetItem()); @@ -552,7 +594,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 孔隙度 (Porosity) m_parameterTable->setItem(3, 0, new QTableWidgetItem("4")); m_phiCheckBox = new QCheckBox(tr("Porosity")); - m_phiCheckBox->setChecked(automaticFittingData.getPorositySelected()); + m_phiCheckBox->setChecked(true); m_parameterTable->setCellWidget(3, 1, m_phiCheckBox); m_parameterTable->setItem(3, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMin().getValue().toDouble()))); m_parameterTable->setItem(3, 3, new QTableWidgetItem(QString::number(reservoirData.getPorosity().getValue().toDouble()))); @@ -565,7 +607,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。 m_parameterTable->setItem(5, 0, new QTableWidgetItem("6")); m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity")); - m_dfcCheckBox->setChecked(automaticFittingData.getFractureConductivitySelected()); + m_dfcCheckBox->setChecked(true); m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox); m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble()))); m_parameterTable->setItem(5, 3, new QTableWidgetItem()); @@ -575,7 +617,7 @@ void nmWxAutomaticFitting::setupParameterTable() // 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。 m_parameterTable->setItem(6, 0, new QTableWidgetItem("7")); m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length")); - m_fractureHalfLengthCheckBox->setChecked(automaticFittingData.getFractureHalfLengthSelected()); + m_fractureHalfLengthCheckBox->setChecked(true); m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox); m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble()))); m_parameterTable->setItem(6, 3, new QTableWidgetItem()); @@ -622,11 +664,10 @@ void nmWxAutomaticFitting::setupParameterTable() void nmWxAutomaticFitting::setupControlPanel() { - m_controlLayout = new QVBoxLayout(); - m_controlLayout->setSizeConstraint(QLayout::SetFixedSize); - - // 上方弹簧 - m_controlLayout->addStretch(1); + // 四列设置:目标井、算法、目标误差、迭代上限。 + m_controlLayout = new QGridLayout(); + m_controlLayout->setHorizontalSpacing(16); + m_controlLayout->setVerticalSpacing(6); // 算法选择 QLabel* algorithmLabel = new QLabel(tr("Algorithm:")); @@ -634,30 +675,16 @@ void nmWxAutomaticFitting::setupControlPanel() m_algorithmCombo->addItem(tr("PSO (Particle Swarm)")); m_algorithmCombo->addItem(tr("LM")); m_algorithmCombo->setCurrentIndex(0); - m_algorithmCombo->setMaximumWidth(160); - m_algorithmCombo->setMinimumWidth(160); - - m_surrogateLabel = new QLabel(tr("PSO acceleration:")); - m_surrogateCombo = new QComboBox(); - m_surrogateCombo->addItem(tr("Off")); - m_surrogateCombo->addItem(tr("On")); - m_surrogateCombo->setCurrentIndex(automaticFittingData.getSurrogateScreeningEnabled() ? 1 : 0); - m_surrogateCombo->setMaximumWidth(160); - m_surrogateCombo->setMinimumWidth(160); + connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int))); - onAlgorithmChanged(m_algorithmCombo->currentIndex()); // 迭代次数 - QLabel* iterationLabel = new QLabel(tr("Number of iterations:")); + m_iterationLabel = new QLabel(); m_iterationEdit = new QLineEdit(QString::number(automaticFittingData.getIterationCount().getValue().toInt())); - m_iterationEdit->setMaximumWidth(100); - m_iterationEdit->setMinimumWidth(100); // 误差上限 - QLabel* errorLabel = new QLabel(tr("Error tolerance:")); + QLabel* errorLabel = new QLabel(tr("Target error:")); m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble())); - m_errorLimitEdit->setMaximumWidth(100); - m_errorLimitEdit->setMinimumWidth(100); // 目标井选择 QLabel* wellLabel = new QLabel(tr("Target Well:")); @@ -689,39 +716,26 @@ void nmWxAutomaticFitting::setupControlPanel() } connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int))); - m_targetWellCombo->setMaximumWidth(100); - m_targetWellCombo->setMinimumWidth(100); - - // 添加到垂直布局 - m_controlLayout->addWidget(algorithmLabel); - m_controlLayout->addWidget(m_algorithmCombo); - m_controlLayout->addSpacing(15); - - m_controlLayout->addWidget(m_surrogateLabel); - m_controlLayout->addWidget(m_surrogateCombo); - m_controlLayout->addSpacing(15); - - m_controlLayout->addWidget(iterationLabel); - m_controlLayout->addWidget(m_iterationEdit); - m_controlLayout->addSpacing(15); - - m_controlLayout->addWidget(errorLabel); - m_controlLayout->addWidget(m_errorLimitEdit); - m_controlLayout->addSpacing(15); - - m_controlLayout->addWidget(wellLabel); - m_controlLayout->addWidget(m_targetWellCombo); - - // 下方弹簧 - m_controlLayout->addStretch(1); + m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_targetWellCombo->setMinimumContentsLength(10); + m_controlLayout->addWidget(wellLabel, 0, 0); + m_controlLayout->addWidget(m_targetWellCombo, 1, 0); + m_controlLayout->addWidget(algorithmLabel, 0, 1); + m_controlLayout->addWidget(m_algorithmCombo, 1, 1); + m_controlLayout->addWidget(errorLabel, 0, 2); + m_controlLayout->addWidget(m_errorLimitEdit, 1, 2); + m_controlLayout->addWidget(m_iterationLabel, 0, 3); + m_controlLayout->addWidget(m_iterationEdit, 1, 3); + for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1); + onAlgorithmChanged(m_algorithmCombo->currentIndex()); } void nmWxAutomaticFitting::onAlgorithmChanged(int index) { - // LM 不使用 PSO 加速;只切换可用状态,保留切回 PSO 时的原选择。 + // 两种算法统一显示最大迭代次数,提示仍说明 LM 共用次数的含义。 const bool usePSO = (index == 0); - m_surrogateLabel->setEnabled(usePSO); - m_surrogateCombo->setEnabled(usePSO); + m_iterationLabel->setText(tr("Maximum iterations:")); + m_iterationEdit->setToolTip(usePSO ? QString() : tr("Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times.")); } void nmWxAutomaticFitting::setupButtons() @@ -729,7 +743,9 @@ void nmWxAutomaticFitting::setupButtons() QHBoxLayout* buttonLayout = new QHBoxLayout(); m_reverseBtn = new QPushButton(tr("Reverse Selection")); - m_okBtn = new QPushButton(tr("OK")); + m_okBtn = new QPushButton(tr("Start fitting")); + m_okBtn->setObjectName("startFitting"); + m_okBtn->setDefault(true); m_cancelBtn = new QPushButton(tr("Cancel")); // 设置按钮大小 @@ -737,10 +753,10 @@ void nmWxAutomaticFitting::setupButtons() m_okBtn->setMinimumWidth(80); m_cancelBtn->setMinimumWidth(80); - buttonLayout->addStretch(); buttonLayout->addWidget(m_reverseBtn); - buttonLayout->addWidget(m_okBtn); + buttonLayout->addStretch(); buttonLayout->addWidget(m_cancelBtn); + buttonLayout->addWidget(m_okBtn); m_mainLayout->addLayout(buttonLayout); @@ -989,7 +1005,8 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked()); automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked()); automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked()); - automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1); + // 界面不再提供 PSO 加速选项,同时关闭旧配置中可能保留的加速开关。 + automaticFittingData.setSurrogateScreeningEnabled(false); // 保存渗透率的最小值和最大值 automaticFittingData.getPermeabilityMin().setValue(m_parameterTable->item(0, 2)->text().toDouble()); @@ -1163,7 +1180,15 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector>& targ double targetError = m_errorLimitEdit->text().toDouble(); QString wellName = m_targetWellCombo->currentText(); m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName); - m_progressMonitor->setSelectedParameters(selectedParams); + // 单位和勾选参数保持同一顺序,供横排参数区显示。 + QStringList selectedUnits; + if(m_kCheckBox->isChecked()) selectedUnits << tr("mD"); + if(m_sCheckBox->isChecked()) selectedUnits << QString(); + if(m_cCheckBox->isChecked()) selectedUnits << tr("m^3/MPa"); + if(m_phiCheckBox->isChecked()) selectedUnits << QString(); + if(m_dfcCheckBox->isChecked()) selectedUnits << tr("md.m"); + if(m_fractureHalfLengthCheckBox->isChecked()) selectedUnits << tr("m"); + m_progressMonitor->setSelectedParameters(selectedParams, selectedUnits); m_progressMonitor->show(); QTimer::singleShot(100, this, SLOT(runAutoFitting())); diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp index de0d367c..f220d974 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp @@ -1,9 +1,11 @@ #include "nmWxAutomaticfittingStart.h" #include +#include #include #include #include #include +#include #include #include #include @@ -33,7 +35,8 @@ nmAutomaticFittingCurveChart::nmAutomaticFittingCurveChart(QWidget* parent) , m_bestFitness(0.0) , m_pseudoPressureMode(false) { - setMinimumHeight(220); + // 保留绘图空间,同时允许小屏幕压缩曲线区域的高度。 + setMinimumHeight(160); setMinimumWidth(420); } @@ -181,7 +184,8 @@ void nmAutomaticFittingCurveChart::paintEvent(QPaintEvent* event) QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); - painter.fillRect(rect(), QColor(240, 240, 240)); + // 保持全宽白色绘图区,曲线和实测标记沿用原有红绿配色。 + painter.fillRect(rect(), Qt::white); const int marginLeft = 62; const int marginTop = 18; @@ -191,7 +195,7 @@ void nmAutomaticFittingCurveChart::paintEvent(QPaintEvent* event) qMax(10, width() - marginLeft - marginRight), qMax(10, height() - marginTop - marginBottom)); - painter.fillRect(m_chartRect, QColor(252, 252, 252)); + painter.fillRect(m_chartRect, Qt::white); drawGrid(painter); drawAxes(painter); drawCurveLine(painter, m_bestPressure, QColor(250, 74, 74)); @@ -210,7 +214,7 @@ void nmAutomaticFittingCurveChart::drawGrid(QPainter& painter) int yStart = static_cast(qFloor(m_logRange.top())); int yEnd = static_cast(qCeil(m_logRange.bottom())); - painter.setPen(QPen(QColor(236, 236, 236), 1, Qt::SolidLine)); + painter.setPen(QPen(QColor(247, 248, 250), 1, Qt::SolidLine)); for(int decade = xStart; decade <= xEnd; ++decade) { for(int multiplier = 2; multiplier <= 9; ++multiplier) { double value = decade + qLn(static_cast(multiplier)) / qLn(10.0); @@ -237,7 +241,7 @@ void nmAutomaticFittingCurveChart::drawGrid(QPainter& painter) } } - painter.setPen(QPen(QColor(214, 214, 214), 1, Qt::SolidLine)); + painter.setPen(QPen(QColor(228, 232, 238), 1, Qt::SolidLine)); for(int x = xStart; x <= xEnd; ++x) { if(x < m_logRange.left() || x > m_logRange.right()) { continue; @@ -397,167 +401,237 @@ nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent) // 设置窗口属性 setWindowTitle(tr("Auto Fitting Progress")); - setMinimumSize(1000, 650); + // 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。 + resize(sizeHint()); setModal(false); } +QSize nmWxAutomaticfittingStart::sizeHint() const +{ + // 加宽进程窗口,为曲线和完整日志行留出空间,同时保留屏幕边缘。 + QDesktopWidget* desktop = QApplication::desktop(); + if(desktop == nullptr) return iDlgBase::sizeHint(); + const QRect available = desktop->availableGeometry(parentWidget() ? parentWidget()->window() : this); + return QSize(available.width() * 75 / 100, available.height() * 65 / 100); +} + nmWxAutomaticfittingStart::~nmWxAutomaticfittingStart() { } void nmWxAutomaticfittingStart::setupUI() { - // 创建主要布局 - mainLayout = new QHBoxLayout(); - leftLayout = new QVBoxLayout(); - rightLayout = new QVBoxLayout(); - - // 设置左右布局 + // 曲线和日志左右并排,参数横排占满下方整行,底部使用一行汇总。 + setObjectName("autoFittingProgress"); + setFont(QFont("Microsoft YaHei", 9)); + setStyleSheet( + "#autoFittingProgress { background: #ffffff; color: #233247; }" + "#autoFittingProgress QGroupBox { background: #ffffff; color: #233247; border: none; margin-top: 0; padding-top: 0; font-weight: bold; }" + "#autoFittingProgress QLabel { color: #66768a; background: transparent; font-weight: normal; }" + "#autoFittingProgress QGroupBox#fittingSummary { padding-top: 0; }" + "#autoFittingProgress QGroupBox#fittingSummary QLabel { font-size: 14px; color: #46556b; }" + "#autoFittingProgress QWidget#parameterStrip { background: #ffffff; border-top: 1px solid #e3e8ef; }" + "#autoFittingProgress QWidget#parameterSeparator { background: #e3e8ef; }" + "#autoFittingProgress QLabel#parameterName { font-size: 12px; }" + "#autoFittingProgress QLabel#parameterValue { color: #233247; font-size: 18px; font-weight: 500; }" + "#autoFittingProgress QGroupBox#fittingSummary QLabel#summaryValue { color: #233247; }" + "#autoFittingProgress QGroupBox#fittingSummary QLabel#stateBadge { background: #edf3fc; color: #366baf; border-radius: 5px; padding: 4px 10px; font-size: 12px; }" + "#autoFittingProgress QGroupBox#fittingLog { padding-top: 0; }" + "#autoFittingProgress QLabel#sectionHeading { color: #233247; font-size: 13px; font-weight: bold; }" + "#autoFittingProgress QWidget#fittingFooter { background: #ffffff; border-top: 1px solid #e3e8ef; }" + "#autoFittingProgress QLabel#resultSummary { color: #66768a; font-size: 14px; }" + "#autoFittingProgress QLabel#stageStatus { color: #66768a; font-size: 12px; }" + "#autoFittingProgress QLabel#runSummary { color: #46556b; font-size: 14px; }" + "#autoFittingProgress QTextEdit { border: none; background: #ffffff; color: #233247; font-size: 13px; font-weight: normal; selection-background-color: #eaf1fb; selection-color: #233247; }" + "#autoFittingProgress QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 5px; padding: 8px 22px; min-width: 64px; min-height: 20px; }" + "#autoFittingProgress QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }" + "#autoFittingProgress QPushButton:pressed { background: #dce8f8; }" + "#autoFittingProgress QPushButton:disabled { background: #f0f2f5; color: #98a2b1; border-color: #e0e5ec; }" + "#autoFittingProgress QPushButton#logToggle { border: none; background: transparent; color: #738091; min-width: 0; padding: 3px 8px; }" + "#autoFittingProgress QPushButton#logToggle:hover { color: #396fc0; }" + "#autoFittingProgress QSplitter::handle { background: #ffffff; }"); + + // 控件复用原有数据更新逻辑,仅重新组织所属区域。 setupParameterArea(); - setupTableArea(); + setupParameterStrip(); setupChartArea(); - setupLogArea(); setupControlArea(); - - // 左侧布局 - leftLayout->addWidget(parameterGroup); - leftLayout->addStretch(); - leftLayout->addWidget(tableGroup); - - // 右侧布局 - rightSplitter = new QSplitter(Qt::Vertical, this); + setupLogArea(); + // 日志只占右侧较窄区域,两侧底边对齐,分隔条允许调整宽度。 + rightSplitter = new QSplitter(Qt::Horizontal, this); + rightSplitter->setHandleWidth(4); rightSplitter->addWidget(chartGroup); rightSplitter->addWidget(logGroup); - rightSplitter->setStretchFactor(0, 3); - rightSplitter->setStretchFactor(1, 2); - rightSplitter->setSizes(QList() << 320 << 260); - rightLayout->addWidget(rightSplitter); - - // 主布局 - mainLayout->addLayout(leftLayout, 1); - mainLayout->addLayout(rightLayout, 2); + rightSplitter->setStretchFactor(0, 1); + rightSplitter->setStretchFactor(1, 1); + rightSplitter->setChildrenCollapsible(false); + rightSplitter->setSizes(QList() << 650 << 350); // 控制区域布局 - QHBoxLayout *controlLayout = new QHBoxLayout(); - controlLayout->addWidget(progressBar); + QWidget* footer = new QWidget(this); + footer->setObjectName("fittingFooter"); + footer->setAttribute(Qt::WA_StyledBackground, true); + QHBoxLayout *controlLayout = new QHBoxLayout(footer); + controlLayout->setContentsMargins(22, 14, 22, 14); + controlLayout->setSpacing(24); + controlLayout->addWidget(resultSummary); + controlLayout->addWidget(runSummary); + controlLayout->addStretch(); + controlLayout->addWidget(progressBar, 1); controlLayout->addWidget(stopButton); QVBoxLayout *overallLayout = new QVBoxLayout(); - overallLayout->addLayout(mainLayout); - overallLayout->addLayout(controlLayout); + overallLayout->setContentsMargins(0, 0, 0, 0); + overallLayout->setSpacing(0); + overallLayout->addWidget(parameterGroup); + overallLayout->addWidget(rightSplitter, 1); + overallLayout->addWidget(parameterStrip); + overallLayout->addWidget(footer); setLayout(overallLayout); } void nmWxAutomaticfittingStart::setupParameterArea() { - parameterGroup = new QGroupBox(tr("Parameter Display"), this); - parameterGroup->setFixedWidth(280); + parameterGroup = new QGroupBox(this); + parameterGroup->setObjectName("fittingSummary"); QGridLayout *paramLayout = new QGridLayout(parameterGroup); + paramLayout->setContentsMargins(22, 19, 22, 17); + paramLayout->setHorizontalSpacing(12); + paramLayout->setVerticalSpacing(8); // 创建标签 algorithmTypeLabel = new QLabel(tr("Algorithm:")); - currentIterationLabel = new QLabel(tr("Current Iteration:")); - maxIterationLabel = new QLabel(tr("Max Iteration:")); + currentIterationLabel = new QLabel(tr("Iterations:")); currentComfortLabel = new QLabel(tr("Current Error:")); bestComfortLabel = new QLabel(tr("Best Error:")); - targetPrecisionLabel = new QLabel(tr("Target Precision:")); + targetPrecisionLabel = new QLabel(tr("Target error:")); targetWellLabel = new QLabel(tr("Target Well:")); // 创建数值显示标签 algorithmTypeValue = new QLabel("PSO"); - currentIterationValue = new QLabel("0"); - maxIterationValue = new QLabel("100"); + stateBadge = new QLabel(tr("Waiting for fitting to start")); + stateBadge->setObjectName("stateBadge"); + currentIterationValue = new QLabel("0 / 100"); + currentIterationValue->setObjectName("summaryValue"); currentComfortValue = new QLabel("N/A"); bestComfortValue = new QLabel("N/A"); targetPrecisionValue = new QLabel("0.001"); targetWellValue = new QLabel("N/A"); - - // 添加到布局 - paramLayout->addWidget(algorithmTypeLabel, 0, 0); - paramLayout->addWidget(algorithmTypeValue, 0, 1); - paramLayout->addWidget(currentIterationLabel, 1, 0); - paramLayout->addWidget(currentIterationValue, 1, 1); - paramLayout->addWidget(maxIterationLabel, 2, 0); - paramLayout->addWidget(maxIterationValue, 2, 1); - paramLayout->addWidget(currentComfortLabel, 3, 0); - paramLayout->addWidget(currentComfortValue, 3, 1); - paramLayout->addWidget(bestComfortLabel, 4, 0); - paramLayout->addWidget(bestComfortValue, 4, 1); - paramLayout->addWidget(targetPrecisionLabel, 5, 0); - paramLayout->addWidget(targetPrecisionValue, 5, 1); - paramLayout->addWidget(targetWellLabel, 6, 0); - paramLayout->addWidget(targetWellValue, 6, 1); + algorithmTypeValue->setObjectName("summaryValue"); + targetWellValue->setObjectName("summaryValue"); + targetPrecisionValue->setObjectName("summaryValue"); + + // 顶部统一显示累计迭代数和上限;PSO 的额外误差信息放在第二行。 + paramLayout->addWidget(targetWellLabel, 0, 0); + paramLayout->addWidget(targetWellValue, 0, 1); + paramLayout->addWidget(algorithmTypeLabel, 0, 2); + paramLayout->addWidget(algorithmTypeValue, 0, 3); + paramLayout->addWidget(targetPrecisionLabel, 0, 4); + paramLayout->addWidget(targetPrecisionValue, 0, 5); + paramLayout->addWidget(currentIterationLabel, 0, 6); + paramLayout->addWidget(currentIterationValue, 0, 7); + paramLayout->addWidget(stateBadge, 0, 9, Qt::AlignRight | Qt::AlignVCenter); + paramLayout->addWidget(currentComfortLabel, 1, 0); + paramLayout->addWidget(currentComfortValue, 1, 1); + paramLayout->addWidget(bestComfortLabel, 1, 2); + paramLayout->addWidget(bestComfortValue, 1, 3); + paramLayout->setColumnStretch(8, 1); + targetWellValue->setWordWrap(true); parameterGroup->setLayout(paramLayout); } -void nmWxAutomaticfittingStart::setupTableArea() +void nmWxAutomaticfittingStart::setupParameterStrip() { - tableGroup = new QGroupBox(tr("Parameter Table"), this); - tableGroup->setFixedWidth(300); - - QVBoxLayout *tableLayout = new QVBoxLayout(tableGroup); - - tableWidget = new QTableWidget(0, 2, this); - tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Parameter Name") << tr("Optimal Value")); - - // 设置表格属性 - tableWidget->setAlternatingRowColors(true); - tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - tableWidget->verticalHeader()->setVisible(false); - tableWidget->setMinimumHeight(310); - - // 设置列宽 - QHeaderView* header = tableWidget->horizontalHeader(); - header->setResizeMode(0, QHeaderView::ResizeToContents); - header->setResizeMode(1, QHeaderView::Stretch); - - // 设置表格填充整个区域 - tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - // 隐藏滚动条 - tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - tableLayout->addWidget(tableWidget); - tableGroup->setLayout(tableLayout); + // 参数独占曲线和日志下方的整行,横向均分空间,不保留尾部空白。 + parameterStrip = new QWidget(this); + parameterStrip->setObjectName("parameterStrip"); + parameterStrip->setAttribute(Qt::WA_StyledBackground, true); + parameterStrip->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + // 标题与参数共用网格,竖线跨越两行,上下留白均为 4 像素。 + parameterLayout = new QGridLayout(parameterStrip); + parameterLayout->setContentsMargins(22, 4, 22, 4); + parameterLayout->setHorizontalSpacing(0); + parameterLayout->setVerticalSpacing(2); } void nmWxAutomaticfittingStart::setupChartArea() { - chartGroup = new QGroupBox(tr("Fitting Curve"), this); + // 曲线上方仅保留标题。 + chartGroup = new QGroupBox(this); QVBoxLayout* chartLayout = new QVBoxLayout(chartGroup); + QLabel* heading = new QLabel(tr("Fitting Curve"), chartGroup); + heading->setObjectName("sectionHeading"); + chartLayout->addWidget(heading); curveChart = new nmAutomaticFittingCurveChart(chartGroup); curveChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - chartLayout->setMargin(6); + chartLayout->setContentsMargins(22, 17, 22, 12); chartLayout->addWidget(curveChart); chartGroup->setLayout(chartLayout); - chartGroup->setMinimumHeight(240); + chartGroup->setMinimumHeight(200); } void nmWxAutomaticfittingStart::setupLogArea() { - logGroup = new QGroupBox(tr("Log Window"), this); + // 日志标题始终可见,正文收起时仍继续接收日志。 + logGroup = new QGroupBox(this); + logGroup->setObjectName("fittingLog"); QVBoxLayout *logLayout = new QVBoxLayout(logGroup); + logLayout->setContentsMargins(18, 13, 22, 16); + logLayout->setSpacing(8); + logLayout->setAlignment(Qt::AlignTop); + QHBoxLayout* logHeader = new QHBoxLayout(); + QLabel* heading = new QLabel(tr("Fitting process"), logGroup); + heading->setObjectName("sectionHeading"); + logToggle = new QPushButton(tr("Collapse"), logGroup); + logToggle->setObjectName("logToggle"); + logToggle->setAutoDefault(false); + logHeader->addWidget(heading); + logHeader->addStretch(); + logHeader->addWidget(logToggle); + logLayout->addLayout(logHeader); + // 阶段状态放到标题下方,避免挤占窄日志区的标题和按钮。 + logLayout->addWidget(stageStatus); + connect(logToggle, SIGNAL(clicked()), this, SLOT(onToggleLog())); logTextEdit = new QTextEdit(this); logTextEdit->setReadOnly(true); - logTextEdit->setFont(QFont("Consolas", 9)); + // 小屏幕允许缩短可见日志区域,完整内容仍可通过滚动查看。 + logTextEdit->setMinimumHeight(48); + logTextEdit->setMinimumWidth(180); + logTextEdit->setFont(font()); + // 正文与标题左对齐,沿用窗口白底,不额外绘制日志底色。 + logTextEdit->document()->setDocumentMargin(0); // 添加初始日志 addLogMessage(tr("System initialization completed")); addLogMessage(tr("Waiting for fitting to start")); - logLayout->addWidget(logTextEdit); + logLayout->addWidget(logTextEdit, 1); logGroup->setLayout(logLayout); } void nmWxAutomaticfittingStart::setupControlArea() { + // 运行状态放在拟合过程标题下方;底部汇总保持单行。 + stageStatus = new QLabel(tr("Waiting for fitting to start"), this); + stageStatus->setObjectName("stageStatus"); + stageStatus->setWordWrap(true); + stageStatus->hide(); + resultSummary = new QLabel(this); + resultSummary->setObjectName("resultSummary"); + resultSummary->setTextFormat(Qt::RichText); + resultSummary->setWordWrap(false); + resultSummary->hide(); + runSummary = new QLabel(this); + runSummary->setObjectName("runSummary"); + runSummary->setWordWrap(false); + runSummary->hide(); // 进度条 progressBar = new QProgressBar(this); progressBar->setRange(0, 100); @@ -571,7 +645,6 @@ void nmWxAutomaticfittingStart::setupControlArea() // 停止按钮 stopButton = new QPushButton(tr("Stop"), this); - stopButton->setFixedSize(80, 30); stopButton->setEnabled(false); // 连接信号槽 @@ -583,10 +656,10 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitte m_autoFitterPSO = autoFitter; m_autoFitterLM = nullptr; m_algorithmName = "PSO"; + updateAlgorithmDisplay(); // 更新算法类型显示 algorithmTypeValue->setText("PSO"); - algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }"); if (m_autoFitterPSO) { connect(m_autoFitterPSO, SIGNAL(progressUpdated(int, double)), @@ -612,13 +685,15 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter m_autoFitterPSO = nullptr; m_autoFitterLM = autoFitter; m_algorithmName = "LM"; + updateAlgorithmDisplay(); // LM 日志由优化器统一组织,清除通用初始化提示。 logTextEdit->clear(); algorithmTypeValue->setText(m_algorithmName); - algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }"); if (m_autoFitterLM) { + connect(m_autoFitterLM, SIGNAL(fittingStageChanged(int)), + this, SLOT(onLMStageChanged(int))); connect(m_autoFitterLM, SIGNAL(progressUpdated(int, double)), this, SLOT(onFittingProgress(int, double))); connect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)), @@ -634,14 +709,48 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter } } +void nmWxAutomaticfittingStart::updateAlgorithmDisplay() +{ + // 迭代数始终在顶部显示,PSO 保留原有额外误差和进度条。 + const bool lm = m_autoFitterLM != nullptr; + QLabel* psoLabels[] = { currentComfortLabel, currentComfortValue, + bestComfortLabel, bestComfortValue }; + for(int i = 0; i < 4; ++i) psoLabels[i]->setVisible(!lm); + progressBar->setVisible(!lm); + stageStatus->setVisible(lm); + resultSummary->setVisible(lm); + if(lm) updateCurrentError(m_autoFitterLM->getBestFitness()); + if(lm) onLMStageChanged(0); +} + +void nmWxAutomaticfittingStart::onLMStageChanged(int stage) +{ + // 仅更新拟合过程区域的运行状态,不在曲线上方增加阶段栏。 + const QString stages[] = { tr("Calculating initial error..."), tr("Height alignment"), + tr("Shape adjustment"), tr("Total-error adjustment") }; + stageStatus->setText(stages[stage]); +} + +void nmWxAutomaticfittingStart::onToggleLog() +{ + // 收起正文时把横向空间让给曲线,保留标题和按钮以便重新展开。 + const bool collapse = !logTextEdit->isHidden(); + if(collapse) expandedLogSizes = rightSplitter->sizes(); + logTextEdit->setVisible(!collapse); + logToggle->setText(collapse ? tr("Expand") : tr("Collapse")); + logGroup->layout()->activate(); + logGroup->setMaximumWidth(collapse ? logGroup->minimumSizeHint().width() : QWIDGETSIZE_MAX); + if(!collapse && !expandedLogSizes.isEmpty()) rightSplitter->setSizes(expandedLogSizes); +} + void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double targetError, const QString& wellName) { m_maxIterations = maxIterations; m_targetError = targetError; m_wellName = wellName; - maxIterationValue->setText(QString::number(maxIterations)); - targetPrecisionValue->setText(formatScientific(targetError)); + currentIterationValue->setText(QString("0 / %1").arg(maxIterations)); + targetPrecisionValue->setText(QString::number(targetError, 'g', 6)); targetWellValue->setText(wellName); progressBar->setRange(0, maxIterations); @@ -654,6 +763,7 @@ void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double t void nmWxAutomaticfittingStart::markFittingStarted() { m_startTime = QDateTime::currentDateTime(); + stateBadge->setText(tr("Fitting in progress")); const QString algorithmName = m_algorithmName; QString timestamp = m_startTime.toString("yyyy-MM-dd hh:mm:ss"); @@ -663,12 +773,56 @@ void nmWxAutomaticfittingStart::markFittingStarted() .arg(timestamp)); } -void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& parameterNames) +void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& parameterNames, + const QStringList& parameterUnits) { - m_selectedParameters = parameterNames; + // 仅在选择变化时重建标签,拟合期间只更新数值,避免布局反复抖动。 + QLayoutItem* item = nullptr; + while((item = parameterLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + parameterValues.clear(); + for(int column = 0; column < parameterLayout->columnCount(); ++column) { + parameterLayout->setColumnStretch(column, 0); + } + QLabel* heading = new QLabel(tr("Current parameters"), parameterStrip); + heading->setObjectName("sectionHeading"); + parameterLayout->addWidget(heading, 0, 0); + for(int i = 0; i < parameterNames.size(); ++i) { + // 名称和单位在上、数值在下,相邻参数用细竖线分隔。 + QWidget* cell = new QWidget(parameterStrip); + cell->setAttribute(Qt::WA_StyledBackground, true); + QVBoxLayout* cellLayout = new QVBoxLayout(cell); + cellLayout->setContentsMargins(12, 0, 12, 0); + cellLayout->setSpacing(2); + const QString unit = parameterUnits.value(i); + QLabel* name = new QLabel(parameterNames[i] + (unit.isEmpty() ? QString() : " / " + unit), cell); + name->setObjectName("parameterName"); + name->setWordWrap(true); + name->setAlignment(Qt::AlignCenter); + QLabel* value = new QLabel("--", cell); + value->setObjectName("parameterValue"); + value->setAlignment(Qt::AlignCenter); + value->setTextInteractionFlags(Qt::TextSelectableByMouse); + cellLayout->addWidget(name, 1); + cellLayout->addWidget(value); + parameterLayout->addWidget(cell, 1, i * 2); + parameterLayout->setColumnStretch(i * 2, 1); + if(i > 0) { + // 分隔线独立于参数单元格,避免标题导致上下间距不一致。 + QWidget* separator = new QWidget(parameterStrip); + separator->setObjectName("parameterSeparator"); + separator->setAttribute(Qt::WA_StyledBackground, true); + separator->setFixedWidth(1); + parameterLayout->addWidget(separator, 0, i * 2 - 1, 2, 1); + } + parameterValues.append(value); + } + parameterStrip->setVisible(!parameterNames.isEmpty()); - // 立即更新参数表格 - updateParameterTable(); + // 立即显示当前已接受的参数值。 + updateParameterValues(); const QString algorithmName = m_algorithmName; if(!m_autoFitterLM) addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", "))); @@ -690,17 +844,17 @@ void nmWxAutomaticfittingStart::setPseudoPressureMode(bool enabled) void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness) { - // LM 预调整没有固定总步数,单独显示阶段;整体阶段从局部迭代 0 开始计进度。 - if (m_autoFitterLM && iteration < 0) { - progressBar->setValue(0); - progressBar->setFormat(tr("Pre-adjustment")); - currentIterationValue->setText(tr("Pre-adjustment")); + // LM 各阶段显示累计迭代次数;达到上限之前仍可能提前收敛,不显示完成百分比。 + currentIterationValue->setText(QString("%1 / %2").arg(qMax(0, iteration)).arg(m_maxIterations)); + if(m_autoFitterLM) { + updateCurrentError(fitness); + return; } else { - int displayIteration = qMax(m_autoFitterLM ? 0 : 1, qMin(iteration, m_maxIterations)); + int displayIteration = qMax(1, qMin(iteration, m_maxIterations)); progressBar->setValue(displayIteration); double progress = (double)displayIteration / m_maxIterations * 100; progressBar->setFormat(QString("%1%").arg(progress, 0, 'f', 1)); - currentIterationValue->setText(QString::number(displayIteration)); + currentIterationValue->setText(QString("%1 / %2").arg(displayIteration).arg(m_maxIterations)); } currentComfortValue->setText(formatScientific(fitness)); @@ -713,8 +867,8 @@ void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness) if(!m_autoFitterLM) addLogMessage(tr("Better solution found: %1").arg(formatScientific(fitness))); - // 更新参数表格 - updateParameterTable(); + // 更新参数值。 + updateParameterValues(); } else { bestComfortValue->setStyleSheet("QLabel { color: black; }"); } @@ -724,11 +878,20 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m { m_isFinished = true; m_isFinalizing = false; + stateBadge->setText(success ? tr("Fitting ended") : tr("Fitting failed")); + stateBadge->setStyleSheet(success + ? "QLabel { color: #28765f; background: #edf7f2; }" + : "QLabel { color: #a04438; background: #fff0ed; }"); const QString algorithmName = m_algorithmName; // 更新状态 - if (success) { + if(m_autoFitterLM) { + stageStatus->setText(success ? tr("Fitting ended") : tr("Fitting failed")); + // 完成区直接读取优化器数值,失败评价显示占位符,不暴露惩罚值。 + updateCurrentError(m_autoFitterLM->getBestFitness()); + stageStatus->hide(); + } else if (success) { progressBar->setValue(m_maxIterations); // 区分正常完成和用户停止 @@ -781,8 +944,8 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m disconnect(stopButton, SIGNAL(clicked()), this, SLOT(onStopButtonClicked())); connect(stopButton, SIGNAL(clicked()), this, SLOT(close())); - // 最终更新参数表格 - updateParameterTable(); + // 同步最终参数值。 + updateParameterValues(); // 添加完成时间戳 QDateTime endTime = QDateTime::currentDateTime(); @@ -803,7 +966,13 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m // LM 的调用次数由优化器汇总,这里只在结束时显示一次总耗时。 if(m_autoFitterLM) { - addLogMessage(tr("Total time: %1 min %2 s").arg(minutes).arg(remainSec, 0, 'f', 1)); + // 不足一分钟只显示秒,窗口汇总和完成日志共用同一耗时文本。 + const QString elapsed = minutes == 0 ? tr("%1 s").arg(seconds, 0, 'f', 1) + : tr("%1 min %2 s").arg(minutes).arg(remainSec, 0, 'f', 1); + addLogMessage(tr("Total time: %1").arg(elapsed)); + runSummary->setText(tr("Solver calls: %1 | Total time: %2") + .arg(m_autoFitterLM->getSolverCallCount()).arg(elapsed)); + runSummary->show(); } else { addLogMessage(tr("Total fitting time: %1 s (%2 min %3 s), total solver calls: %4") .arg(seconds, 0, 'f', 2).arg(minutes).arg(remainSec, 0, 'f', 2).arg(totalEvaluations)); @@ -816,6 +985,8 @@ void nmWxAutomaticfittingStart::onFinalizingStarted() { // 最终完整求解负责生成正式快照,此阶段不能再次停止或关闭进度窗口。 m_isFinalizing = true; + stateBadge->setText(tr("Finalizing...")); + if(m_autoFitterLM) stageStatus->setText(tr("Generating final result...")); stopButton->setEnabled(false); stopButton->setText(tr("Finalizing...")); addLogMessage(tr("Generating the final result with the current best parameters...")); @@ -837,6 +1008,8 @@ void nmWxAutomaticfittingStart::onStopButtonClicked() // 先锁定按钮,等优化循环退出后由 finalizingStarted() 切换到最终求解状态。 stopButton->setEnabled(false); stopButton->setText(tr("Stopping...")); + stateBadge->setText(tr("Stopping...")); + if(m_autoFitterLM) stageStatus->setText(tr("Stopping...")); if (m_autoFitterPSO) { m_autoFitterPSO->stopFitting(); } else if (m_autoFitterLM) { @@ -847,40 +1020,27 @@ void nmWxAutomaticfittingStart::onStopButtonClicked() } } -void nmWxAutomaticfittingStart::updateParameterTable() +void nmWxAutomaticfittingStart::updateParameterValues() { - // 设置表格行数 - tableWidget->setRowCount(m_selectedParameters.size()); - - // 更新表格内容 - for (int i = 0; i < m_selectedParameters.size(); ++i) { - // 参数名 - QTableWidgetItem* nameItem = new QTableWidgetItem(m_selectedParameters.at(i)); - nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - tableWidget->setItem(i, 0, nameItem); - - // 最优参数值 - QString valueText = "N/A"; - - if (m_autoFitterPSO) { - QVector bestSolution = m_autoFitterPSO->getBestSolution(); - if (i < bestSolution.size()) { - valueText = formatScientific(bestSolution[i]); - } - } else if (m_autoFitterLM) { - QVector bestSolution = m_autoFitterLM->getBestSolution(); - if (i < bestSolution.size()) { - valueText = formatScientific(bestSolution[i]); - } - } - - QTableWidgetItem* valueItem = new QTableWidgetItem(valueText); - valueItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - valueItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); - tableWidget->setItem(i, 1, valueItem); + // 使用当前已接受参数,普通数值优先显示十进制,极小数仍保留有效位。 + const QVector values = m_autoFitterLM ? m_autoFitterLM->getBestSolution() + : (m_autoFitterPSO ? m_autoFitterPSO->getBestSolution() : QVector()); + for(int i = 0; i < parameterValues.size(); ++i) { + parameterValues[i]->setText(i < values.size() ? QString::number(values[i], 'g', 6) : "--"); + parameterValues[i]->setToolTip(i < values.size() ? QString::number(values[i], 'g', 15) : QString()); } } +void nmWxAutomaticfittingStart::updateCurrentError(double fitness) +{ + // 仅展示当前已接受参数对应的总误差,未获得有效结果时显示占位符。 + const QString value = nmAutoFitChartIsFinite(fitness) && fitness >= 0.0 && fitness < 1.0e9 + ? QString::number(fitness, 'f', 6) : "--"; + resultSummary->setText(tr("Current error: %1") + .arg(QString("%1").arg(value))); + resultSummary->show(); +} + void nmWxAutomaticfittingStart::addLogMessage(const QString& message) { // 获取滚动条 @@ -957,4 +1117,9 @@ void nmWxAutomaticfittingStart::onBestCurveUpdated(QVector > tar } curveChart->setBestLogLogData(bestData, iteration, fitness); + // 形状改善时总误差可能上升,参数值仍须同步到当前已接受曲线。 + if(m_autoFitterLM) { + updateParameterValues(); + updateCurrentError(fitness); + } }