二次元翼理論
2022 AdventCalender 数学・物理 の10日目です。
二次元完全流体
流体の基礎方程式は解きづらいことで有名(?)ですが、完全流体の仮定を置いてよいときにはかなり簡単化されます。完全流体とは、
- 非圧縮:密度が一定
- 粘性なし:応力が圧力による面法線方向のみ
を満たす流体のことです。非圧縮なので密度
流体の速度場
さらに速度場が時間的に定常であるような状況を考えます。
ここまで仮定するとベルヌーイの定理から、
が成り立つことが知られています。ここで
回転と発散がないということは、速度場は調和関数ということになります。このため、境界条件を色々固定するだけで速度場の解が決まってしまい、流体の運動方程式を実質的に考える必要がなくなります。
さらに3次元方向を無視します。つまり、
以下適当に数学的な話もしますが、もし数学的な話に飽きたら最後の節に飛んで目の保養をしましょう。
二次元速度場と複素関数
二次元速度場
と表現することにします。このとき、回転(渦)は
発散(湧き出し)は
に相当します。ただし二次元であるため、ベクトル場の回転はその
なおホッジ作用素は
と振る舞い
ベクトル解析的には
軌道法線積分について、
速度場に対するこれらをそれぞれ循環と流量と呼ぶことにします。
渦なし
ポテンシャル、流れ関数がそれぞれ存在する場合は
となります。渦なし湧き出しなしの完全流体を考えますが、二次元翼理論では、平面上に翼断面が穴を開けているようなシチュエーションを考えます。このため、速度場が渦なし、つまり環形式であっても、境界形式とは限りません。これは翼の周りの線積分が0でない=循環が存在し得ることを意味しますが、このことが翼理論では重要になってきます。
以下、平面上の区分的滑らかな単純閉曲線
という条件にあたります。つまり法線ベクトルが
で、それと直交するということです。従って翼型境界での速度場の軌道法線積分は常にゼロになります。
ところで(
と書き直すことができます。ここで、
したがって、翼型境界で流体がめり込まない境界条件を課しておけば、流れ関数
二次元では、速度場を複素関数として表現できます。
を複素速度といいます。ここで残念なお知らせがあり、
よいお知らせは、渦なし湧き出し無し条件が実は
翼境界上の境界条件を複素関数の観点から書き直しておくとあとで便利です。
とは
と書き直すことができます。これは
と同じことです。
複素速度については正則なので複素積分をしたくなります。これを実行すると
となるので循環は
であり、流量は
と複素積分を用いて表現できます。
ブラウジスの定理
今、二次元平面/複素平面があって、そこに翼断面に相当する領域とその境界である区分的連続な単純閉曲線
このような状況ではローラン展開が可能です。ただし、ここで追加で次のように境界条件を課してしておきましょう。
そうなると、
翼型を左に回る軌道で複素速度を積分した場合、積分定理から
です。ところで、翼型の表面での流量は0であり湧き出しもないので、この値の虚部は0であり、実部は循環です。したがって
とおくことができます。
翼は剛体だとします。そうすると力学的に重要なのは、かかる力の総和と、モーメントです。
流体への外力を無視してよいなら、ベルヌーイの定理から、圧力が
と分かっているので、この速度場から力とモーメントを計算することができます。
定数項を単純閉曲線で線積分すると相殺して消えるため、これは無視します。これを複素積分で書き直します。
ここで、翼型境界では
が成り立つことを用いています。
同様に、モーメントについても
ですが、
なのでこの項を再び無視すると、
これらの公式はブラウジスの定理と呼ばれています(クラウジウスではありません)。これは複素速度の複素積分になっており、正則関数の積分なので、積分軌道を翼型境界から、翼を含む任意の左回り閉曲線に取り直してもよいです。ローラン展開を考慮すると、負べきの複素関数の閉軌道積分では、
例えば、翼の性能として揚力というものがありますが、揚力については循環
となり、揚力が気体密度、翼まわりの循環、対気速度で決まることがわかります。これはクッタ・ジューコフスキーの定理と呼ばれています。(後に説明する同名の仮定とは別)。速度を上げれば揚力が得られるのは経験上も頷けます。その係数が翼まわりの循環
なおこのとき
リーマンの写像定理と円筒翼
リーマンの写像定理によれば、単連結(円盤と同相)領域の間の正則な同相写像がつねに存在します。今考えている速度場の定義域は、円盤領域というよりはその外側ですが、考えている複素速度が無限遠で収束するという条件を課しているので、無限遠点を追加して反転することで同様の主張を得ておきましょう。つまり、任意の単連結な翼型について、その外側領域同士を結ぶ正則な同相写像があることになります。
これは、二次元翼理論にある種の万能性を保証します。
を考えると、これは
したがって、翼型
円筒翼は対称性が高いため、速度場を簡単に書き下せます。天下りですが、原点の半径
で与えることができます。実際に
となり、円筒翼の接ベクトルに並行になっていて境界条件を満たしています。
これより高次の負冪項は、境界条件の観点からは線形独立になるため他の項をここに加えることはできません。したがって、円筒翼周りの複素速度場はこれで尽きることになります。(円筒翼境界条件を満たさせるという観点からみると、複素速度の
クッタ・ジューコフスキー条件
前節で円筒翼の速度場を与えました。しかし 円筒翼の速度場には、その循環パラメータ
つまり、ここから任意の翼型に写像してその翼型について調べようとしても、もっとも重要な数値が不明なままです。 翼型境界方向の境界条件を決める必要があります。
ところが 完全流体の仮定のもとでは、翼表面付近の流体の振る舞いを記述することができません。 実際には粘性などによって翼の表面付近に薄い渦の層が発生し、これによって翼表面でいくらか流体が「滑る」ことができ....といった感じで翼境界方向の境界条件が決まるはずですが、完全流体は粘性を考慮に入れていない上、今渦なしの流れを考えているため、そうした議論ができません。
しかし実は、完全流体のまま、こうした境界条件を固定する絶妙な経験則があります。これがクッタ・ジューコフスキーの条件です。このポイントは、翼型を「尖らせる」ことにあります。
翼の後端を非常に薄く尖らせた場合、翼の上面と下面からきた流れは「なめらかに合流する」と期待できます(これこそが経験則であり、ここでは証明されません)。もとの円筒翼の速度場の循環項
この滑らかに合流するときの
逆に言えば、翼型後端が鋭く尖っていない場合、クッタ・ジューコフスキーの条件を運用できません。クッタ・ジューコフスキーの条件は、本来完全流体の観点からは議論できない翼境界付近での挙動を、翼境界を「摘んで押しつぶす」ことで、「翼後端付近の流れが滑らかに合流しているか」という単純な規準に落とし込んでしまうという(やや強引な)テクニックです。
この代償として、クッタ・ジューコフスキーの仮定で速度場を決定可能な翼型は、常に鋭く薄い後端をもつことになります。薄いということは強度を稼げないという工学上のデメリットがあります。
ジューコフスキー翼の可視化
クッタ・ジューコフスキーの条件は前節のように言葉で説明されてもピンと来ないと思われます。そこで、具体的にこの条件を運用するとどうなるのか可視化しましょう。
円/翼型の外側領域の間の正則写像をもち、翼型の一点が特異的に尖った形をもつ翼型としてジューコフスキー翼があります。そしてこれを実現する関数もジューコフスキー変換と呼ばれています。これを精査することで、ここまでの話を実際に実現してみましょう。つまり、写像定理で得られる写像がジューコフスキー変換だった場合に、実際にクッタ・ジューコフスキー条件で循環を決定してみます(二次元翼理論としては、一般に一点で尖った翼型とそれを円筒からの写像として与える正則関数があれば、同様の議論ができますが、具体例としてジューコフスキー変換を扱うということです)。ここで、ジューコフスキー変換とは以下です。
これの逆関数は二価です。
解と係数の関係から
が成り立ちます。
半径
ジューコフスキー翼とは、複素平面上
これも図示することでわかるのですが、
に制限しても一般性を失いません。
この条件を満たしているとき、円と反転円はともに原点を含みます。ジューコフスキー変換は明らかに原点で正則でないので、原点を円筒翼で隠す必要がありますが、反転円を円筒翼とみなすことでこれが満足されます。
このとき、パラメータ円の半径は
ジューコフスキー変換は逆関数が二価なので、ここまでに説明した方法で翼周りの速度場を求めるには、実際に考慮する翼型を考慮に入れた上で、逆像を適切に選ぶ必要があります。
これ以上は数式をこねくり回して理解するのは直観が追いつかず大変です。ということで、計算機の力で可視化します。
このあたりの可視化の良いツールがあるのかもしれませんが、平面上の各点で計算を高速にできるのでGLSLを用います。
VScodeのExtensionでShaderToyExtensionを用いると、以下のコードを適当な新規ファイルにコピペ+Preview(cmd or ctl + shift + P
, Show GLSL Preview
)で動作確認ができます。
GLSLでジューコフスキー変換とその逆変換を実装します。まずはパラメータ円と、その反転円と、ジューコフスキー翼がどのような形になるか見てみましょう。
const vec4 c1 = vec4(1.0,0.0,0.0,1.0);
const vec4 c2 = vec4(0.0,1.0,0.0,1.0);
const vec4 c3 = vec4(0.0,1.0,1.0,1.0);
const vec4 c4 = vec4(1.0,0.0,1.0,1.0);
const vec4 c5 = vec4(0.5,0.5,1.0,1.0);
const vec4 c6 = vec4(1.0,1.0,0.0,1.0);
const float a = 2.5;
const float eps = 0.01;
vec2 anotherPair(vec2 pt) {
return vec2(pt.x,-pt.y)*a*a/pow(length(pt),2.0);
}
vec2 reScale(vec2 pt,float scale){
float l = min(iResolution.x,iResolution.y);
return (pt.xy - (iResolution.xy / 2.0)) / (l*scale);
}
bool onCircle(vec2 pt,float r,vec2 center) {
return abs(length(pt-center) - r) < eps;
}
bool inCircle(vec2 pt,float r,vec2 center) {
return length(pt-center) < r;
}
vec2 joukowskiForward(vec2 pt) {
float r = length(pt);
float x = pt.x + ((a*a)*pt.x)/(r*r);
float y = pt.y - ((a*a)*pt.y)/(r*r);
return vec2(x,y);
}
const float PI = 3.14159265359;
vec2 poler(float r, float theta) {
return vec2(r*cos(theta),r*sin(theta));
}
vec2 sqrtZZminusAA4(vec2 pt,float a) {
float r = length(pt);
float d = pow(
pow(r,4.0)
-8.0*pow(a,2.0)*(pt.x*pt.x - pt.y*pt.y)
+16.0*pow(a,4.0)
,0.25);
float zzminusaa4_y = (2.0*pt.x*pt.y);
float zzminusaa4_x = ((pt.x*pt.x - pt.y*pt.y) - (4.0 * pow(a,2.0)));
float angle = atan(zzminusaa4_y/zzminusaa4_x);
bool cos2thetaover = zzminusaa4_x > 0.0;
float angleModified;
if (pt.x > 0.0 && cos2thetaover) {
angleModified = 0.5 * angle;
}
if (pt.y > 0.0 && !cos2thetaover) {
angleModified = 0.5 * (PI+angle);
}
if (pt.y < 0.0 && !cos2thetaover) {
angleModified = 0.5 * (-PI+angle);
}
if (pt.x < 0.0 && pt.y > 0.0 && cos2thetaover) {
angleModified = 0.5 * (2.0*PI+angle);
}
if (pt.x < 0.0 && pt.y < 0.0 && cos2thetaover) {
angleModified = 0.5 * ((-2.0*PI)+angle);
}
return poler(d,angleModified);
}
vec2 joukowskiInv1(vec2 pt) {
return (pt + sqrtZZminusAA4(pt,a))*0.5;
}
vec2 joukowskiInv2(vec2 pt) {
return (pt - sqrtZZminusAA4(pt,a))*0.5;
}
void main(){
float scale = 0.1;
vec2 pt = reScale(gl_FragCoord.xy,scale);
vec2 mouse = reScale(iMouse.xy,scale);
vec2 anotherMouse = anotherPair(mouse);
gl_FragColor = vec4(1.0,1.0,1.0,1.0);
// パラメタ円の反転円
float outer_rate = a / (a - 2.0 * mouse.x);
float outer_radias = length(mouse-vec2(a,0)) * abs(outer_rate);
vec2 outer_center =vec2(a,0.0) + ((mouse-vec2(a,0))* outer_rate);
// 翼の内側の逆写像:ただし、パラメータの実部が正のときだけ動作する。
if (!inCircle(pt,length(mouse-vec2(a,0)),mouse) && (outer_rate<0.0) != inCircle(pt,outer_radias,outer_center)) {
gl_FragColor = c6;
}
// 翼の内側:ただし、パラメータの実部が正のときだけ動作する。
vec2 j1 = joukowskiInv1(pt);
vec2 j2 = joukowskiInv2(pt);
if (length(j1-mouse) > length(mouse-vec2(a,0)) && length(j2-mouse) > length(mouse-vec2(a,0)) ) {
gl_FragColor = c5;
}
// 座標軸
if (abs(pt.x) < eps || abs(pt.y) < eps) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
// 原点のa円
if (onCircle(pt,a,vec2(0,0))) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
// パラメータ円
if (onCircle(pt,length(mouse-vec2(a,0)),mouse)) {
gl_FragColor = c1;
}
// の中心
if (length(pt-mouse)< eps+0.03) {
gl_FragColor = c1;
}
// 反転円
if (onCircle(pt,outer_radias,outer_center)) {
gl_FragColor = c2;
}
// 翼境界1
if (onCircle(joukowskiInv1(pt),length(mouse-vec2(a,0)),mouse)) {
gl_FragColor = c3;
}
// 翼境界2
if (onCircle(joukowskiInv2(pt),length(mouse-vec2(a,0)),mouse)) {
gl_FragColor = c4;
}
}
逆変換の実装がちょっと面倒です。根気よく場合分けをします。ジューコフスキー変換はあくまで翼型を与える関数の一例だといいましたが、逆変換をする必要があることを考えると正則といえあまり複雑な関数は使いたくないですね...ともかく表示しましょう。
コードではExtensionの機能で、マウス座標をパラメータ円の中心
ちょっと太い。
これはイケメン。
ここで、赤い色が、パラメータ円とその中心です。緑は反転円かつ参照する円筒翼であり、ジューコフスキー変換で赤い円と同じ場所に写像されます。黒の円は半径
パラメータ円(赤)を
ジューコフスキー逆変換を調べましょう。ジューコフスキー翼の輪郭はわかりましたが、この翼の内側と外側は写像前のどこに対応するでしょうか?
写像前後の領域を追ってみると、実はジューコフスキー翼の内側の逆像とは、パラメータ円(赤)の反転円(緑)から、元のパラメータ円(赤)を引いた領域 (上図の黄色部分)だとわかります。 上図でいいう青領域と黄領域が1:2で対応しているということです(黄色の三日月(?)領域はジューコフスキー変換によって、半径
このことから、ある点がジューコフスキー翼の内側であることは 逆変換
円筒翼の速度場を使って、一般翼形の流れを求めるために、円筒翼の速度場も可視化しましょう。ところで、速度場の可視化をするには、流れ場そのものよりも、流れ関数
円筒翼の複素速度は
でしたが、これに対応する流れ関数は
と与えることができます。ここで、
これもGLSLで描いてみましょう。GLSLは関数の等高線を表示するのが得意なのでこちらは楽勝です。
const float PI = 3.14159265359;
const float r = 1.0;
const vec2 center = vec2(0.0,0.0);
const float gamma_coeff = 10.0;
float arg(vec2 pt) {
if (pt.x>0.0) {
return atan(pt.y/pt.x);
} else {
return PI - atan(-pt.y/pt.x);
}
}
// 円柱まわりの循環を持つ流れ関数
float flowFunctionAroundCircle(vec2 pt,vec2 center,vec2 b_0,float a,float g) {
float r = length(pt-center);
float u = length(b_0);
float angle = -arg(b_0);
return u*(r-((a*a)/r))*sin(arg(pt-center)-angle)-(g/(PI*2.0))*log(r);
}
bool contour(float div, float width,float v) {
return abs(fract(iTime + v/div) - width*0.5) < (width*0.5);
}
bool inCircle(vec2 pt,vec2 center,float a) {
return length(pt-center) < a;
}
vec2 reScale(vec2 pt,float scale){
float l = min(iResolution.x,iResolution.y);
return (pt.xy - (iResolution.xy / 2.0)) / (l*scale);
}
void main(){
float scale = 0.1;
vec2 mouse = reScale(iMouse.xy,scale)-center;
vec2 mouse_yinv = vec2(mouse.x,-mouse.y);
vec2 center = vec2(0.0,0.0);
vec2 pt = reScale(gl_FragCoord.xy,scale);
// 円の内側
float a = 1.0;
if (inCircle(pt,center,a)) {
gl_FragColor = vec4(0.0,0.0,1.0,1.0);
return;
}
float f = flowFunctionAroundCircle(pt,center,mouse_yinv/length(mouse_yinv),a,-length(mouse)*gamma_coeff);
if (contour(1.0,0.03,f)) {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
} else {
gl_FragColor = vec4(1.0,1.0,1.0,1.0);
}
}
コード上はShaderToyExtensionのuniform機能でアニメーションしていますが、このアニメーションの方向は流れの方向ではありません。赤い線が流れの方向で、その密度が速さです。循環を設定することで、円筒翼の上下の流線密度が変わっています。 密な方が速度が早く、圧力が低いため、圧力差によって揚力が発生します。循環が大きい場合は円筒翼の外側に淀み点が発生します。
さて、循環
ジューコフスキー変換/逆変換と円筒翼の流れ関数が手に入ったので、円筒翼の流れ関数をジューコフスキー翼に流し込みましょう。
各座標について、ジューコフスキー逆変換を実施し、先の規準にしたがって、翼の内側になった場合はそれを描画します。外側になった場合は、反転円の外側を常に選択し、反転円を円筒翼とした流れ関数を返し、等高線を描画します。つまり、円筒翼流れ関数をジューコフスキー逆変換で引き戻します。
// ジューコフスキー翼
// 循環:
const float gamma =-2.0;
// 円筒翼のローランb_0値:迎角と速度
const vec2 b_0 = vec2(1.0,-0.4);
// ジューコフスキー変換の元円盤の中心(xは正で2倍がaを超えないこと)
const vec2 inner_center = vec2(0.3,1.0);
// ジューコフスキー変換と単位円の大きさ
const float a = 3.0;
// 描画スケール
const float scale = 0.05; // 描画サイズを変える。
const vec2 offset = vec2(0.0,0.0); // 描画位置をずらす。
const float countour_div = 1.0; // 大きくすると粗くなる。
// 適当な色
const vec4 c1 = vec4(1.0,0.0,0.0,1.0);
const vec4 c2 = vec4(0.0,1.0,0.0,1.0);
const vec4 c3 = vec4(0.0,1.0,1.0,1.0);
const vec4 c4 = vec4(1.0,0.0,1.0,1.0);
const vec4 c5 = vec4(0.5,0.5,0.5,1.0);
// 等高線を描画する場合の厚み
const float eps = 0.01;
// a^2/z
vec2 anotherPair(vec2 pt) {
return vec2(pt.x,-pt.y)*a*a/pow(length(pt),2.0);
}
vec2 reScale(vec2 pt,float scale){
float l = min(iResolution.x,iResolution.y);
return (pt.xy - (iResolution.xy / 2.0)) / (l*scale);
}
// 円周に乗ってる
bool onCircle(vec2 pt,float r,vec2 center) {
return abs(length(pt-center) - r) < eps;
}
vec2 joukowskiForward(vec2 pt) {
float r = length(pt);
float x = pt.x + ((a*a)*pt.x)/(r*r);
float y = pt.y - ((a*a)*pt.y)/(r*r);
return vec2(x,y);
}
const float PI = 3.14159265359;
vec2 poler(float r, float theta) {
return vec2(r*cos(theta),r*sin(theta));
}
// 複素関数(z^2-4a^2)^0.5
vec2 sqrtZZminusAA4(vec2 pt,float a) {
float r = length(pt);
float d = pow(
pow(r,4.0)
-8.0*pow(a,2.0)*(pt.x*pt.x - pt.y*pt.y)
+16.0*pow(a,4.0)
,0.25);
float zzminusaa4_y = (2.0*pt.x*pt.y);
float zzminusaa4_x = ((pt.x*pt.x - pt.y*pt.y) - (4.0 * pow(a,2.0)));
float angle = atan(zzminusaa4_y/zzminusaa4_x);
bool cos2thetaover = zzminusaa4_x > 0.0;
float angleModified;
if (pt.x > 0.0 && cos2thetaover) {
angleModified = 0.5 * angle;
}
if (pt.y > 0.0 && !cos2thetaover) {
angleModified = 0.5 * (PI+angle);
}
if (pt.y < 0.0 && !cos2thetaover) {
angleModified = 0.5 * (-PI+angle);
}
if (pt.x < 0.0 && pt.y > 0.0 && cos2thetaover) {
angleModified = 0.5 * (2.0*PI+angle);
}
if (pt.x < 0.0 && pt.y < 0.0 && cos2thetaover) {
angleModified = 0.5 * ((-2.0*PI)+angle);
}
return poler(d,angleModified);
}
// a円盤の外側にある方の解。
vec2 joukowskiInv1(vec2 pt) {
return (pt + sqrtZZminusAA4(pt,a))*0.5;
}
// a円盤の内側にある方の解。
vec2 joukowskiInv2(vec2 pt) {
return (pt - sqrtZZminusAA4(pt,a))*0.5;
}
// 偏角
float arg(vec2 pt) {
if (pt.x>0.0) {
return atan(pt.y/pt.x);
} else {
return PI - atan(-pt.y/pt.x);
}
}
// 円柱まわりの循環を持つ流れ関数
float flowFunctionAroundCircle(vec2 pt,vec2 center,vec2 b_0,float circlerad,float g) {
float r = length(pt-center);
float u = length(b_0);
float angle = -arg(b_0);
return u*(r-((circlerad*circlerad)/r))*sin(arg(pt-center)-angle)-(g/(PI*2.0))*log(r);
}
// 等高線判定
bool contour(float div, float width,float v) {
return abs(fract(iTime + v/div) - width*0.5) < (width*0.5);
}
// 円に属するか
bool inCircle(vec2 pt,vec2 center,float a) {
return length(pt-center) < a;
}
void main(){
// vec2 mouse = reScale(iMouse.xy,scale);
float inner_radias = length(inner_center - vec2(a,0.0));
vec2 pt = reScale(gl_FragCoord.xy,scale)-offset;
// 背景と座標軸
gl_FragColor = vec4(1.0,1.0,1.0,1.0);
if (abs(pt.x) < eps*1.5 || abs(pt.y) < eps*1.5) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
}
// 逆変換
vec2 j1 = joukowskiInv1(pt);
vec2 j2 = joukowskiInv2(pt);
// パラメタ円の反転円
float outer_rate = a / (a - 2.0 * inner_center.x);
float outer_radias = inner_radias * outer_rate;
vec2 outer_center =vec2(a,0.0) + ((inner_center-vec2(a,0))* outer_rate);
// 翼の内側判定と翼型塗りつぶし
bool insideFoil = length(j1-outer_center) < outer_radias && length(j2-outer_center) < outer_radias;
if (insideFoil) {
gl_FragColor = c5;
}
// 翼の外側判定と円筒翼流れ関数の参照
float f;
if (!insideFoil) {
// 反転大円を円筒翼として外側にあるものを参照
if (length(j2-outer_center) > outer_radias) {
f = flowFunctionAroundCircle(j2,outer_center,b_0,outer_radias,gamma);
}
if (length(j1-outer_center) > outer_radias) {
f = flowFunctionAroundCircle(j1,outer_center,b_0,outer_radias,gamma);
}
if (contour(countour_div,0.03,f)) {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
}
}
ジューコフスキー翼まわりに流線が描かれます。
コード上の
// 循環:
const float gamma =-2.0;
// 円筒翼のローランb_0値:迎角と速度
const vec2 b_0 = vec2(1.0,-0.4);
// ジューコフスキー変換の元円盤の中心(xは正で2倍がaを超えないこと)
const vec2 inner_center = vec2(0.3,1.0);
を変えることで、循環と無限遠の一様流の向き、パラメータ円の中心を調整します。ここで適当に描画すると、翼の後端で流れが折れ曲がっていることがあります。
そしてここがクッタ・ジューコフスキー条件の出番となります。鋭く尖った翼後端であるにもかかわらず、その近傍で流れがなめらかに合流せず折れ曲がっているのは奇妙ですね? そこで、参照している円筒翼周りの循環である
こんなところでしょうか。
上記のパラメータだと、
パラメータを色々に変えると、しばし極端な翼型とその流れが得られたりします。
// 循環:
const float gamma =-80.9;
// 円筒翼のローランb_0値:迎角と速度
const vec2 b_0 = vec2(1.0,-0.4);
// ジューコフスキー変換の元円盤の中心(xは正で2倍がaを超えないこと)
const vec2 inner_center = vec2(0.3,4.0);
こうした形については、クッタ・ジューコフスキー条件を満足しようとすると、翼後端が右に回り込んでいるため、循環がかなり大きくなります(上記は
追記
上の可視化ではクッタ・ジューコフスキー条件を満たす
ジューコフスキー翼の場合、翼後端は常に
です。ところがジューコフスキー変換の定義から
参照している円筒翼は、今原点にはなく、その大きさは反転円のそれであり、円筒翼中心は反転円の中心です。したがって、ジューコフスキー翼が参照している円筒翼の複素速度は
です。
となります。
そこで、先のコードで
...
// 循環の理論値
float t_gamma() {
float num = 2.0 * PI *a;
float den = a - 2.0 * inner_center.x;
float r = b_0.x * inner_center.y + b_0.y * (inner_center.x-a);
return -(num / den) * 2.0 *r;
}
...
void main() {
...
float gamma = t_gamma();
...
}
とすることで、常にクッタ・ジューコフスキー条件を満足した流線を描画できます。
Discussion