From c6c791dea5e057cc90f5c697e08f9bd008643255 Mon Sep 17 00:00:00 2001 From: AmauryBilocq <amaury.bilocq@gmail.com> Date: Fri, 6 Nov 2020 12:03:29 +0100 Subject: [PATCH] Quasi-simultaneous coupler for VII with test cases --- flow/models/n0012.db | Bin 0 -> 3861 bytes flow/models/n0012s.geo | 298 +++++++++++++++ flow/models/rae2822.db | Bin 0 -> 3751 bytes flow/tests/bliLiftComp.py | 37 +- flow/tests/bliLiftIncomp.py | 134 ------- flow/tests/bliNonLift.py | 134 ------- flow/viscous/Airfoil.py | 13 +- flow/viscous/Boundary.py | 8 +- flow/viscous/Wake.py | 11 +- flow/viscous/coupler.py | 22 +- flow/viscous/newton.py | 70 ++-- flow/viscous/solver.py | 738 +++++++++++++++++++++--------------- 12 files changed, 812 insertions(+), 653 deletions(-) create mode 100644 flow/models/n0012.db create mode 100644 flow/models/n0012s.geo create mode 100644 flow/models/rae2822.db delete mode 100644 flow/tests/bliLiftIncomp.py delete mode 100644 flow/tests/bliNonLift.py diff --git a/flow/models/n0012.db b/flow/models/n0012.db new file mode 100644 index 0000000000000000000000000000000000000000..b8f485733b337b3280214d824d6f6b73da657805 GIT binary patch literal 3861 zcmbuCTW^~%6vzFXPvOT+(-KTVAbCrZwwlzNrtS@Ca=;;|fDty$GHKs^$C$K`i_?_k z0RrKFj(_LE@#Dkw-Rug7RMI6C3<m|Hl7)D=#XMsPU1GtCS609;{9H0TD<e!iOvY2& znmQJ?E!*mi;X;3ivNBz=0$KM=(v*iR8QgK6BZP(oS@l<)eJAXjZCR*3s0-EI9vQZT zW^u9Naf0uzamvIRi})|oYU=+sx-DqFM%D*DNIOE%Z7Sdi1kd;3G1t*Yb+D^bGMPAU z?s((ovUc7)AN59QHpOcTCzM^crJO|x!+eDcE~$)p_P)rr>}hhI-4vX&OsbHbSM1vr z0s@EXWHj*r$}S!*X{s)GO5+SCEQ{nCfBEx0jEk=)Ty+44lKz;~_9Cfq3*HSaRxwM$ zV=2<2r*fw0k>ga06S9c2sEXZS7S#0Lv-K^eC}J5a;s8{7gjfg9-m8SibSZdJN``YP z*T6;zDZ`EASWNv{O3PyFFL+R<5Gvt6-(33}`pSgAO8CaFibwcOlU}Pblve|7F^oY% z=%mq>2%0Bcun>$k%-W}uhgh0u6NMJfXsRl%(<(B{B6dGw9IfJnfmV;$N9>994cvZE z2Jcm|Ytzs<^rDU}>Ek4|532TeK1>dIK2+^XRZYI$ia4MNjtXAp0tlGMdZ)=wbe`0W zv5MMLOB<8H;vj&nbTPC`R~LOVYzOKU(4GG+VaZzg)hepvo@zjEG+Jt;3k3Td1cSP( z18IO@4^BgX=t_9wrI(=qMg6Kf3f53*=ksCM1;yx~EA8dO(11e3;e{CiY6c9$E*UZE zfWbR#&U+X}1`HwJWS_|i&@{jpb%Eh@fKg8xhVpO>FsPouE~^d@G>{l~Au;Yk;%uWB zpC|GAG8CX_K;d>l;dViBwo$kS6dU?-VWR*w1BOoTPwyNCLpOF0C&0Z%;TbSgQ|D#r z0CENnlhf&7lf#Jp`;q0P#A={X6H2wmQq$6`lzhHs!OLgI&b@)nM)Rrv3wD8`o?EZ{ zS)MOo!|4CyG=<@L5W!?s1nAOvOwEKnx7Dp5tL^CiAWqN|n2&{oVYwc~0-nO^TX}d? U>8g}cZ>Z`iwx!gVkDHPF514_xIsgCw literal 0 HcmV?d00001 diff --git a/flow/models/n0012s.geo b/flow/models/n0012s.geo new file mode 100644 index 00000000..d42b3531 --- /dev/null +++ b/flow/models/n0012s.geo @@ -0,0 +1,298 @@ +/* Naca0012 */ + +// Geometry +DefineConstant[ xLgt = { 5.0, Name "Domain length (x-dir)" } ]; +DefineConstant[ yLgt = { 5.0, Name "Domain length (y-dir)" } ]; + +// Mesh +nChord = 200; +pChord = 0.3; +nLe = 115; +pLe = 1.015; +nPerp = 25; +pPerp = 1.25; + +/************** + Geometry + **************/ + +//Point(201) = {1.000000,0.000000,0}; +Point(200) = {0.999753,-0.000035,0}; +Point(199) = {0.999013,-0.000141,0}; +Point(198) = {0.997781,-0.000317,0}; +Point(197) = {0.996057,-0.000562,0}; +Point(196) = {0.993844,-0.000876,0}; +Point(195) = {0.991144,-0.001258,0}; +Point(194) = {0.987958,-0.001707,0}; +Point(193) = {0.984292,-0.002222,0}; +Point(192) = {0.980147,-0.002801,0}; +Point(191) = {0.975528,-0.003443,0}; +Point(190) = {0.970440,-0.004147,0}; +Point(189) = {0.964888,-0.004909,0}; +Point(188) = {0.958877,-0.005729,0}; +Point(187) = {0.952414,-0.006603,0}; +Point(186) = {0.945503,-0.007531,0}; +Point(185) = {0.938153,-0.008510,0}; +Point(184) = {0.930371,-0.009537,0}; +Point(183) = {0.922164,-0.010610,0}; +Point(182) = {0.913540,-0.011726,0}; +Point(181) = {0.904508,-0.012883,0}; +Point(180) = {0.895078,-0.014079,0}; +Point(179) = {0.885257,-0.015310,0}; +Point(178) = {0.875056,-0.016574,0}; +Point(177) = {0.864484,-0.017868,0}; +Point(176) = {0.853553,-0.019189,0}; +Point(175) = {0.842274,-0.020535,0}; +Point(174) = {0.830656,-0.021904,0}; +Point(173) = {0.818712,-0.023291,0}; +Point(172) = {0.806454,-0.024694,0}; +Point(171) = {0.793893,-0.026111,0}; +Point(170) = {0.781042,-0.027539,0}; +Point(169) = {0.767913,-0.028974,0}; +Point(168) = {0.754521,-0.030414,0}; +Point(167) = {0.740877,-0.031856,0}; +Point(166) = {0.726995,-0.033296,0}; +Point(165) = {0.712890,-0.034733,0}; +Point(164) = {0.698574,-0.036163,0}; +Point(163) = {0.684062,-0.037582,0}; +Point(162) = {0.669369,-0.038988,0}; +Point(161) = {0.654508,-0.040378,0}; +Point(160) = {0.639496,-0.041747,0}; +Point(159) = {0.624345,-0.043094,0}; +Point(158) = {0.609072,-0.044414,0}; +Point(157) = {0.593691,-0.045705,0}; +Point(156) = {0.578217,-0.046962,0}; +Point(155) = {0.562667,-0.048182,0}; +Point(154) = {0.547054,-0.049362,0}; +Point(153) = {0.531395,-0.050499,0}; +Point(152) = {0.515705,-0.051587,0}; +Point(151) = {0.500000,-0.052625,0}; +Point(150) = {0.484295,-0.053608,0}; +Point(149) = {0.468605,-0.054534,0}; +Point(148) = {0.452946,-0.055397,0}; +Point(147) = {0.437333,-0.056195,0}; +Point(146) = {0.421783,-0.056924,0}; +Point(145) = {0.406309,-0.057581,0}; +Point(144) = {0.390928,-0.058163,0}; +Point(143) = {0.375655,-0.058666,0}; +Point(142) = {0.360504,-0.059087,0}; +Point(141) = {0.345492,-0.059424,0}; +Point(140) = {0.330631,-0.059674,0}; +Point(139) = {0.315938,-0.059834,0}; +Point(138) = {0.301426,-0.059902,0}; +Point(137) = {0.287110,-0.059876,0}; +Point(136) = {0.273005,-0.059754,0}; +Point(135) = {0.259123,-0.059535,0}; +Point(134) = {0.245479,-0.059217,0}; +Point(133) = {0.232087,-0.058799,0}; +Point(132) = {0.218958,-0.058280,0}; +Point(131) = {0.206107,-0.057661,0}; +Point(130) = {0.193546,-0.056940,0}; +Point(129) = {0.181288,-0.056119,0}; +Point(128) = {0.169344,-0.055197,0}; +Point(127) = {0.157726,-0.054176,0}; +Point(126) = {0.146447,-0.053056,0}; +Point(125) = {0.135516,-0.051839,0}; +Point(124) = {0.124944,-0.050527,0}; +Point(123) = {0.114743,-0.049121,0}; +Point(122) = {0.104922,-0.047624,0}; +Point(121) = {0.095492,-0.046037,0}; +Point(120) = {0.086460,-0.044364,0}; +Point(119) = {0.077836,-0.042608,0}; +Point(118) = {0.069629,-0.040770,0}; +Point(117) = {0.061847,-0.038854,0}; +Point(116) = {0.054497,-0.036863,0}; +Point(115) = {0.047586,-0.034800,0}; +Point(114) = {0.041123,-0.032668,0}; +Point(113) = {0.035112,-0.030471,0}; +Point(112) = {0.029560,-0.028212,0}; +Point(111) = {0.024472,-0.025893,0}; +Point(110) = {0.019853,-0.023517,0}; +Point(109) = {0.015708,-0.021088,0}; +Point(108) = {0.012042,-0.018607,0}; +Point(107) = {0.008856,-0.016078,0}; +Point(106) = {0.006156,-0.013503,0}; +Point(105) = {0.003943,-0.010884,0}; +Point(104) = {0.002219,-0.008223,0}; +Point(103) = {0.000987,-0.005521,0}; +Point(102) = {0.000247,-0.002779,0}; +Point(101) = {0.000000,0.000000,0}; +Point(100) = {0.000247,0.002779,0}; +Point(99) = {0.000987,0.005521,0}; +Point(98) = {0.002219,0.008223,0}; +Point(97) = {0.003943,0.010884,0}; +Point(96) = {0.006156,0.013503,0}; +Point(95) = {0.008856,0.016078,0}; +Point(94) = {0.012042,0.018607,0}; +Point(93) = {0.015708,0.021088,0}; +Point(92) = {0.019853,0.023517,0}; +Point(91) = {0.024472,0.025893,0}; +Point(90) = {0.029560,0.028212,0}; +Point(89) = {0.035112,0.030471,0}; +Point(88) = {0.041123,0.032668,0}; +Point(87) = {0.047586,0.034800,0}; +Point(86) = {0.054497,0.036863,0}; +Point(85) = {0.061847,0.038854,0}; +Point(84) = {0.069629,0.040770,0}; +Point(83) = {0.077836,0.042608,0}; +Point(82) = {0.086460,0.044364,0}; +Point(81) = {0.095492,0.046037,0}; +Point(80) = {0.104922,0.047624,0}; +Point(79) = {0.114743,0.049121,0}; +Point(78) = {0.124944,0.050527,0}; +Point(77) = {0.135516,0.051839,0}; +Point(76) = {0.146447,0.053056,0}; +Point(75) = {0.157726,0.054176,0}; +Point(74) = {0.169344,0.055197,0}; +Point(73) = {0.181288,0.056119,0}; +Point(72) = {0.193546,0.056940,0}; +Point(71) = {0.206107,0.057661,0}; +Point(70) = {0.218958,0.058280,0}; +Point(69) = {0.232087,0.058799,0}; +Point(68) = {0.245479,0.059217,0}; +Point(67) = {0.259123,0.059535,0}; +Point(66) = {0.273005,0.059754,0}; +Point(65) = {0.287110,0.059876,0}; +Point(64) = {0.301426,0.059902,0}; +Point(63) = {0.315938,0.059834,0}; +Point(62) = {0.330631,0.059674,0}; +Point(61) = {0.345492,0.059424,0}; +Point(60) = {0.360504,0.059087,0}; +Point(59) = {0.375655,0.058666,0}; +Point(58) = {0.390928,0.058163,0}; +Point(57) = {0.406309,0.057581,0}; +Point(56) = {0.421783,0.056924,0}; +Point(55) = {0.437333,0.056195,0}; +Point(54) = {0.452946,0.055397,0}; +Point(53) = {0.468605,0.054534,0}; +Point(52) = {0.484295,0.053608,0}; +Point(51) = {0.500000,0.052625,0}; +Point(50) = {0.515705,0.051587,0}; +Point(49) = {0.531395,0.050499,0}; +Point(48) = {0.547054,0.049362,0}; +Point(47) = {0.562667,0.048182,0}; +Point(46) = {0.578217,0.046962,0}; +Point(45) = {0.593691,0.045705,0}; +Point(44) = {0.609072,0.044414,0}; +Point(43) = {0.624345,0.043094,0}; +Point(42) = {0.639496,0.041747,0}; +Point(41) = {0.654508,0.040378,0}; +Point(40) = {0.669369,0.038988,0}; +Point(39) = {0.684062,0.037582,0}; +Point(38) = {0.698574,0.036163,0}; +Point(37) = {0.712890,0.034733,0}; +Point(36) = {0.726995,0.033296,0}; +Point(35) = {0.740877,0.031856,0}; +Point(34) = {0.754521,0.030414,0}; +Point(33) = {0.767913,0.028974,0}; +Point(32) = {0.781042,0.027539,0}; +Point(31) = {0.793893,0.026111,0}; +Point(30) = {0.806454,0.024694,0}; +Point(29) = {0.818712,0.023291,0}; +Point(28) = {0.830656,0.021904,0}; +Point(27) = {0.842274,0.020535,0}; +Point(26) = {0.853553,0.019189,0}; +Point(25) = {0.864484,0.017868,0}; +Point(24) = {0.875056,0.016574,0}; +Point(23) = {0.885257,0.015310,0}; +Point(22) = {0.895078,0.014079,0}; +Point(21) = {0.904508,0.012883,0}; +Point(20) = {0.913540,0.011726,0}; +Point(19) = {0.922164,0.010610,0}; +Point(18) = {0.930371,0.009537,0}; +Point(17) = {0.938153,0.008510,0}; +Point(16) = {0.945503,0.007531,0}; +Point(15) = {0.952414,0.006603,0}; +Point(14) = {0.958877,0.005729,0}; +Point(13) = {0.964888,0.004909,0}; +Point(12) = {0.970440,0.004147,0}; +Point(11) = {0.975528,0.003443,0}; +Point(10) = {0.980147,0.002801,0}; +Point(9) = {0.984292,0.002222,0}; +Point(8) = {0.987958,0.001707,0}; +Point(7) = {0.991144,0.001258,0}; +Point(6) = {0.993844,0.000876,0}; +Point(5) = {0.996057,0.000562,0}; +Point(4) = {0.997781,0.000317,0}; +Point(3) = {0.999013,0.000141,0}; +Point(2) = {0.999753,0.000035,0}; +Point(1) = {1.000000,0.000000,0}; + +Spline(1) = {80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}; +Spline(2) = {101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80}; +Spline(3) = {122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101}; +Spline(4) = {1,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122}; + +// Farfield +Point(10001) = {1+xLgt, 0, 0}; +Point(10002) = {1+xLgt, yLgt, 0}; +Point(10003) = {1, yLgt, 0}; +Point(10004) = {-xLgt, yLgt, 0}; +Point(10005) = {-xLgt, 0, 0}; +Point(10006) = {-xLgt,-yLgt, 0}; +Point(10007) = {1, -yLgt, 0}; +Point(10008) = {1+xLgt, -yLgt, 0}; + +Line(10001) = {10001, 10002}; +Line(10002) = {10002, 10003}; +Line(10003) = {10003, 10004}; +Line(10004) = {10004, 10005}; +Line(10005) = {10005, 10006}; +Line(10006) = {10006, 10007}; +Line(10007) = {10007, 10008}; +Line(10008) = {10008, 10001}; + +// Perpendicular lines +Line(10011) = {1, 10001}; +Line(10012) = {1, 10003}; +Line(10013) = {80, 10004}; +Line(10014) = {101, 10005}; +Line(10015) = {122, 10006}; +Line(10016) = {1, 10007}; + +// Internal field +Line Loop(10001) = {10011, 10001, 10002, -10012}; +Line Loop(10002) = {1, 10012, 10003, -10013}; +Line Loop(10003) = {2, 10013, 10004, -10014}; +Line Loop(10004) = {3, 10014, 10005, -10015}; +Line Loop(10005) = {4, 10015, 10006, -10016}; +Line Loop(10006) = {-10011, 10016, 10007, 10008}; + +Plane Surface(10001) = {10001}; +Plane Surface(10002) = {10002}; +Plane Surface(10003) = {10003}; +Plane Surface(10004) = {10004}; +Plane Surface(10005) = {10005}; +Plane Surface(10006) = {10006}; + +/************************* + Meshing + *************************/ + +Transfinite Line {1, 4, 10003, 10006} = nChord Using Bump pChord; +Transfinite Line {2, 10004} = nLe Using Progression 1/pLe; +Transfinite Line {3, 10005} = nLe Using Progression pLe; +Transfinite Line {10011, 10012, 10013, 10014, 10015, 10016, 10001, 10007} = nPerp Using Progression pPerp; +Transfinite Line {10002, 10008} = nPerp Using Progression 1/pPerp; + +Transfinite Surface {10001} = {1, 10001, 10002, 10003}; +Transfinite Surface {10002} = {1, 10003, 10004, 80}; +Transfinite Surface {10003} = {80, 10004, 10005, 101}; +Transfinite Surface {10004} = {122, 10006, 10005, 101}; +Transfinite Surface {10005} = {1, 10007, 10006, 122}; +Transfinite Surface {10006} = {1, 10007, 10008, 10001}; +//Recombine Surface {10001, 10002, 10003, 10004, 10005, 10006}; + +/************************* + Physical Groups + *************************/ + +Physical Point("te") = {1}; +Physical Line("upstream") = {10004, 10005}; +Physical Line("side") = {10002, 10003, 10006, 10007}; +Physical Line("downstream") = {10001, 10008}; +Physical Line("airfoil") = {1, 2}; +Physical Line("airfoil_") = {3, 4}; +Physical Line("wake") = {10011}; +Physical Surface("field") = {10001, 10002, 10003, 10004, 10005, 10006}; + diff --git a/flow/models/rae2822.db b/flow/models/rae2822.db new file mode 100644 index 0000000000000000000000000000000000000000..a340d2d615e7884045fd00448803c3550a5e15bd GIT binary patch literal 3751 zcmcJSU2mH(6o!4BU*XG5(;6@#kldwBTTSYhw(bsTa==MY0V8ahWzzopjv+}QB&HOt zm6`(KJ;#sFIlMeRT;I(uaX=+qP{FXDF)CSr7hB8|7SRP3EPG`c9N`~S;#t1Jri~4E zO0212VPX(tFm|zdVWA|C7c4`@J(D!%0gHxroTmsO%S6WRSN-?ZWM7D3pxuW$QCuCE z*0?G(3$rB;BYby_V<y&EgnwA6roK<mZAQ~IGT!ln%0x_b8w<Dt!TlG8QT{b-I3VST z$;56;=J`J*qq2aNoJqzpt6=6lreT63maOC&fBOA32(!;`WYqc&3$@{Zfa+mgTB&U( zsj*Yd>V{^^kVV0%&8nrOcCVU|Z5J{^W?`}_G9S)-8S(^{^j0^A6`>VNSQh$lZD5*4 z<KBB!dYCQ*k8;UyO67Vdqyksa%%|Qgrg^sY=G@O?mPp|}-&}he`pSg2jQGYY%2;^S zkZ!HmRaV0?Vi1CaDAO(@ndmCwf&~z?5|(Hhz?hs_PC{c<`wGX0SCLUhTtY5ZjH6{3 zu>v4D5mBsf;PQhCd9NC;3=UmfhCa2Z_p{t*p*nG&#iYsfp%^BZFLAowiqNMKUS&K_ z1yIn@b<fkCl(n)OQ^n3b)wHoWoHsVHRx+C5<&ehKHPj2F`}}LdlGSRg)>x-AEdd>9 z)YQyA5J(#Yjk;5AWVMx#AdumC2(Y^no<DIn6rfl>cus=VbX$8pSbb28+Umj5pb%lu zu_8cCgTd-k5u+X$+~$Bjs)&&WL%=u5M`{8zH84hfVAwriIAvdJ+QZhsprs$`)*T?I zAu;YlV%&#B>!297llavQ1t@AzIDJq!eNeOx3P*!tLti=$3Q*Hv==J<`+c0RRvD0*c zM}xxEU?`ktw{ieE4Ts73av)~2c>jJ&(p+L?Xp}=K-guN>n&pyTt(pJwQR}(4O9#_N z(ACuY32$X$=Uy+pS(?t_1<w1<X$%&6xB_pL`KY@mQ$Yq!ZDKzbFNnv6+(b{1Kqw@b g=A9k$c?<+<wRlqLGMBQj_SVkL)|4tfxw@PG00(ELf&c&j literal 0 HcmV?d00001 diff --git a/flow/tests/bliLiftComp.py b/flow/tests/bliLiftComp.py index b924e101..20d8e2e3 100644 --- a/flow/tests/bliLiftComp.py +++ b/flow/tests/bliLiftComp.py @@ -19,7 +19,14 @@ # @brief Compute lifting (linear or nonlinear) viscous flow around a naca0012 # @authors Adrien Crovato, Amaury Bilocq # Test the viscous-inviscid interaction scheme -# +# Reference to the master's thesis: http://hdl.handle.net/2268/252195 +# Reference test cases with Naca0012: +# 1) Incompressible: Re = 1e7, M_inf = 0, alpha = 5°, p = 2, m = 7, tol = 10^-4, msTE = 0.01, msLE = 0.001 +# Results: Cl = 0.58, Cd = 0.0062, xtrTop = 0.0555, xtrBot = 0.7397 +# 2) Compressible: Re = 1e7, M_inf = 5, alpha = 5°, p = 2, m = 7, tol = 10^-4, msTE = 0.01, msLE = 0.001 +# Results: Cl = 0.69, Cd = 0.0067, xtrTop = 0.0384, xtrBot = 0.7364 +# 3) Separated: Re = 1e7, M_inf = 0, alpha = 12°, p = 2, m = 11, tol = 5.15*10^-3, msTE = 0.01, msLE = 0.00075 +# Results: Cl = 1.39 , Cd = 0.011, xtrTop = 0.008, xtrBot = 1 # CAUTION # This test is provided to ensure that the solver works properly. # Mesh refinement may have to be performed to obtain physical results. @@ -29,8 +36,8 @@ import math import flow as flo import flow.utils as floU import flow.default as floD -import flow.viscous.solver as floVS -import flow.viscous.coupler as floVC +import flow.viscous.Solver as floVS +import flow.viscous.Coupler as floVC import tbox import tbox.utils as tboxU import tbox.gmsh as gmsh @@ -45,13 +52,18 @@ def main(): # define flow variables Re = 10*10**6 - alpha = 2*math.pi/180 + alpha = 5*math.pi/180 U_inf = [math.cos(alpha), math.sin(alpha)] # norm should be 1 - M_inf = 0.6 + M_inf = 0.5 M_crit = 2 # Squared critical Mach number (above which density is modified) c_ref = 1 dim = len(U_inf) + # define filter parameters and tolerance of the VII + p = 2 + m = 7 + tol = 10**-4 + # mesh the geometry print(ccolors.ANSI_BLUE + 'PyMeshing...' + ccolors.ANSI_RESET) tms['msh'].start() @@ -93,8 +105,8 @@ def main(): tms['solver'].start() isolver = flo.Newton(pbl) floD.initNewton(isolver) - vsolver = floVS.Solver(Re) - coupler = floVC.Coupler(isolver, vsolver, gmshWriter,blw,blw2) + vsolver = floVS.Solver(Re, p, m) + coupler = floVC.Coupler(isolver, vsolver, gmshWriter,blw,blw2, tol) coupler.run() tms['solver'].stop() @@ -119,12 +131,11 @@ def main(): # check results print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) tests = CTests() - tests.add(CTest('min(Cp)', min(Cp[:,3]), -1.0558, 1e-1)) # TODO check value and tolerance - tests.add(CTest('Cl', isolver.Cl, 0.292, 5e-2)) - tests.add(CTest('Cdp', vsolver.Cdp, 0.0013, 0.01)) - tests.add(CTest('Cd', vsolver.Cd, 0.0057, 0.01)) - tests.add(CTest('top_xtr', vsolver.top_xtr, 0.16, 0.5 )) - tests.add(CTest('bot_xtr', vsolver.bot_xtr, 0.48, 0.5 )) + tests.add(CTest('Cl', isolver.Cl, 0.68, 5e-2)) + tests.add(CTest('Cd', vsolver.Cd, 0.0067, 0.01)) + tests.add(CTest('Cdp', vsolver.Cdp, 0.0025, 0.01)) + tests.add(CTest('top_xtr', vsolver.xtr[0], 0.0374, 0.05 )) + tests.add(CTest('bot_xtr', vsolver.xtr[1], 0.7391, 0.05 )) tests.run() # eof diff --git a/flow/tests/bliLiftIncomp.py b/flow/tests/bliLiftIncomp.py deleted file mode 100644 index 635cc62a..00000000 --- a/flow/tests/bliLiftIncomp.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2020 University of Liège -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# @brief Compute lifting (linear or nonlinear) viscous flow around a naca0012 -# @authors Adrien Crovato, Amaury Bilocq -# Test the viscous-inviscid interaction scheme -# -# CAUTION -# This test is provided to ensure that the solver works properly. -# Mesh refinement may have to be performed to obtain physical results. - -from __future__ import print_function -import math -import flow as flo -import flow.utils as floU -import flow.default as floD -import flow.viscous.solver as floVS -import flow.viscous.coupler as floVC -import tbox -import tbox.utils as tboxU -import tbox.gmsh as gmsh -import fwk -from fwk.testing import * -from fwk.coloring import ccolors - -def main(): - # timer - tms = fwk.Timers() - tms['total'].start() - - # define flow variables - Re = 10*10**6 - alpha = 5*math.pi/180 - U_inf = [math.cos(alpha), math.sin(alpha)] # norm should be 1 - M_inf = 0.0 - M_crit = 5 # Squared critical Mach number (above which density is modified) - c_ref = 1 - dim = len(U_inf) - - # mesh the geometry - print(ccolors.ANSI_BLUE + 'PyMeshing...' + ccolors.ANSI_RESET) - tms['msh'].start() - pars = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1, 'msTe' : 0.01, 'msLe' : 0.001} - msh = gmsh.MeshLoader("../models/n0012.geo",__file__).execute(**pars) - # crack the mesh - mshCrck = tbox.MshCrack(msh, dim) - mshCrck.setCrack('wake') - mshCrck.addBoundaries(['field', 'airfoil', 'downstream']) - mshCrck.run() - del mshCrck - gmshWriter = tbox.GmshExport(msh) - gmshWriter.save(msh.name) - tms['msh'].stop() - - # set the problem and add medium, initial/boundary conditions - tms['pre'].start() - pbl = flo.Problem(msh, dim, alpha, M_inf, c_ref, c_ref, 0.25, 0., 0.) - if M_inf == 0: - pbl.set(flo.Medium(msh, 'field', flo.F0ElRhoL(), flo.F0ElMachL(), flo.F0ElCpL(), flo.F0PsPhiInf(dim, alpha))) - else: - pbl.set(flo.Medium(msh, 'field', flo.F0ElRho(M_inf, M_crit), flo.F0ElMach(M_inf), flo.F0ElCp(M_inf), flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Initial(msh, 'field', flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Dirichlet(msh, 'upstream', flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Freestream(msh, 'side', tbox.Fct1C(-U_inf[0], -U_inf[1], 0.))) - pbl.add(flo.Freestream(msh, 'downstream', tbox.Fct1C(-U_inf[0], -U_inf[1], 0.))) - pbl.add(flo.Wake(msh, ['wake', 'wake_', 'field'])) - pbl.add(flo.Kutta(msh, ['te', 'wake_', 'airfoil', 'field'])) - bnd = flo.Boundary(msh, ['airfoil', 'field']) - pbl.add(bnd) - blw = flo.Blowing(msh, ['airfoil', 'field']) - blw2 = flo.Blowing(msh, ['wake', 'field']) - pbl.add(blw) - pbl.add(blw2) - tms['pre'].stop() - - # solve inviscid problem - print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET) - tms['solver'].start() - isolver = flo.Newton(pbl) - floD.initNewton(isolver) - vsolver = floVS.Solver(Re) - coupler = floVC.Coupler(isolver, vsolver, gmshWriter,blw,blw2) - coupler.run() - tms['solver'].stop() - - # extract Cp - Cp = floU.extract(bnd.groups[0].tag.elems, isolver.Cp) - tboxU.write(Cp, 'Cp_airfoil.dat', '%1.5e', ', ', 'x, y, z, Cp', '') - # display results - print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET) - print(' M alpha Cl Cd Cm') - print('{0:8.2f} {1:8.1f} {2:8.4f} {3:8.4f} {4:8.4f}'.format(M_inf, alpha*180/math.pi, isolver.Cl, vsolver.Cd, isolver.Cm)) - - # display timers - tms['total'].stop() - print(ccolors.ANSI_BLUE + 'PyTiming...' + ccolors.ANSI_RESET) - print('CPU statistics') - print(tms) - - # visualize solution and plot results - floD.initViewer(pbl) - tboxU.plot(Cp[:,0], Cp[:,3], 'x', 'Cp', 'Cl = {0:.{3}f}, Cd = {1:.{3}f}, Cm = {2:.{3}f}'.format(isolver.Cl, vsolver.Cd, isolver.Cm, 4), True) - - # check results - print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) - tests = CTests() - tests.add(CTest('min(Cp)', min(Cp[:,3]), -1.9531, 1e-1)) # TODO check value and tolerance - tests.add(CTest('Cl', isolver.Cl, 0.5658, 5e-2)) - tests.add(CTest('Cdp', vsolver.Cdp, 0.0017, 0.01)) - tests.add(CTest('Cd', vsolver.Cd, 0.0061, 0.01)) - tests.add(CTest('top_xtr', vsolver.top_xtr, 0.0531, 0.5 )) - tests.add(CTest('bot_xtr', vsolver.bot_xtr, 0.7484, 0.5 )) - tests.run() - - # eof - print('') - -if __name__ == "__main__": - main() diff --git a/flow/tests/bliNonLift.py b/flow/tests/bliNonLift.py deleted file mode 100644 index d1f4ef78..00000000 --- a/flow/tests/bliNonLift.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2020 University of Liège -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# @brief Compute lifting (linear or nonlinear) viscous flow around a naca0012 -# @authors Adrien Crovato, Amaury Bilocq -# Test the viscous-inviscid interaction scheme -# -# CAUTION -# This test is provided to ensure that the solver works properly. -# Mesh refinement may have to be performed to obtain physical results. - -from __future__ import print_function -import math -import flow as flo -import flow.utils as floU -import flow.default as floD -import flow.viscous.solver as floVS -import flow.viscous.coupler as floVC -import tbox -import tbox.utils as tboxU -import tbox.gmsh as gmsh -import fwk -from fwk.testing import * -from fwk.coloring import ccolors - -def main(): - # timer - tms = fwk.Timers() - tms['total'].start() - - # define flow variables - Re = 5*10**6 - alpha = 0*math.pi/180 - U_inf = [math.cos(alpha), math.sin(alpha)] # norm should be 1 - M_inf = 0.0 - M_crit = 5 # Squared critical Mach number (above which density is modified) - c_ref = 1 - dim = len(U_inf) - - # mesh the geometry - print(ccolors.ANSI_BLUE + 'PyMeshing...' + ccolors.ANSI_RESET) - tms['msh'].start() - pars = {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1, 'msTe' : 0.01, 'msLe' : 0.001} - msh = gmsh.MeshLoader("../models/n0012.geo",__file__).execute(**pars) - # crack the mesh - mshCrck = tbox.MshCrack(msh, dim) - mshCrck.setCrack('wake') - mshCrck.addBoundaries(['field', 'airfoil', 'downstream']) - mshCrck.run() - del mshCrck - gmshWriter = tbox.GmshExport(msh) - gmshWriter.save(msh.name) - tms['msh'].stop() - - # set the problem and add medium, initial/boundary conditions - tms['pre'].start() - pbl = flo.Problem(msh, dim, alpha, M_inf, c_ref, c_ref, 0.25, 0., 0.) - if M_inf == 0: - pbl.set(flo.Medium(msh, 'field', flo.F0ElRhoL(), flo.F0ElMachL(), flo.F0ElCpL(), flo.F0PsPhiInf(dim, alpha))) - else: - pbl.set(flo.Medium(msh, 'field', flo.F0ElRho(M_inf, M_crit), flo.F0ElMach(M_inf), flo.F0ElCp(M_inf), flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Initial(msh, 'field', flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Dirichlet(msh, 'upstream', flo.F0PsPhiInf(dim, alpha))) - pbl.add(flo.Freestream(msh, 'side', tbox.Fct1C(-U_inf[0], -U_inf[1], 0.))) - pbl.add(flo.Freestream(msh, 'downstream', tbox.Fct1C(-U_inf[0], -U_inf[1], 0.))) - pbl.add(flo.Wake(msh, ['wake', 'wake_', 'field'])) - pbl.add(flo.Kutta(msh, ['te', 'wake_', 'airfoil', 'field'])) - bnd = flo.Boundary(msh, ['airfoil', 'field']) - pbl.add(bnd) - blw = flo.Blowing(msh, ['airfoil', 'field']) - blw2 = flo.Blowing(msh, ['wake', 'field']) - pbl.add(blw) - pbl.add(blw2) - tms['pre'].stop() - - # solve inviscid problem - print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET) - tms['solver'].start() - isolver = flo.Newton(pbl) - floD.initNewton(isolver) - vsolver = floVS.Solver(Re) - coupler = floVC.Coupler(isolver, vsolver, gmshWriter,blw,blw2) - coupler.run() - tms['solver'].stop() - - # extract Cp - Cp = floU.extract(bnd.groups[0].tag.elems, isolver.Cp) - tboxU.write(Cp, 'Cp_airfoil.dat', '%1.5e', ', ', 'x, y, z, Cp', '') - # display results - print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET) - print(' M alpha Cl Cd Cm') - print('{0:8.2f} {1:8.1f} {2:8.4f} {3:8.4f} {4:8.4f}'.format(M_inf, alpha*180/math.pi, isolver.Cl, vsolver.Cd, isolver.Cm)) - - # display timers - tms['total'].stop() - print(ccolors.ANSI_BLUE + 'PyTiming...' + ccolors.ANSI_RESET) - print('CPU statistics') - print(tms) - - # visualize solution and plot results - floD.initViewer(pbl) - tboxU.plot(Cp[:,0], Cp[:,3], 'x', 'Cp', 'Cl = {0:.{3}f}, Cd = {1:.{3}f}, Cm = {2:.{3}f}'.format(isolver.Cl, vsolver.Cd, isolver.Cm, 4), True) - - # check results - print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET) - tests = CTests() - tests.add(CTest('min(Cp)', min(Cp[:,3]), -0.4132, 1e-1)) # TODO check value and tolerance - tests.add(CTest('Cl', isolver.Cl, 0.0, 5e-2)) - tests.add(CTest('Cdp', vsolver.Cdp, 0.00067, 0.01)) - tests.add(CTest('Cd', vsolver.Cd, 0.0051, 0.01)) - tests.add(CTest('top_xtr', vsolver.top_xtr, 0.4381, 0.5 )) - tests.add(CTest('bot_xtr', vsolver.bot_xtr, 0.4368, 0.5 )) - tests.run() - - # eof - print('') - -if __name__ == "__main__": - main() diff --git a/flow/viscous/Airfoil.py b/flow/viscous/Airfoil.py index 3c954dab..bb6d11f7 100644 --- a/flow/viscous/Airfoil.py +++ b/flow/viscous/Airfoil.py @@ -43,7 +43,7 @@ class Airfoil(Boundary): self.H0 = 2.23 # One parameter family Falkner Skan # self.H0 = 2.23 self.n0 = 0 - self.Ct0 = 0.03 + self.Ct0 = 0 return self.T0, self.H0, self.n0, self.Ct0 def connectList(self): @@ -52,7 +52,7 @@ class Airfoil(Boundary): N1 = np.zeros(self.nN, dtype=int) # Node number connectListNodes = np.zeros(self.nN, dtype=int) # Index in boundary.nodes connectListElems = np.zeros(self.nE, dtype=int) # Index in boundary.elems - data = np.zeros((self.boundary.nodes.size(), 9)) + data = np.zeros((self.boundary.nodes.size(), 10)) i = 0 for n in self.boundary.nodes: data[i,0] = n.no @@ -63,6 +63,8 @@ class Airfoil(Boundary): data[i,5] = self.v[i,1] data[i,6] = self.Me[i] data[i,7] = self.rhoe[i] + data[i,8] = self.deltaStar[i] + data[i,9] = self.xx[i] i += 1 # Table containing the element and its nodes eData = np.zeros((self.nE,3), dtype=int) @@ -116,10 +118,11 @@ class Airfoil(Boundary): data[:,5] = data[connectListNodes,5] data[:,6] = data[connectListNodes,6] data[:,7] = data[connectListNodes,7] - data[:,8] = self.deltaStar + data[:,8] = data[connectListNodes,8] + data[:,9] = data[connectListNodes,9] # Separated upper and lower part data = np.delete(data,0,1) uData = data[0:np.argmax(data[:,0])+1] lData = data[np.argmax(data[:,0])+1:None] - lData = np.insert(lData, 0, uData[0,:], axis = 0) - return connectListElems,[uData, lData] \ No newline at end of file + lData = np.insert(lData, 0, uData[0,:], axis = 0) #double the stagnation point + return connectListNodes, connectListElems,[uData, lData] \ No newline at end of file diff --git a/flow/viscous/Boundary.py b/flow/viscous/Boundary.py index cd9a1c20..9526a829 100644 --- a/flow/viscous/Boundary.py +++ b/flow/viscous/Boundary.py @@ -31,7 +31,7 @@ class Boundary(object): self.u = np.zeros(self.nE) # blowing velocity self.Me = np.zeros(self.nN) # Mach number at the edge of the boundary layer self.rhoe = np.zeros(self.nN) # Density at the edge of the boundary layer - self.connectListElems = np.zeros(self.nE) # Connectivity for the blowing velocity - self.Cd = 0 # drag coefficient of the physical boundary - self.Cdp = 0 # Form drag - self.deltaStar = np.zeros(self.nN) # Displacement thickness \ No newline at end of file + self.connectListnodes = np.zeros(self.nN) # Connectivity for nodes + self.connectListElems = np.zeros(self.nE) # Connectivity for elements + self.deltaStar = np.zeros(self.nN) # Displacement thickness + self.xx = np.zeros(self.nN) # coordinates in the reference of the physical surface. Used to store the values for the interaction law \ No newline at end of file diff --git a/flow/viscous/Wake.py b/flow/viscous/Wake.py index 667230bd..bae06e53 100644 --- a/flow/viscous/Wake.py +++ b/flow/viscous/Wake.py @@ -29,7 +29,7 @@ class Wake(Boundary): self.name = "wake" self.T0 = 0 # initial condition for the momentum thickness self.H0 = 0 - self.n0 = 0 # should not look at transition inside the wake + self.n0 = 9 # wake is always turbulent self.Ct0 = 0 @@ -39,7 +39,7 @@ class Wake(Boundary): N1 = np.zeros(self.nN, dtype=int) # Node number connectListNodes = np.zeros(self.nN, dtype=int) # Index in boundary.nodes connectListElems = np.zeros(self.nE, dtype=int) # Index in boundary.elems - data = np.zeros((self.boundary.nodes.size(), 9)) + data = np.zeros((self.boundary.nodes.size(), 10)) i = 0 for n in self.boundary.nodes: data[i,0] = n.no @@ -50,6 +50,8 @@ class Wake(Boundary): data[i,5] = self.v[i,1] data[i,6] = self.Me[i] data[i,7] = self.rhoe[i] + data[i,8] = self.deltaStar[i] + data[i,9] = self.xx[i] i += 1 # Table containing the element and its nodes eData = np.zeros((self.nE,3), dtype=int) @@ -69,7 +71,8 @@ class Wake(Boundary): data[:,5] = data[connectListNodes,5] data[:,6] = data[connectListNodes,6] data[:,7] = data[connectListNodes,7] - data[:,8] = self.deltaStar + data[:,8] = data[connectListNodes,8] + data[:,9] = data[connectListNodes,9] # Separated upper and lower part data = np.delete(data,0,1) - return connectListElems, data + return connectListNodes, connectListElems, data diff --git a/flow/viscous/coupler.py b/flow/viscous/coupler.py index 723f509c..c2aabe26 100644 --- a/flow/viscous/coupler.py +++ b/flow/viscous/coupler.py @@ -28,7 +28,7 @@ from flow.viscous.Airfoil import Airfoil from flow.viscous.Wake import Wake class Coupler(object): - def __init__(self, _isolver, _vsolver, _writer, _boundaryAirfoil, _boundaryWake): + def __init__(self, _isolver, _vsolver, _writer, _boundaryAirfoil, _boundaryWake, _tol): '''... ''' self.isolver =_isolver # inviscid solver @@ -37,23 +37,20 @@ class Coupler(object): self.airfoil = Airfoil(_boundaryAirfoil) # create the object airfoil self.wake = Wake(_boundaryWake) # create the object wake self.group = [self.airfoil, self.wake] + self.tol = _tol # tolerance of the VII def run(self): ''' Perform coupling ''' # initialize loop it = 0 - alpha = 1# underrelaxation factor converged = 0 # temp - tol = 10**-6 CdOld = self.vsolver.Cd - # while it < 15: while converged == 0: print(ccolors.ANSI_BLUE + 'Iteration: ', it, ccolors.ANSI_RESET) # run inviscid solver self.isolver.run() for n in range(0, len(self.group)): - uOld = self.group[n].u for i in range(0, len(self.group[n].boundary.nodes)): self.group[n].v[i,0] = self.isolver.U[self.group[n].boundary.nodes[i].row].x[0] self.group[n].v[i,1] = self.isolver.U[self.group[n].boundary.nodes[i].row].x[1] @@ -62,26 +59,27 @@ class Coupler(object): self.group[n].rhoe[i] = self.isolver.rho[self.group[n].boundary.nodes[i].row] # run viscous solver self.vsolver.run(self.group[n]) - self.group[n].u = self.group[n].u[self.group[n].connectListElems] - self.group[n].u = uOld + alpha*(self.group[n].u - uOld) # underrelaxation if self.group[n].name == "airfoil": self.group[n+1].T0 = self.group[n].TEnd[0]+self.group[n].TEnd[1] self.group[n+1].H0 = (self.group[n].HEnd[0]*self.group[n].TEnd[0]+self.group[n].HEnd[1]*self.group[n].TEnd[1])/self.group[n+1].T0 self.group[n+1].Ct0 = (self.group[n].CtEnd[0]*self.group[n].TEnd[0]+self.group[n].CtEnd[1]*self.group[n].TEnd[1])/self.group[n+1].T0 - self.group[n+1].n0 = self.group[n].nEnd[0] # get blowing velocity from viscous solver and update inviscid solver for i in range(0, self.group[n].nE): self.group[n].boundary.setU(i, self.group[n].u[i]) print('Computation is done for: ' ,self.group[n].name) print('Number of iterations:' ,+it) print('Cd = ' ,+self.vsolver.Cd) - print('Top_xtr = ' ,+self.vsolver.top_xtr) - print('Bot_xtr = ' ,+self.vsolver.bot_xtr) - if abs(self.vsolver.Cd - CdOld) < tol: + print('Top_xtr = ' ,+self.vsolver.xtr[0]) + print('Bot_xtr = ' ,+self.vsolver.xtr[1]) + print('Error = ' ,+abs(self.vsolver.Cd - CdOld)/self.vsolver.Cd) + # Converged or not + if abs(self.vsolver.Cd - CdOld)/self.vsolver.Cd < self.tol: converged = 1 else: CdOld = self.vsolver.Cd - it += 1 + it += 1 + self.vsolver.it += 1 # save results self.isolver.save(0, self.writer) + self.vsolver.writeFile() print('\n') diff --git a/flow/viscous/newton.py b/flow/viscous/newton.py index 262818ea..e6130db6 100644 --- a/flow/viscous/newton.py +++ b/flow/viscous/newton.py @@ -27,43 +27,45 @@ import math def newtonRaphson(f, x, maxIt, tol=1.0e-9): - ''' Newton procedure to solve the coupled, non linear system. Boundary layer equation are parabolic in x --> Use of downstream marching type of solver - As an implicit marching model is applied (Crank Nicolson), a matrix inversion is performed''' - # Trial to develop Jacobian - def jacobian(f, x): - dx = 1.0e-8 - n = len(x) - jac = np.zeros((n,n)) - f0 = f(x) - for j in range(n): - Dxj = (abs(x[j])*dx if x[j] != 0 else dx) - x_plus = [(xi if k != j else xi+Dxj) for k, xi in enumerate(x)] - jac[:,j] = (f(x_plus)-f0)/Dxj # numerical solution of the jacobian - return jac, f0 + ''' Newton procedure to solve the coupled, non linear system. Boundary layer equation are parabolic in x --> Use of downstream marching type of solver + As an implicit marching model is applied (Crank Nicolson), a matrix inversion is performed''' + # Trial to develop Jacobian + def jacobian(f, x): + dx = 1.0e-8 + n = len(x) + jac = np.zeros((n,n)) + f0 = f(x) + for j in range(n): + Dxj = (abs(x[j])*dx if x[j] != 0 else dx) + x_plus = [(xi if k != j else xi+Dxj) for k, xi in enumerate(x)] + jac[:,j] = (f(x_plus)-f0)/Dxj # numerical solution of the jacobian + return jac, f0 # Backtrack linesearch - def BacktrackingLineSearch(f, x, dx, maxLsIt): - a = 1 - f0 = f(x+dx) - fevalIt = 1 - while fevalIt < maxLsIt: - fa = f(x+a*dx) - fevalIt += 1 - print('fa = ', + fa ,'f0 = ', +f0) - if abs(fa[1]) > abs(f0[1]): # compare H - a = a/2 - print('a = ' ,+a) - else: - break - return a + def BacktrackingLineSearch(f, x, dx, maxLsIt): + a = 0.5 + f0 = f(x+dx) + fevalIt = 1 + while fevalIt < maxLsIt: + fa = f(x+a*dx) + fevalIt += 1 + # print('fa = ', + fa ,'f0 = ', +f0) + if abs(any(fa)) > abs(any(f0)): # compare H + a = a/2 + print('a = ' ,+a) + else: + break + return a - for i in range(maxIt): - jac, f0 = jacobian(f,x) - if np.sqrt(np.dot(f0,f0)/len(x)) < tol and x[0]>0 and x[1]>0: return x - dx = np.linalg.solve(jac, -f0) - x = x + dx + for i in range(maxIt): + jac, f0 = jacobian(f,x) + if np.sqrt(np.dot(f0,f0)/len(x)) < tol and x[0]>0 and x[1]>0: return x + dx = np.linalg.solve(jac, -f0) + # a = BacktrackingLineSearch(f, x, dx, maxIt) + x = x + dx + x[1] = max(x[1],1.0005) # to avoid too low shape parameter. Solver must be redone to be more robust # print('New x =',+ x) - if np.sqrt(np.dot(dx, dx)) < tol*max(abs(any(x)),1) and x[0]>0 and x[1]>0: return x - print("Too many iterations") + if np.sqrt(np.dot(dx, dx)) < tol*max(abs(any(x)),1) and x[0]>0 and x[1]>0: return x + print("Too many iterations") diff --git a/flow/viscous/solver.py b/flow/viscous/solver.py index e74482bf..a60fabac 100644 --- a/flow/viscous/solver.py +++ b/flow/viscous/solver.py @@ -24,7 +24,7 @@ from builtins import range from builtins import object from flow.viscous.Boundary import Boundary from flow.viscous.Wake import Wake -import flow.viscous.newton as newton +import flow.viscous.Newton as Newton import numpy as np import math import matplotlib.pyplot as plt @@ -33,328 +33,440 @@ from scipy.io import loadmat from scipy.signal import savgol_filter class Solver(object): - def __init__(self, _Re): - '''... + '''To do: + - Improve the filter in boundaryLayer (l 263); + - Inverse procedure at the singularity location ( such as in Xfoil); + - Create a new mesh for the viscous solver; + - Improve the transition solver by refining the grid to split the transitional element into a laminar element and a turbulent element. + Here it is an average between laminar and turbulent solution (l 397-416); + - Implement quasi 2D method for 3D capability; + - User can define the transition location (l 303); + - Write the empirical relation between the amplification factor and the turbulence intensity (l 303); + - Verify why the computation crash if the first point of the wake is computed with the turbulent closure (l 309). + ''' + + def __init__(self, _Re, _p, _m): + '''Initialize the viscous solver ''' self.Re = _Re # Reynolds number - self.gamma = 1.4 - self.Cd = 0 + self.p = _p # Filtering order + self.m = _m # Filtering bandwith + self.gamma = 1.4 # heat capacity of air + self.Cd = 0 # drag coefficient + self.it = 0 # viscous inviscid iteration for interaction law + self.xtr = [1, 1] # set transition at TE reference coordinates - def run(self, group): - ''' Solve BLE - ''' - def writeFile(data, H, theta, Cf, vt, Cdissip, Ct, Dstar, blwUe): - # save_path = 'D:/Amaury/Documents/TFE/xfoil/' - name = 'BLparameters' - name2 = 'blwU' - # complete_name = os.path.join(save_path, name+".dat") - # complete_name2 = os.path.join(save_path, name2+".dat") - f = open(name, 'w+') - for i in range(len(H)): - f.write("%s %s %s %s %s %s %s %s\n" % (data[i,0], H[i], theta[i], Cf[i], vt[i], Cdissip[i], Ct[i], Dstar[i])) - f.close() - f = open(name2, 'w+') - for i in range(len(blwUe)): - f.write("%s %s\n" % (0, blwUe[i])) - f.close() + def dictionary(self): + '''Create dictionary with the needed coordinates and the boundary layer parameters''' + wData = {} + wData['x'] = self.data[:,0] + wData['y'] = self.data[:,1] + wData['z'] = self.data[:,2] + wData['x/c'] = self.data[:,0]/max(self.data[:,0]) + wBlVariables = {} + wBlVariables['Cd'] = self.blScalars[0] + wBlVariables['Cdf'] = self.blScalars[1] + wBlVariables['Cdp'] = self.blScalars[2] + wBlVariables['delta*'] = self.blVectors[0] + wBlVariables['theta'] = self.blVectors[1] + wBlVariables['H'] = self.blVectors[2] + wBlVariables['Hk'] = self.blVectors[3] + wBlVariables['H*'] = self.blVectors[4] + wBlVariables['H**'] = self.blVectors[5] + wBlVariables['Cf'] = self.blVectors[6] + wBlVariables['Cdissip'] = self.blVectors[7] + wBlVariables['Ctau'] = self.blVectors[8] + wBlVariables['xtrTop'] = self.xtr[0] + wBlVariables['xtrBot'] = self.xtr[1] + return wData, wBlVariables - def __getBLcoordinates(data): - nN = len(data[:,0]) - nE = nN-1 #nbr of element along the surface - vt = np.zeros(nN) - xx = np.zeros(nN) # position along the surface of the airfoil - dx = np.zeros(nE) # distance along two nodes - dv = np.zeros(nE) # speed difference according to element - alpha = np.zeros(nE) # angle of the element for the rotation matrix - for i in range(0,nE): - alpha[i] = np.arctan2((data[i+1,1]-data[i,1]),(data[i+1,0]-data[i,0])) - dx[i] = np.sqrt((data[i+1,0]-data[i,0])**2+(data[i+1,1]-data[i,1])**2) - xx[i+1] = dx[i]+xx[i] - vt[i] = abs(data[i,3]*np.cos(alpha[i]) + data[i,4]*np.sin(alpha[i])) # tangential speed at node 1 element i - vt[i+1] = abs(data[i+1,3]*np.cos(alpha[i]) + data[i+1,4]*np.sin(alpha[i])) # tangential speed at node 2 element i - dv[i] = (vt[i+1] - vt[i])/dx[i] #velocity gradient according to element i. central scheme with half point - return xx, dx, dv, vt, nN + def writeFile(self): + '''Write the results in airfoil_viscous.dat''' + wData, wBlVariables = self.dictionary() + f = open('airfoil_viscous.dat', 'w+') + f.write('$Aerodynamic coefficients \n') + f.write("%s, %s, %s, %s, %s, %s \n" % ('Re','Cd', 'Cdf', 'Cdp', 'xtrTop', 'xtrBot')) + f.write("%f, %f, %f, %f, %f, %f \n" %(self.Re, wBlVariables['Cd'], wBlVariables['Cdf'], wBlVariables['Cdp'], wBlVariables['xtrTop'], wBlVariables['xtrBot'] )) + f.write('$Boundary layer variables \n') + f.write("%s, %s, %s, %s, %s, %s, %s \n" % ('x','y', 'z', 'x/c', 'delta*', 'H', 'Cf')) + for i in range(len(self.data[:,0])): + f.write("%e, %e, %e, %e, %e, %e, %e \n" % (wData['x'][i], wData['y'][i], wData['z'][i], wData['x/c'][i], wBlVariables['delta*'][i], wBlVariables['H'][i], wBlVariables['Cf'][i])) + f.close() + print('writing file: airfoil_viscous.dat... done!') - def boundaryLayer(data): - # Get the BL coordinates - xx, dx, dv, vt, nN = __getBLcoordinates(data) - if group.name == 'airfoil': - vt = savgol_filter(vt, 11, 2, mode = 'interp') - Me = data[:,5] - rhoe = data[:,6] - deltaStarOld = data[:,7] - #Initialize variable - blwVel = np.zeros(nN-1) # Blowing velocity - theta = np.zeros(nN) # Momentum thickness - H = np.zeros(nN) # Shape factor - deltaStar = np.zeros(nN) # Displacement thickness - Hstar = np.zeros(nN) # Kinetic energy shape parameter - Hstar2 = np.zeros(nN) # Density shape parameter - Hk = np.zeros(nN) # Kinematic shape factor - Ret = np.zeros(nN) #Re based on momentum thickness - Cd = np.zeros(nN) # Dissipation coefficient - Cf = np.zeros(nN) # Skin friction - n = np.zeros(nN) # Logarithm of the maximum amplification ratio - dndRet = np.zeros(nN) # Variation of n according to Re_theta - m = np.zeros(nN) # Empirical relation - l = np.zeros(nN) # Idem - Ct = np.zeros(nN) # Shear stress coefficient - Cteq = np.zeros(nN) # Equilibrium shear stress coefficient - delta = np.zeros(nN) # BL thickness - # Initial condition - if group.name == "airfoil": - theta[0], H[0], n[0], Ct[0] = group.initialConditions(self.Re, dv[0]) - elif group.name == "wake": - theta[0] = group.T0 - H[0] = group.H0 - n[0] = group.n0 - Ct[0] = group.Ct0 - idxTr = 0 # To Do: it is the index of the transition, should find a way to delete it for the wake - # vt[0] = vt[0]+ 4/(np.pi*dx[i-1])*(theta[0]*H[0]-deltaStarOld[i]) !!!! remains vt[0]? - Ret[0] = vt[0]*theta[0]*self.Re - deltaStar[0] = theta[0]*H[0] - # print('---------------Grid point n 0---------------------') - Cd[0], Cf[0], Hstar[0], Hstar2[0], dndRet[0], m[0], l[0], Hk[0], delta[0] = newLaminarClosure( theta[0], H[0], Ret[0],Me[0], group.name) - ntr = 9 # amplification ratio at which transition occurs TO DO: params from user (if not, impose a value of 9) - if n[0] < 9: - turb =0 # we begin with laminar flow - else : - turb =1 # typically for the wake - xtr = 0 # if only turbulent flow - # print('------------The solution at grid point ',+0,'=', + theta[0], +H[0], +Ct[0],+vt[0], 'X position:', + data[0,0]) - for i in range(1,nN): - '''x[0] = theta; x[1] = H; x[2] = n for laminar/Ct for turbulent; x[3] = vt from interaction law - ''' - # print('---------------Grid point n',+i,'---------------------') - def f_lam(x): - f = np.zeros(len(x)) - Ret[i] = x[3]*x[0]*self.Re - Cd[i], Cf[i], Hstar[i], Hstar2[i], dndRet[i], m[i], l[i], Hk[i], delta[i] = newLaminarClosure(x[0], x[1], Ret[i],Me[i], group.name) - dTheta = x[0]-theta[i-1] - due = x[3]-vt[i-1] - dH = x[1]-H[i-1] - dHstar = Hstar[i]-Hstar[i-1] - Ta = upw*x[0]+(1-upw)*theta[i-1] - RhsTheta1 = (2 + x[1] - Me[i]**2) * (x[0]/x[3]) * (due/dx[i-1]) - Cf[i]/2 - RhsTheta2 = (2 + H[i-1] - Me[i-1]**2) * (theta[i-1]/vt[i-1]) * (due/dx[i-1])- Cf[i-1]/2 - RhsH1 = (2*Hstar2[i] + Hstar[i]*(1-x[1])) * (x[0]/x[3]) * (due/dx[i-1]) - 2*Cd[i] + 0.5*Cf[i]*Hstar[i] - RhsH2 = (2*Hstar2[i-1] + Hstar[i-1]*(1-H[i-1])) * (theta[i-1]/vt[i-1]) * (due/dx[i-1]) - 2*Cd[i-1] + 0.5*Cf[i-1]*Hstar[i-1] - RhsN1 = dndRet[i]*((m[i])+1)/2 * l[i]/x[0] - RhsN2 = dndRet[i-1]*((m[i-1])+1)/2 * l[i-1]/theta[i-1] - f[0] = dTheta/dx[i-1] + ((1-upw)*RhsTheta2+upw*RhsTheta1) - f[1] = Ta*(dHstar/dx[i-1]) + ((1-upw)*RhsH2+upw*RhsH1) - f[2] = (x[2]-n[i-1])/dx[i-1] - ((1-upw)*RhsN2+upw*RhsN1) - # f[3] = x[3]-vt[i]- (4/(np.pi*dx[i-1]))*(x[0]*x[1]-deltaStarOld[i]) - f[3] = x[3] -vt[i] - return f - # Central differencing to gain accuracy - def f_turb(x): - f = np.zeros(len(x)) - Ret[i] = x[3]*x[0]*self.Re - Cd[i], Cf[i], Hstar[i], Hstar2[i],Hk[i],Cteq[i], delta[i] = newTurbulentClosure(x[0], x[1],x[2], Ret[i],Me[i], group.name) - dTheta = x[0]-theta[i-1] - due = x[3]-vt[i-1] - dH = x[1]-H[i-1] - dHstar = Hstar[i]-Hstar[i-1] - dCt = x[2]-Ct[i-1] # here it is Ct^1/2 which is represented - Ta = upw*x[0]+(1-upw)*theta[i-1] - Cta = upw*x[2]+(1-upw)*Ct[i-1] - deriv = 1/Cta * dCt/dx[i-1] - RhsTheta1 = (2 + x[1] - Me[i]**2) * (x[0]/x[3]) * (due/dx[i-1]) - Cf[i]/2 - RhsTheta2 = (2 + H[i-1] - Me[i-1]**2) * (theta[i-1]/vt[i-1]) * (due/dx[i-1])- Cf[i-1]/2 - RhsH1 = (2*Hstar2[i] + Hstar[i]*(1-x[1])) * (x[0]/x[3]) * (due/dx[i-1]) - 2*Cd[i] + 0.5*Cf[i]*Hstar[i] - RhsH2 = (2*Hstar2[i-1] + Hstar[i-1]*(1-H[i-1])) * (theta[i-1]/vt[i-1]) * (due/dx[i-1]) - 2*Cd[i-1] + 0.5*Cf[i-1]*Hstar[i-1] - delta1 =delta[i] - delta2 = delta[i-1] - TmpHk1 = 4/(3*x[1]*x[0]) * (0.5*Cf[i] - ((Hk[i]-1)/(6.7*Hk[i]))**2) - TmpHk2 = 4/(3*H[i-1]*theta[i-1]) * (0.5*Cf[i-1] - ((Hk[i-1]-1)/(6.7*Hk[i-1]))**2) - TmpUe1 = 1/x[3] * due/dx[i-1] - TmpUe2 = 1/vt[i-1] * due/dx[i-1] - TmpCteq1 = 5.6*(Cteq[i]-x[2]) - TmpCteq2 = 5.6*(Cteq[i-1]-Ct[i-1]) - f[0] = dTheta/dx[i-1] + ((1-upw)*RhsTheta2+upw*RhsTheta1) - f[1] = Ta*(dHstar/dx[i-1]) + ((1-upw)*RhsH2+upw*RhsH1) - f[2] = 2*((upw*delta1+(1-upw)*delta2)*(deriv - (upw*TmpHk1+(1-upw)*TmpHk2) + (upw*TmpUe1+(1-upw)*TmpUe2))) - (upw*TmpCteq1+(1-upw)*TmpCteq2) - # f[3] = x[3]-vt[i]- 4/(np.pi*dx[i-1])*(x[0]*x[1]-deltaStarOld[i]) - f[3] = x[3] -vt[i] - return f - '''Solver depending on the case ''' - # Laminar solver - if turb == 0: - if n[i-1] > 8.5: - upw = 0.5 # Trapezoidal scheme - elif i < 7: - upw = 1 # backward Euler - else: - upw = 0.5 # Trapezoidal scheme - x = np.array([theta[i-1],H[i-1], n[i-1], vt[i]]) - sol = newton.newtonRaphson(f_lam, x, 120) - logRet_crit = 2.492*(1/(H[i-1]-1))**0.43 + 0.7*(np.tanh(14*(1/(H[i-1]-1))-9.24)+1.0) # value at which the amplification starts to growth - if np.log10(Ret[i]) <= logRet_crit - 0.08 : - n[i] = 0 - else: - n[i] = sol[2] - xtr = 1 # no transition if remains laminar - # Turbulent solver - elif turb == 1: - upw = 0.5 # trapezoidal scheme - x = np.array([theta[i-1], H[i-1], Ct[i-1], vt[i]]) - sol = newton.newtonRaphson(f_turb, x, 120) - Ct[i] = sol[2] - # print('------------The solution at grid point ',+i,'=', + sol,+vt[i], 'X position:', + data[i,0]) - theta[i] = sol[0] - H[i] = sol[1] - vt[i] = sol[3] - # Transition solver - if n[i] >= ntr and turb ==0: - # Initial condition - turb=1 - Ct[i-1] = Ct[0] - # Interpolation to define position of the transition - idxTr = i - xtr = (ntr-n[i-1])*((data[i,0]-data[i-1,0])/(n[i]-n[i-1]))+data[i-1,0] - avTurb = (data[i,0]-xtr)/(data[i,0]-data[i-1,0]) # percentage of the subsection corresponding to a laminar flow - avLam = 1- avTurb # percentage of the subsection corresponding to a turbulent flow - # Compute boundary layer parameters with FOU for turbulent case - upw = 0 - x_turbTr = np.array([theta[i-1], 1.515, Ct[i-1], vt[i]]) - sol_turbTr = newton.newtonRaphson(f_turb, x_turbTr, 30) - # # Averaging both solutions - theta[i] = avLam*sol[0]+avTurb*sol_turbTr[0] - H[i] = avLam*sol[1]+avTurb*sol_turbTr[1] - vt[i] = avLam*sol[3]+avTurb*sol_turbTr[3] - # theta[i] = avTurb*sol_turbTr[0] - # H[i] = avTurb*sol_turbTr[1] - Ct[i] = avTurb*sol_turbTr[2] - # print('Ok for the transition part') - # print('------------The solution at grid point ',+i,'=', +theta[i], +H[i], +Ct[i],+vt[i], 'X position:', + data[i,0]) - deltaStar[i] = H[i]*theta[i] - blwVel[i-1] = (rhoe[i]*vt[i]*deltaStar[i]-rhoe[i-1]*vt[i-1]*deltaStar[i-1])/(rhoe[i]*dx[i-1]) # TO DO: need to divide by rhoe or not? - Cdf = np.trapz(Cf, data[:,0]) - Cdtot = 2*theta[-1]*(vt[-1]**((H[-1]+5)/2)) - blVariables = [blwVel, deltaStar, theta, H, Cf, xtr, Cdf, Cdtot, vt, Cd ,n[idxTr], Ct] - return blVariables - - def newLaminarClosure(theta, H, Ret,Me, name): - ''' Laminar closure derived from the Falkner-Skan one-parameter profile family - Drela(1996)''' - Hk = (H - 0.29*Me**2)/(1+0.113*Me**2) # Kinematic shape factor - Hstar2 = (0.064/(Hk-0.8)+0.251)*Me**2 # Density shape parameter - dndRet = 0.01*np.sqrt((2.4*Hk-3.7+2.5*np.tanh(1.5*Hk-4.65))**2+0.25) - l = (6.54*Hk-14.07)/(Hk**2) - m = (0.058*(((Hk-4)**2)/(Hk-1))-0.068)/l - Hmi = (1/(H-1)) - # m = -0.05+2.7*Hmi-5.5*Hmi**2+3*Hmi**3+0.1*np.exp(-20*Hmi) - # dndRet = 0.028*(Hk-1)-0.0345*np.exp((-3.87*Hmi+2.52)**2) - delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta) - Hks = Hk - 4.35 - if Hk <= 4.35: - dens = Hk + 1 - Hstar = 0.0111*Hks**2/dens - 0.0278*Hks**3/dens + 1.528 -0.0002*(Hks*Hk)**2 - elif Hk > 4.35: - Hstar = 1.528 + 0.015*Hks**2/Hk - Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction - if Hk <= 5.5: - tmp = (5.5-Hk)**3 / (Hk+1) - Cf = (-0.07 + 0.0727*tmp)/ Ret - elif Hk > 5.5: - print('Laminar separation') - tmp = 1 - 1/(Hk-4.5) - Cf = (-0.07 + 0.015*tmp**2) / Ret - if Hk <= 4: - Cd = ( 0.00205*(4.0-Hk)**5.5 + 0.207 ) * (Hstar/(2*Ret)) - elif Hk > 4: - HkCd = (Hk-4)**2 - denCd = 1+0.02*HkCd - Cd = ( -0.0016*HkCd/denCd + 0.207) * (Hstar/(2*Ret)) - if name == 'wake': - Cd = 2*(1.10*(1.0-1.0/Hk)**2/Hk)*(Hstar/(2*Ret)) - # print(' Ret, Cd, Cf, Hk, H* = ',+ Ret, + Cd, +Cf, +Hk, + Hstar) - return Cd, Cf, Hstar, Hstar2, dndRet, m, l, Hk, delta + def __getBLcoordinates(self, data): + '''Transform the reference coordinates into airfoil coordinates''' + nN = len(data[:,0]) + nE = nN-1 #nbr of element along the surface + vt = np.zeros(nN) + xx = np.zeros(nN) # position along the surface of the airfoil + dx = np.zeros(nE) # distance along two nodes + dv = np.zeros(nE) # speed difference according to element + alpha = np.zeros(nE) # angle of the element for the rotation matrix + for i in range(0,nE): + alpha[i] = np.arctan2((data[i+1,1]-data[i,1]),(data[i+1,0]-data[i,0])) + dx[i] = np.sqrt((data[i+1,0]-data[i,0])**2+(data[i+1,1]-data[i,1])**2) + xx[i+1] = dx[i]+xx[i] + vt[i] = (data[i,3]*np.cos(alpha[i]) + data[i,4]*np.sin(alpha[i])) # tangential speed at node 1 element i + vt[i+1] = (data[i+1,3]*np.cos(alpha[i]) + data[i+1,4]*np.sin(alpha[i])) # tangential speed at node 2 element i + dv[i] = (vt[i+1] - vt[i])/dx[i] #velocity gradient according to element i. central scheme with half point + return xx, dv, vt, nN, alpha - def newTurbulentClosure(theta, H, Ct, Ret, Me, name): - ''' Turbulent closure derived using the skin friction and velocity profile formulas of Swafford (1983)''' - Hk = (H - 0.29*Me**2)/(1+0.113*Me**2) - if name == "airfoil": - Hk = max(Hk, 1.05) + def __amplRoutine(self, Hk, Ret, theta): + '''Compute the amplitude of the TS waves envelop for the transition''' + Dgr = 0.08 + Hk1 = 3.5 + Hk2 = 4 + Hmi = (1/(Hk-1)) + logRet_crit = 2.492*(1/(Hk-1))**0.43 + 0.7*(np.tanh(14*Hmi-9.24)+1.0) # value at which the amplification starts to grow + if Ret <=0: + Ret = 1 + if np.log10(Ret) < logRet_crit - Dgr : + Ax = 0 + else: + Rnorm = (np.log10(Ret) - (logRet_crit-Dgr))/(2*Dgr) + if Rnorm <=0: + Rfac = 0 + if Rnorm >= 1: + Rfac = 1 else: - Hk = max(Hk, 1.00005) - Hstar2 = ((0.064/(Hk-0.8))+0.251)*Me**2 - gam = self.gamma - 1 - Fc = np.sqrt(1+0.5*gam*Me**2) - if Ret <= 400: - H0 = 4 - elif Ret > 400: - H0 = 3 + (400/Ret) - if Ret > 200: - Ret = Ret - elif Ret <= 200: - Ret = 200 - if Hk <= H0: - Hstar = ((0.5-4/Ret)*((H0-Hk)/(H0-1))**2)*(1.5/(Hk+0.5))+1.5+4/Ret - elif Hk > H0: - print('The flow is separated here') - Hstar = (Hk-H0)**2*(0.007*np.log10(Ret)/(Hk-H0+4/np.log10(Ret))**2 + 0.015/Hk) + 1.5 + 4/Ret - Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction - logRt = np.log10(Ret/Fc) - logRt = np.max((logRt,3)) - arg = np.max((-1.33*Hk, -20)) - Us = (Hstar/2)*(1-4*(Hk-1)/(3*H)) # Equivalent normalized wall slip velocity - delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta) - Ctcon = 0.5/(6.7**2*0.75) - if name == "wake": - if Us > 0.99995: - Us = 0.99995 - n = 2 # two wake halves - Cf = 0 # no friction inside the wake - Hkc = Hk - 1 - Cdi = 0.03*0.75**3*Us**3 # inner shear layer Row 1062 from xfoil - else: - if Us > 0.95: - Us = 0.98 - n = 1 - # Cf = (1/Fc)*(0.3*np.exp(arg)*(logRt)**(-1.74-0.31*Hk)+0.00011*(np.tanh(4-(Hk/0.875))-1)) - Cf0 = 0.01013/(logRt-1.02) - 0.00075 - Ho = 1/(1-6.55*np.sqrt(Cf0/2)) - Cf = (1/Fc)*Cf0*(-0.5 + 0.9/(Hk/Ho-0.4)) - Hkc = Hk - 1 - 18/Ret - Hkc = max(Hkc, 0.01) - Cdi = 0 - Cd = n*((Cf*Us/2)+(0.995-Us)*Ct**2 +0.15*(0.995-Us)**2/Ret) - Cteq = np.sqrt(Hstar*Ctcon*((Hk-1)*Hkc**2)/((1-Us)*((Hk**2)*H))) #Here it is Cteq^0.5 - # print('Cteq, Cd, Cf, Hk, H* = ', +Cteq, + Cd, +Cf, +Hk, + Hstar) - return Cd, Cf, Hstar, Hstar2, Hk, Cteq, delta + Rfac = 3*Rnorm**2 - 2*Rnorm**3 + # normal envelope amplification rate + m = -0.05+2.7*Hmi-5.5*Hmi**2+3*Hmi**3+0.1*np.exp(-20*Hmi) + arg = 3.87*Hmi-2.52 + dndRet = 0.028*(Hk-1)-0.0345*np.exp(-(arg**2)) + Ax = (m*dndRet/theta)*Rfac + # set correction for separated profiles + if Hk > Hk1: + Hnorm = (Hk - Hk1)/(Hk2-Hk1) + if Hnorm >=1: + Hfac = 1 + else: + Hfac = 3*Hnorm**2 - 2*Hnorm**3 + Ax1 = Ax + Gro = 0.3+0.35*np.exp(-0.15*(Hk-5)) + Tnr = np.tanh(1.2*(np.log10(Ret)-Gro)) + Ax2 = (0.086*Tnr - 0.25/(Hk-1)**1.5)/theta + if Ax2 < 0: + Ax2 = 0 + Ax = Hfac*Ax2 + (1-Hfac)*Ax1 + if Ax < 0: + Ax = 0 + return Ax + + def __laminarClosure(self, theta, H, Ret,Me, name): + ''' Laminar closure derived from the Falkner-Skan one-parameter profile family + Nishida and Drela (1996)''' + Hk = (H - 0.29*Me**2)/(1+0.113*Me**2) # Kinematic shape factor + if name == "airfoil": + Hk = max(Hk, 1.02) + Hk = min(Hk,6) + else: + Hk = max(Hk, 1.00005) + Hk = min(Hk,6) + Hstar2 = (0.064/(Hk-0.8)+0.251)*Me**2 # Density shape parameter + delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta) + Hks = Hk - 4.35 + if Hk <= 4.35: + dens = Hk + 1 + Hstar = 0.0111*Hks**2/dens - 0.0278*Hks**3/dens + 1.528 -0.0002*(Hks*Hk)**2 + elif Hk > 4.35: + Hstar = 1.528 + 0.015*Hks**2/Hk + Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction + if Hk < 5.5: + tmp = (5.5-Hk)**3 / (Hk+1) + Cf = (-0.07 + 0.0727*tmp)/Ret + elif Hk >= 5.5: + tmp = 1 - 1/(Hk-4.5) + Cf = (-0.07 + 0.015*tmp**2) /Ret + if Hk < 4: + Cd = ( 0.00205*(4.0-Hk)**5.5 + 0.207 ) * (Hstar/(2*Ret)) + elif Hk >= 4: + HkCd = (Hk-4)**2 + denCd = 1+0.02*HkCd + Cd = ( -0.0016*HkCd/denCd + 0.207) * (Hstar/(2*Ret)) + if name == 'wake': + Cd = 2*(1.10*(1.0-1.0/Hk)**2/Hk)* (Hstar/(2*Ret)) + Cf = 0 + return Cd, Cf, Hstar, Hstar2, Hk, delta, H + + def __turbulentClosure(self, theta, H, Ct, Ret, Me, name): + ''' Turbulent closure derived from Nishida and Drela (1996)''' + # eliminate absurd transients + Ct = min(Ct, 0.30) + Ct = max(Ct, 0.0000001) + Hk = (H - 0.29*Me**2)/(1+0.113*Me**2) + if name == "wake": + Hk = max(Hk, 1.00005) + Hk = min(Hk,10) + else: + Hk = max(Hk, 1.00005) + Hk = min(Hk,10) + Hstar2 = ((0.064/(Hk-0.8))+0.251)*Me**2 + gam = self.gamma - 1 + Fc = np.sqrt(1+0.5*gam*Me**2) + if Ret <= 400: + H0 = 4 + elif Ret > 400: + H0 = 3 + (400/Ret) + if Ret > 200: + Ret = Ret + elif Ret <= 200: + Ret = 200 + if Hk <= H0: + Hstar = ((0.5-4/Ret)*((H0-Hk)/(H0-1))**2)*(1.5/(Hk+0.5))+1.5+4/Ret + elif Hk > H0: + Hstar = (Hk-H0)**2*(0.007*np.log(Ret)/(Hk-H0+4/np.log(Ret))**2 + 0.015/Hk) + 1.5 + 4/Ret + Hstar = (Hstar + 0.028*Me**2)/(1+0.014*Me**2) # Whitfield's minor additional compressibility correction + logRt = np.log(Ret/Fc) + logRt = np.max((logRt,3)) + arg = np.max((-1.33*Hk, -20)) + Us = (Hstar/2)*(1-4*(Hk-1)/(3*H)) # Equivalent normalized wall slip velocity + delta = min((3.15+ H + (1.72/(Hk-1)))*theta, 12*theta) + Ctcon = 0.5/(6.7**2*0.75) + if name == "wake": + if Us > 0.99995: + Us = 0.99995 + n = 2 # two wake halves + Cf = 0 # no friction inside the wake + Hkc = Hk - 1 + Cdw = 0 # wall contribution + Cdd = (0.995-Us)*Ct**2 # turbulent outer layer contribution + Cdl = 0.15*(0.995-Us)**2/Ret # laminar stress contribution to outer layer + else: + if Us > 0.95: + Us = 0.98 + n = 1 + Cf = (1/Fc)*(0.3*np.exp(arg)*(logRt/2.3026)**(-1.74-0.31*Hk)+0.00011*(np.tanh(4-(Hk/0.875))-1)) + Hkc = max(Hk-1-18/Ret, 0.01) + # dissipation coefficient + Hmin = 1 + 2.1/np.log(Ret) + Fl = (Hk-1)/(Hmin-1) + Dfac = 0.5+0.5*np.tanh(Fl) + Cdw = 0.5*(Cf*Us) * Dfac + Cdd = (0.995-Us)*Ct**2 + Cdl = 0.15*(0.995-Us)**2/Ret + Cteq = np.sqrt(n*Hstar*Ctcon*((Hk-1)*Hkc**2)/((1-Us)*(Hk**2)*H)) #Here it is Cteq^0.5 + Cd = n*(Cdw+ Cdd + Cdl) + Ueq = 1.25/Hk*(Cf/2-((Hk-1)/(6.432*Hk))**2) + return Cd, Cf, Hstar, Hstar2, Hk, Cteq, delta, Ueq, Ct - group.connectListElems, data = group.connectList() + + def __boundaryLayer(self, data, group): + '''Discretize and solve the boundary layer equations for laminar/turbulent flows''' + xx, dv, vtOld, nN, alpha = self.__getBLcoordinates(data) + Me = data[:,5] + rhoe = data[:,6] + deltaStarOld = data[:,7] + #Filter the initial conditions (Me and rho can be filtered too) + if group.name == 'airfoil': + vtOld = savgol_filter(vtOld, self.m, self.p, mode = 'interp') + vtInv = vtOld + rhoInv = rhoe + if self.it == 0: + xxOld = xx + else: + xxOld = data[:,8] + #Initialize variables + blwVel = np.zeros(nN-1) # Blowing velocity + vt = np.zeros(nN) # Boundary layer velocity + theta = np.zeros(nN) # Momentum thickness + H = np.zeros(nN) # Shape factor + deltaStar = np.zeros(nN) # Displacement thickness + Hstar = np.zeros(nN) # Kinetic energy shape parameter + Hstar2 = np.zeros(nN) # Density shape parameter + Hk = np.zeros(nN) # Kinematic shape factor + Ret = np.zeros(nN) #Re based on momentum thickness + Cd = np.zeros(nN) # Dissipation coefficient + Cf = np.zeros(nN) # Skin friction + n = np.zeros(nN) # Logarithm of the maximum amplification ratio + Ax = np.zeros(nN) # Amplification factor + Ct = np.zeros(nN) # Shear stress coefficient + Cteq = np.zeros(nN) # Equilibrium shear stress coefficient + Ueq = np.zeros(nN) # Equilibrium velocity gradient + delta = np.zeros(nN) # BL thickness + # Boundary conditions + if group.name == "airfoil": + theta[0], H[0], n[0], Ct[0] = group.initialConditions(self.Re, dv[0]) + elif group.name == "wake": + theta[0] = group.T0 + H[0] = group.H0 + n[0] = group.n0 + Ct[0] = group.Ct0 + vt[0] = vtOld[0] + Ret[0] = vt[0]*theta[0]*self.Re + if Ret[0] < 1: + Ret[0] = 1 + vt[0] = Ret[0]/(theta[0]*self.Re) + deltaStar[0] = theta[0]*H[0] + # Define transition location ( N = 9) and compute stagnation point + ntr = 9 + if n[0] < 9: + turb =0 # we begin with laminar flow + Cd[0], Cf[0], Hstar[0], Hstar2[0], Hk[0], delta[0], H[0] = self.__laminarClosure( theta[0], H[0], Ret[0],Me[0], group.name) + else : + turb = 1 # typically for the wake + Cd[0], Cf[0], Hstar[0], Hstar2[0], Hk[0], delta[0], H[0] = self.__laminarClosure( theta[0], H[0], Ret[0],Me[0], group.name) + xtr = 0 # if only turbulent flow + itTurb = 0 + # Solve the boundary layer equations + for i in range(1,nN): + # x[0] = theta; x[1] = H; x[2] = n for laminar/Ct for turbulent; x[3] = vt from interaction law + def f_lam(x): + f = np.zeros(len(x)) + Ret[i] = np.maximum(x[3]*x[0]*self.Re, 1) + Cd[i], Cf[i], Hstar[i], Hstar2[i], Hk[i], delta[i], x[1] = self.__laminarClosure(x[0], x[1], Ret[i],Me[i], group.name) + Ta = upw*x[0]+(1-upw)*theta[i-1] + Ha = upw*x[1]+(1-upw)*H[i-1] + uea = upw*x[3]+(1-upw)*vt[i-1] + Reta = upw*Ret[i] + (1-upw)*Ret[i-1] + Mea = upw*Me[i]+(1-upw)*Me[i-1] + Cda, Cfa, Hstara, Hstar2a, Hka, deltaa, Ha = self.__laminarClosure(Ta , Ha, Reta, Mea, group.name) + dx = xx[i] - xx[i-1] + Axa = self.__amplRoutine(Hka, Reta, Ta) + dTheta = x[0]-theta[i-1] + due = x[3]-vt[i-1] + dH = x[1]-H[i-1] + dn = x[2] - n[i-1] + dHstar = Hstar[i]-Hstar[i-1] + Hstara = upw*Hstar[i]+(1-upw)*Hstar[i-1] + f[0] = dTheta/dx + (2 + Ha - Mea**2)*(Ta/uea)*due/dx - Cfa/2 + f[1] = Ta*(dHstar/dx) + (2*Hstar2a + Hstara*(1-Ha))*Ta/uea * due/dx - 2*Cda + Hstara*Cfa/2 + f[2] = dn - dx*Axa + f[3] = uea - 4/(np.pi*dx)*Ta*Ha - (upw*vtOld[i]+(1-upw)*vtOld[i-1]) + 4/(np.pi*(xxOld[i]-xxOld[i-1]))*(upw*deltaStarOld[i]+(1-upw)*deltaStarOld[i-1]) + return f + def f_turb(x): + f = np.zeros(len(x)) + if group.name == 'wake': + dissipRatio = 0.9 # wall/wake dissipation length ratio + else: + dissipRatio = 1 + Ret[i] = x[3]*x[0]*self.Re + Ta = upw*x[0]+(1-upw)*theta[i-1] + xxa = upw*xx[i]+(1-upw)*xx[i-1] + Ha = upw*x[1]+(1-upw)*H[i-1] + Cta = upw*x[2]+(1-upw)*Ct[i-1] + uea = upw*x[3]+(1-upw)*vt[i-1] + Reta = np.maximum(upw*(x[3]*x[0]*self.Re)+(1-upw)*(vt[i-1]*theta[i-1]*self.Re), 1) + Mea = upw*Me[i]+(1-upw)*Me[i-1] + Cd[i], Cf[i], Hstar[i], Hstar2[i],Hk[i],Cteq[i], delta[i], Ueq[i], x[2] = self.__turbulentClosure(x[0], x[1],x[2], Ret[i],Me[i], group.name) + Cda, Cfa, Hstara, Hstar2a, Hka,Cteqa, deltaa, Ueqa, Cta = self.__turbulentClosure(Ta, Ha,Cta, Reta,Mea, group.name) + dx = xx[i]-xx[i-1] + dTheta = x[0]-theta[i-1] + due = x[3]-vt[i-1] + dH = x[1]-H[i-1] + dHstar = Hstar[i]-Hstar[i-1] + dCt = x[2]-Ct[i-1] # here it is Ct^1/2 which is represented + Ta = upw*x[0]+(1-upw)*theta[i-1] + Cta = upw*x[2]+(1-upw)*Ct[i-1] + f[0] = dTheta/dx + (2 + Ha - Mea**2)*(Ta/uea)*due/dx - Cfa/2 + f[1] = Ta*(dHstar/dx) + (2*Hstar2a + Hstara*(1-Ha))*Ta/uea * due/dx - 2*Cda + Hstara*Cfa/2 + f[2] = (2*deltaa/Cta)*(dCt/dx) - 5.6*(Cteqa-Cta*dissipRatio)-2*deltaa*(4/(3*Ha*Ta)*(Cfa/2-((Hka-1)/(6.7*Hka*dissipRatio))**2)-1/uea * due/dx) + f[3] = uea - 4/(np.pi*dx)*Ta*Ha - (upw*vtOld[i]+(1-upw)*vtOld[i-1]) + 4/(np.pi*(xxOld[i]-xxOld[i-1]))*(upw*deltaStarOld[i]+(1-upw)*deltaStarOld[i-1]) + return f + # Laminar solver + if turb == 0: + if n[i-1] > 8 or i < 5: + upw = 1 # backward Euler + else: + upw = 0.5 # Trapezoidal scheme + x = np.array([theta[i-1],H[i-1], n[i-1], vt[i-1]]) + sol = Newton.newtonRaphson(f_lam, x, 200) + # Determine if the amplification waves start to grow or not + logRet_crit = 2.492*(1/(Hk[i]-1))**0.43 + 0.7*(np.tanh(14*(1/(Hk[i]-1))-9.24)+1.0) + if np.log10(Ret[i]) <= logRet_crit - 0.08 : + n[i] = 0 + else: + n[i] = sol[2] + xtr = 1 + # Turbulent solver + elif turb == 1: + if i <2 and group.name =='wake': + upw = 1 # begin of the wake + elif itTurb <2 and group.name == 'airfoil': + upw = 1 + itTurb += 1 + else: + upw = 0.5 # trapezoidal scheme + x = np.array([theta[i-1], H[i-1], Ct[i-1], vt[i-1]]) + sol = Newton.newtonRaphson(f_turb, x, 200) + Ct[i] = sol[2] + theta[i] = sol[0] + H[i] = sol[1] + vt[i] = sol[3] + # Transition solver + if n[i] >= ntr and turb ==0: + turb = 1 + upw = 1 + xtr = (ntr-n[i-1])*((data[i,0]-data[i-1,0])/(n[i]-n[i-1]))+data[i-1,0] + avTurb = (data[i,0]-xtr)/(data[i,0]-data[i-1,0]) # percentage of the subsection corresponding to a turbulent flow + avLam = 1- avTurb # percentage of the subsection corresponding to a laminar flow + # Averaging both solutions + Cteq[i-1]= self.__turbulentClosure(theta[i-1], H[i-1], 0.03, Ret[i-1], Me[i-1], group.name)[5] + Ct[i-1] = avLam*Cteq[i-1] * 0.7 + x_turb = np.array([theta[i-1], 1.515, Ct[i-1] , vt[i-1]]) + sol_turb = Newton.newtonRaphson(f_turb, x_turb, 200) + theta[i] = avLam*sol[0]+avTurb*sol_turb[0] + H[i] = avLam*sol[1]+avTurb*sol_turb[1] + if group.name == 'airfoil': + Ct[i] = max(avTurb*sol_turb[2],0.03) + else: + Ct[i] = max(avTurb*sol_turb[2],0.05) + vt[i] = avLam*sol[3]+avTurb*sol_turb[3] + Cd[i-1], Cf[i-1], Hstar[i-1], Hstar2[i-1], Hk[i-1], delta[i-1], _ = self.__laminarClosure(theta[i-1], H[i-1], Ret[i-1],Me[i-1], group.name) + Cd[i], Cf[i], Hstar[i], Hstar2[i],Hk[i],Cteq[i], delta[i], Ueq[i], _ = self.__turbulentClosure(theta[i], H[i],Ct[i], Ret[i],Me[i], group.name) + deltaStar[i] = H[i]*theta[i] + blwVel[i-1] = -1*(rhoe[i]*vt[i]*deltaStar[i]-rhoe[i-1]*vt[i-1]*deltaStar[i-1])/(rhoe[i]*(xx[i]-xx[i-1])) # TO DO: need to divide by rhoe or not? + # Normalize the friction and dissipation coefficients by the freestream velocity + Cf = vt**2*Cf + Cd = vt**2*Cd + # Compute the various drag coefficients (total, friction, profile) + Cdtot = 2*theta[-1]*(vt[-1]**((H[-1]+5)/2)) + Cdf = np.trapz(Cf, data[:,0]) + Cdp = Cdtot-Cdf + # Outputs + blScalars = [Cdtot, Cdf, Cdp] + blVectors = [deltaStar, theta, H, Hk, Hstar, Hstar2, Cf, Cd, Ct] + return blwVel, xtr, xx, blScalars, blVectors + + + def run(self, group): + ''' Run the viscous solver to solve the BL equations and give the outputs to the coupler + ''' + group.connectListNodes, group.connectListElems, data = group.connectList() + # Compute BLE for the airfoil (upper section(data[0]) and lower section (data[1])) if len(data) == 2: - ublVariables = boundaryLayer(data[0]) - lblVariables = boundaryLayer(data[1]) - blwVel = abs(np.concatenate((ublVariables[0], lblVariables[0]))) - group.Cd = ublVariables[7] + lblVariables[7] - group.Cdp = group.Cd-(ublVariables[6]+lblVariables[6]) - self.top_xtr = ublVariables[5] - self.bot_xtr = lblVariables[5] - self.Cdp = group.Cdp - group.TEnd = [ublVariables[2][-1], lblVariables[2][-1]] - group.HEnd = [ublVariables[3][-1], lblVariables[3][-1]] - group.CtEnd = [ublVariables[11][-1], lblVariables[11][-1]] - group.nEnd = [ublVariables[10], lblVariables[10]] - # Write the results - writeFile(np.concatenate((data[0], data[1])), np.concatenate((ublVariables[3], lblVariables[3])), np.concatenate((ublVariables[2], lblVariables[2])), - np.concatenate((ublVariables[4], lblVariables[4])), np.concatenate((ublVariables[8], lblVariables[8])), - np.concatenate((ublVariables[9], lblVariables[9])), np.concatenate((ublVariables[11], lblVariables[11])), - np.concatenate((ublVariables[1], lblVariables[1])), blwVel) - lblVariables[1] = np.delete(lblVariables[1],0,0) # issue with the number of point - group.deltaStar = np.concatenate((ublVariables[1], lblVariables[1])) + ublwVel, xtrTop, uxx, ublScalars, ublVectors = self.__boundaryLayer(data[0], group) + lblwVel, xtrBot, lxx, lblScalars, lblVectors = self.__boundaryLayer(data[1], group) + # Put the upper and lower values together + self.data = np.concatenate((data[0], data[1])) + self.blScalars = np.add(ublScalars, lblScalars) + self.blVectors = np.hstack((ublVectors, lblVectors)) + blwVel = np.concatenate((ublwVel, lblwVel)) + blwVel = savgol_filter(blwVel, self.m, self.p, mode = 'interp') + self.xtr = [xtrTop, xtrBot] + # Boundary conditions for the wake + group.TEnd = [ublVectors[1][-1], lblVectors[1][-1]] + group.HEnd = [ublVectors[2][-1], lblVectors[2][-1]] + group.CtEnd = [ublVectors[8][-1], lblVectors[8][-1]] + # Delete the stagnation point doublon for variables in the VII loop + lblVectors[0] = np.delete(lblVectors[0],0,0) #deltastar + lxx = np.delete(lxx,0,0) # airfoil coordinates + group.deltaStar = np.concatenate((ublVectors[0], lblVectors[0])) + group.xx = np.concatenate((uxx, lxx)) + # Compute BLE for the wake else: - blVariables = boundaryLayer(data) - blwVel = abs(blVariables[0]) - group.Cd = blVariables[7] - group.Cdp = group.Cd-blVariables[6] - self.Cd = group.Cd - group.deltaStar = blVariables[1] - - group.u = blwVel + blwVel, _, group.xx, blScalars, blVectors = self.__boundaryLayer(data, group) + group.deltaStar = blVectors[0] + # The drag is measured at the end of the wake + self.Cd = blScalars[0] + self.Cdp = self.Cd-self.blScalars[1] # Cdf comes from the airfoil as there is no friction in the wake + self.blScalars[0] = self.Cd + self.blScalars[2] = self.Cdp + # Sort the following results in reference frame + group.deltaStar = group.deltaStar[group.connectListNodes.argsort()] + group.xx = group.xx[group.connectListNodes.argsort()] + group.u = blwVel[group.connectListElems] -- GitLab