1971 年のある日、ハンソン氏はこう言いました。「今日は数値の平方根を求める方法を教えます。 よりも速い方法長期分割私が教えるべき方法。 一言で教えます。 そしてあなたは決して忘れることはありません。」それから彼は黒板に書きます
推測
そしてそれに下線を引きます。 笑いが収まると、彼は推測を元の数値、平均値、繰り返し値に分割することについて説明しました。 彼はおそらく言及したニュートン・ラフソン。
方程式の根または零を求めるニュートン法では、方程式の導関数を使用してプロセスを高速化します。 10 の平方根は、X^2 == 10 または X^2 – 10 == 0 となる X の値を見つけることに似ています。 X^2 – 10 の導関数は 2X で、X の連続する値を見つけるループの中心は次のとおりです。
Xnew = X - ( (X^2 - 10) / (2X) )
実際に遊んでみると、まさにそれです。推測式を作成し、初期推定値 2 の場合、次のように収束します。
2 – 初期推測
3.5 – x = (x + 10/x)/2 を適用します。
3.17857142857143
3.16231942215088
3.16227766044414
3.16227766016838
3.16227766016838 – 6 回の反復で安定した答えが得られます。
ここでは、デモ用の JSL プログラムを示します。
f = Expr( x ^ 2 - V );
step = Eval Expr( x - Expr( Name Expr( f ) ) / Expr( Derivative( Name Expr( f ), x ) ) );
V = 1e2; // find the sqrt of V
x = -8; // initial guess.
xnext = .; // set up loop
n = 0; // counter
While( 1,
xnext = step;
If( Abs( x - xnext ) < Abs( 1e-14 * x ),
Break()
);
x = xnext;
Write( "\!n", n, " ", x );
n = n + 1;
);
Write( "\!ndone, a root of " || Char( Name Expr( f ) ) || " for V = " || Char( v ) || " is ", x );
最初の行は、V の平方根を見つけるのに役立つ式 f を定義します。 2 行目の step は、1 サイクルのニュートン・ラフソン反復です。 step は while ループ内に表示され、次の X 値に評価されます。 Derivative 関数は単純な多項式でうまく機能し、この例では 2X が得られます。 NameExpr() は評価せずに f から式を取得し、EvalExpr() が Expr() を見つけたときに Expr() は NameExpr() を実行します (式を取得するため)。 ステップは x - f(x)/f'(x) に設定されます。
while(1) ループ内で、ステップが実行され、出力され、カウントされます。 テストでは、現在の X 値と次の X 値の差をチェックします。 X の現在のサイズで値が非常に小さい場合、おそらく値は近づかず、2 つの非常に近い数値の間で変動する可能性があるため、while ループから抜け出します。
0 -10.25
1 -10.0030487804878
2 -10.0000004646115
3 -10
完了すると、V = 100 の場合の x ^ 2 - V の根は -10 になります。
正の平方根と負の平方根があることに注意してください。 開始値によって、収束がどのルートに至るかが決まります。 -8 から始まり -10.25 までオーバーシュートしましたが、その後 -10 に落ち着きました。
(なぜ導関数 f' で割るのでしょうか? 直感的には、線 f の傾きがゼロに近い場合、f の Y 結果を変更するには次の点をさらに遠くに置く必要があり、傾きで割ることでそれが行われます)。
上記の JSL は、不可能なリクエストをうまく処理できません。 sqrt(-100) を見つけるように要求された場合、JSL はループして、見つけることができない複素数を探します。
複素数
JSL スクリプト インデックスで complex を検索すると、2 つの異なる中途半端な例が生成されます。 1 つは JSL クラスに基づいており、もう 1 つは名前空間と行列に基づいています。 マトリックス バージョンの方が堅牢なソリューションに近く、おそらく高速であると思います。 クラス版は魅力的ですが、使用するのに苦労しました (速度、メモリの問題)。 JMP には演算子用のユーザー オーバーロードがないため、どちらも使いやすいものではありません。
この JSL は前の JSL と非常によく似ています。 多かれ少なかれ同じ式である複素数の算術を処理するためのコードの大きな塊があり、sqrt(-100) も処理できるようになりました。
ns = New Namespace("complex", // adapted from scripting index example
{
make = Function( {a, b}, a || b ),
add = Function( {x, y, t=[0 0], u=[0 0], v=[0 0], w=[0 0]}, // an example has 5 terms
if( Is Number( x ),x = x || 0);
if( Is Number( y ),y = y || 0);
x + y + t + u + v + w;
),
sub = Function( {x, y, t=[0 0], u=[0 0], v=[0 0], w=[0 0]}, // an example has 3 terms. this sub,add trick is good enough for a demo.
if( Is Number( x ),x = x || 0);
if( Is Number( y ),y = y || 0);
x - y - t - u - v - w;
),
mul = Function( {x, y},
if( Is Number( x ),x = x || 0);
if( Is Number( y ),y = y || 0);
local:z = J( 1, 2 );
local:z[1] = x[1] * y[1] - x[2] * y[2];
local:z[2] = x[1] * y[2] + x[2] * y[1];
local:z;
),
div = Function( {x, y},
if( Is Number( x ),x = x || 0);
if( Is Number( y ),y = y || 0);
local:z = J( 1, 2 );
local:d = (y[1] ^ 2 + y[2] ^ 2);
local:z[1] = (x[1] * y[1] + x[2] * y[2]) / local:d;
local:z[2] = (x[2] * y[1] - x[1] * y[2]) / local:d;
local:z;
),
// https://math.stackexchange.com/questions/1397437/formula-for-raising-a-complex-number-to-a-power
// If a,b are real, then a+ib=r(cosθ+i*sinθ) where r^2=a^2+b^2 and tanθ=b/a, and (a + i*b)^N = r^N(cos(Nθ) + i*sin(Nθ))
pow = function( {c, n},
if( Is Number( c ),c = c || 0);
local:a = c[1];
local:b = c[2];
local:theta = ATan( local:b, local:a );
local:r = Sqrt( local:a ^ 2 + local:b ^ 2 );
local:r ^ n * (Cos( n * local:theta ) || Sin( n * local:theta ));
),
abs = function( {x},
Sqrt( x[1] ^ 2 + x[2] ^ 2 )
),
toChar = Function( {x,w=20,d=14}, // rounding (to 14 places) is not a good idea if the numbers are small ( 1e-9 will lose lots of precision)
Char( round(x[1],d),w ) || "+" || Char( round(x[2],d),w ) || "i"
),
convertExpression = function( {e},
substitute( Name Expr( e ),
Expr( Add() ), Expr( complex:add() ),
Expr( Subtract() ), Expr( complex:sub() ),
Expr( Multiply() ), Expr( complex:mul() ),
Expr( Divide() ), Expr( complex:div() ),
Expr( Power() ), Expr( complex:pow() )
)
)
}
);上記では、x^2 または x^3 などの pow() 関数 (x は複素数)、abs() 関数を追加しました。これはおそらく、magnitude() と呼ばれるはずですが、abs としての非複素数バージョンと一致します。 - add() と sub() の追加パラメータと、convertExpression() 関数を探します。 理解しやすい pow() 関数について、stackexchange/Michael Hardy に大いに感謝します。
ConvertExpression 関数は、+、-、*、/、および ^ 演算子を使用した JSL 式を、複雑な名前空間関数を呼び出す同等の式に変換します。 JSL の二項演算子は、((a+b)+(c+d)) を二項関数ではない add(a,b,c,d) に変換することに非常に積極的です...引数が 4 つあります。 そのため、complex:add 関数と :sub 関数には追加のオプション引数が存在します。
JSL の次の部分では、将来の開発を予測してループを関数に移動します。
findzero = function( {x},
{xnext, n},
xnext = .; // set up loop
n = 0; // counter
While( n < 200,
xnext = step;
If( complex:Abs( complex:sub( x, xnext ) ) < 1e-14 * complex:abs( x ),
Break()
);
x = xnext;
n = n + 1;
);
If( n == 200, x = [. .] );
Eval List( {x, n} );
);findzero は開始点 x を取得し、反復解 x と反復回数 n を含むリストを返します。
そして関数を呼び出すコード…複素数の場合、公式と導関数は同じです。
f = Expr( x ^ 2 - V );
step = Eval Expr( x - Expr( Name Expr( f ) ) / Expr( Derivative( Name Expr( f ), x ) ) );
step = complex:convertExpression( Name Expr( step ) );
V = complex:make( -100, 0 ); // find the sqrt of V==-100
x = complex:make( 0, 1 ); // initial guess. Does not like 0i for a start value.
{x, n} = findzero( x );
Write( "\!ndone, a root of " || Char( Name Expr( f ) ) || " for V = " || complex:toChar( v, 10, 5 ) || " is ", complex:toChar( x, 10, 5 ) );
Eval( complex:convertExpression( Name Expr( f ) ) );3 行目は、convertExpression 関数を呼び出して、+、-、*、/、^ 演算子を実数バージョンから複素数バージョンに変更します。 V は複素数値に設定され、初期開始点は実数直線から外れた点に設定されます。 完了すると、ログに次のように表示されます
V = -100+0i の x ^ 2 - V の根は 0+10i です。
[0 0]
V=-100 + 0i は、実数 -100 (虚数成分なし) です。0 + 10i は、実数成分のない純粋な虚数の答えです。 10i * 10i は、予想どおり 100*(i^2)、つまり -100 です。 複素根は共役ペアで発生します。ニュートン法は出発点に基づいて 1 つを見つけました。 もう1つは-10iです。
コードを変更して、複素平面内の多数の点の反復回数 n を追跡し、おそらく等高線プロットとしてグラフを作成し、二次関数の代わりに三次関数を使用しましょう。
f = Expr( x ^ 3 - V );
step = Eval Expr( x - Expr( Name Expr( f ) ) / Expr( Derivative( Name Expr( f ), x ) ) );
step = complex:convertExpression( Name Expr( step ) );
V = complex:make( -2, 0 ); // find the sqrt of V==-2
dt = New Table( "complex",New Column( "xre" ),New Column( "yim" ),New Column( "iterations" ),New Column( "basinr" ),New Column( "basini" ));
stepsize=1/32;
for(xreal=-2,xreal<=2,xreal+=stepsize,
for(yimag=-2,yimag<=2,yimag+=stepsize,
x = complex:make( xreal, yimag ); // initial guess. Does not like 0i for a start value.
{x, n} = findzero( x );
dt<<ADDROWS></ADDROWS>同じ根まで反復する複素平面内のすべての点が盆地を形成します。 上記では、グラフ ビルダーは実数と虚数を単一の値に結合してルートを識別します。
このイメージには、3 つの盆地が赤、シアン、ダークブルーで示されています。 すべての赤色の開始点は同じ値に収束します。 ドット サイズが大きくなると、収束するまでに時間がかかります。 灰色のスポットは 200 回の反復後も収束しませんでした。
Three basins. Larger dots where the basins touch take longer to converge.Three basins. Larger dots where the basins touch take longer to converge.Three basins. Larger dots where the basins touch take longer to converge.Three basins. Larger dots where the basins touch take longer to converge.盆地が3つ。 盆地が接触する大きなドットは、収束するまでに時間がかかります。
JSL 複素数実装を使用して画像のサイズを 16K ポイントから 65K ポイントに 2 倍にするには、10 分程度かかります。 4Kx2K ポイントの 10,000 ビットマップを作成したいので、別のアプローチが必要です。
ベオウルフクラスター
あベオウルフクラスター古いコンピューターの山で、ほとんどがディスプレイやキーボードがなく、LAN 上で制御コンピューターに接続されています。 私の場合、制御コンピューターは JSL を使用してクラスターに何をすべきかを指示し、結果を収集します。 Linux ベースのクラスターは、JMP が作成した Python プログラムを実行しています。 JMP を実行している Windows コンピュータは、RunProgram の PLINK を使用して、各コンピュータ上で 1 つ以上の SSH セッションを開き、必要なプログラムとパラメータを提供します。 Python プログラムが実行され、結果が NFS ディスクに保存されて終了します。 JMP は RunProgram の終了を確認すると、結果を収集し、次の RunProgram を開始します。 10,000 RunPrograms を実行すると、5 分間のビデオを作成するのに十分な画像が生成されます。
Ethernet hub in center, below power-strip. Not enough mice and monitors and keyboards, SSH is best.Ethernet hub in center, below power-strip. Not enough mice and monitors and keyboards, SSH is best.Ethernet hub in center, below power-strip. Not enough mice and monitors and keyboards, SSH is best.Ethernet hub in center, below power-strip. Not enough mice and monitors and keyboards, SSH is best.中央の電源タップの下にあるイーサネット ハブ。 マウス、モニター、キーボードが足りないので、SSH が最適です。
Python にはネイティブの複素数データ型があるため、上記の JSL の一部は廃止されます。 JSL は次のようになります。少しすると、文字列として埋め込まれた Python が表示されます。
F = Expr( // f and e parameters are driven by the frame animation time
f6 * xOld ^ e6 + f5 * xOld ^ e5 + f4 * xOld ^ e4 + f3 * xOld ^ e3 + f2 * xOld ^ e2 + f1 * xOld ^ e1 - 1
); // <<<<<<<<<<< supply equation here <<<<<<<<<<<<<<
scale = 2 / 1;
xresolution = 1920 * scale;
yresolution = 1080 * scale;
// keep aspect ratio by chopping shorter dimension...
xcenter = -1;
ycenter = 0;
delta = 3;
ytop = ycenter - delta * yresolution / xresolution;
xleft = xcenter - delta;
xright = xcenter + delta;
ybottom = ycenter + delta * yresolution / xresolution;
上記のビットには、方程式といくつかのビデオ サイズ パラメーターが含まれています。 以下のビットはネットワークについて説明します。 ホストキー、サーバー、CPU、実行中、ギグは、クラスターに関する情報の condat (接続データ) リストへのインデックスです。
// named indexes for condat
hostkey = 1; // from a failed connection, this makes the connection work
server = 2; // the machine names
cpus = 3; // from the lscpu command, below
running = 4; // dynamic: how many are running on this cpu, right now
gigs = 5; // from the free command, below
// the avahi-daemon may need to be stop/start/status sometimes,
// unfortunately that is hard to do except at server keyboard if you can't connect...
デーモンの問題はまだ存在しますが、デーモンが -2 (など) サフィックスを付けてマシンをアドバタイズしていることがわかり、ディスプレイを接続せずに修正できるようになりました。
condat = {
{"4e:c3:8a:3b:e7:d4:bd:0d:85:62:36:40:17:15:b4:a3", "dell790a.local", 4, 0, 22},
{"00:c2:37:09:dc:d7:cf:fb:33:e3:14:e3:d6:fb:39:be", "dell790b.local", 4, 0, 14},
{"54:c8:1a:6a:50:e3:51:02:ed:1b:f8:fa:bd:f4:e4:17", "dell7010a.local", 8, 0, 30},
{"66:0d:ca:3c:1f:11:eb:45:d4:92:d0:0b:ae:01:b8:6b", "dell7010b.local", 8, 0, 30},
{"94:84:2f:a5:56:2d:db:16:b5:80:a9:0d:87:07:ee:53", "dell9010.local", 8, 0, 30},
{"b2:b7:b3:c3:83:63:11:e7:56:e7:c9:c2:e6:fc:f0:c9", "thinkb.local", 8, 0, 30},
{"2b:ca:fc:45:f0:64:cd:ac:70:ce:93:73:e8:84:2d:75", "thinkc.local", 8, 0, 30},
{"62:b7:67:23:8d:86:fe:80:1a:62:ec:d8:41:1b:b4:1b", "thinkd.local", 8, 0, 30},
{"8e:54:ee:b0:25:67:b8:5c:4d:ad:5e:e9:71:87:fd:71", "netflix.local", 2, 0, 30} // host for vm
};以下の JSL は condat リストを調べて、dell9010 のような名前をドット付き IP アドレス (または IPV6 相当) に変換します。これにより、後で検索が失敗するのを防ぎます。クラスターは約 3 日間実行されましたが、妻のデスクトップが DNS ルックアップ サービスを提供している理由がまだわかりません…GetAddrInfo を覚える前は、コメント付きの ping コードを使用していましたが、これは少し不格好です。 私の家は、数年前に比べて、今では IPV6 にとても満足しています。 IPV6 アドレスが角括弧内に含まれる場合があります。
// on my local network, the lookups for xxx.local fail randomly,
// and more often when busy. pre-lookup the dotted ip now.
//
// better way? Get Addr Info( "thinkd.local" )[3][4]
//
for( itry = 1, itry < 10, itry += 1,
nprocs = 0;
MAXCPU = 0;
good = 0;
for( iCondat = 1, iCondat <= N Items( condat ), iCondat += 1,
nprocs += condat[iCondat][cpus]; // nprocs will divide up the work (stepsize)
condat[iCondat][running] = 0;
MAXCPU = Max( MAXCPU, condat[iCondat][cpus] ); // MAXCPU loops needed later
if( Starts With( condat[iCondat][server], "[" ),
good += 1, // else
// txt = Run Program(
// Executable( "PING.EXE"/*path probably not needed*/ ),
// Options( {"-n 1", condat[iCondat][server]} ),
// ReadFunction( "text" )
// );
// ip = Regex( txt, "\[\[([^]]+)\]]\", "\0" ); // typ: [2603:6081:1a02:1200:8973:e7be:605c:f753]
try(
ip = "[" || Get Addr Info( condat[iCondat][server] )[3][4] || "]";
if( !Is Missing( ip ),
Write( "\!n", condat[iCondat][server] || " --> " || ip );
condat[iCondat][server] = ip;
good += 1;
, //
Write( "\!n", condat[iCondat][server] || " lookup failed ", Left( txt, 70 ) )
); //
, // catch
show(exception_msg,iCondat,condat[iCondat][server],Get Addr Info( condat[iCondat][server] ));
);
);
);
if( good == N Items( condat ),
Break(),
Wait( .1 )
);
);
Write( "\!n",good," of ", N Items( condat )," names resolved." );次の部分は資格情報に関するものです。ppk は JMP マシンの秘密鍵です。 そのキーの公開部分はクラスター内の各マシン上にあります。 Plink は、リモート マシンへの SSH 接続に署名するためにそのプライベート ビットを必要とします。 if(0) コードがあるので、すべてのマシンで実験できます。 一日の終わりには、全員にシャットダウン コマンドを送信するかもしれません。 condat リストに、この JSL スクリプトを実行している VirtualBox を実行している Linux マシンが含まれている場合は、shutdown コマンドに注意してください (上記の行 9)。
remoteuser = "c@";
localkey = " -i c:\Users\v1\Desktop\keys\private.ppk ";
// test code examples...
if( 0, // sequential test using command
//command = " \!"python3.6 -m pip install numpy";
//command = " \!"python3.6 -m pip --version";
//command = " \!"python3.6 -m pip install -U pip";
//command = " \!"python3.6 --version";
command = " \!"hostname -A";
command = " \!"top -b -n 2|head -n 15";
// check if the common nfs mount is set up
//command = " \!"ls /mnt/nfsc"; // fstab: closet.local:/mnt/nfs /mnt/nfsc/ nfs defaults 0 0
// cycle the power; -r means restart. for this to work, must be sudo and not need password
//command = " \!"sudo shutdown -r now"; // visudo: c ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown
//command = " \!"sudo shutdown now"; // visudo: c ALL=(ALL) NOPASSWD: /sbin/poweroff, /sbin/reboot, /sbin/shutdown
// info about number of cores
//command = " \!"lscpu | egrep '^(Model name|Thread|CPU\(s\):)'";
// info about memory
//command = " \!"free -h";
for( iCondat = 1, iCondat <= N Items( condat ), iCondat += 1,
result = runprogram(
executable( "plink" ),
options(
localkey ||
/**/ " -hostkey " || condat[iCondat][hostkey] || /* this line hides the host key */
" " || remoteuser || condat[iCondat][server] || command
),
write function( Function( {this}, this << Write( "n\r\n" ) ) ),
readfunction( "blob" )
);
result = Regex( Blob To Char( result ), "(\r\n)+", "\r\n", GLOBALREPLACE );
Write( "\!n\!n", iCondat, " ", condat[iCondat][server], " ", result );
Wait( 0 );
);
Write( "\!n\!ntest done" );
);次の部分は、RunProgram の多数のコピーを同時に実行する方法についてです。 ImgNum はこれらの各連想配列のキーであり、Python プログラムが処理する画像の番号です。 ImgNumToRP は、実行中の RunProgram オブジェクトを保持します。 そのオブジェクトは終了時に imgNumToRPdone に移動されます。 imgNumToResult は Python プログラムのテキスト出力を保持し、imgNumToMachine はマシンの ID、imgNumToLabel は画像のラベルを保持します。 (ラベルは後で適用されます。詳細は以下で説明します。)
imgNumToRP = [=> ]; // holder for active RunProgram objects
imgNumToRPdone = [=> ]; // holder for RunProgram objects that need a moment to terminate
imgNumToRPresult = [=> ""]; // result strings from RunProgram objects
imgNumToMachine = [=> 0]; // which machine is this running on?
imgNumToLabel = [=>""]; // f(x) labels for graphs
nextImageNumberToMakePicture = 0;
// also: keep track of zeros from previous image
unstick 関数は、RunProgram の終了後に最後に ReadFunction を呼び出さない RunProgram を処理します。
unstick = function({},{imagenumber},
for( imagenumber = imgNumToRP << first, !Is Empty( imagenumber ), imagenumber = imgNumToRPnext,
imgNumToRPnext = imgNumToRP << next( imagenumber );
if( (imgNumToRP[imagenumber]) << canread == 0 & (imgNumToRP[imagenumber]) << canwrite == 0 & (imgNumToRP[imagenumber]) << isreadeof == 1,
imgNumToRPresult[imagenumber] = "\!n" || char(imagenumber) || ": " || imgNumToRPresult[imagenumber] || "\!n"; // NoInterleave save this with count and basin
imgNumToRPdone[imagenumber] = imgNumToRP[imagenumber];
imgNumToRP << removeitem( imagenumber );
//if(imgNumToMachine[imagenumber]!=machine,throw("b machine not match"||char(machine)||","||char(imgNumToMachine[imagenumber])));
condat[imgNumToMachine[imagenumber]][running] = condat[imgNumToMachine[imagenumber]][running] - 1;
imgNumToMachine[imagenumber] = 0;
try(
write("\!nunsticking ",imagenumber);
process( imagenumber ); // must be after...
,
show(exception_msg);
);
Break(); // restart with <<FIRST></FIRST>以下の findmachine 関数は、開始された SSH セッションが多すぎないマシンを検索します。 unstick() 関数は、スタックしている 10% のケースを探すために定期的に呼び出されます。
findMachine = function( {},{iCondat,n}, // find a machine that is not filled up
n=0;
while( 1,
for( iCondat = 1, iCondat <= N Items( condat ), iCondat += 1,
if( condat[iCondat][running] < condat[iCondat][cpus],
Return( iCondat )
)
);
if(mod(n+=1,60)==0,
write("wait for machine...");
// look for stuck
unstick();
);
Wait( 1 ); // a full second is fine; the processes run for 10s of seconds
)
);process 関数は、JMP が RunProgram の終了を検出したときに呼び出されます。 Python プログラムは出力の最後に「センチネル」を書き込みます。このコードは、すべてがスムーズに実行されているかどうかの大まかなチェックとしてそれをチェックします。 bindata は、Python プログラムが NFS ディスク上のファーム ディレクトリに生成するバイナリ ファイルです。 Windows と JMP は、NFS ディスクを \\VBOXSVR パスで認識しますが、Linux と Python プログラムは、//mnt パスで NFS ディスクを認識します。 このコードはバイナリ データから画像を生成する予定でしたが、それは後処理ステップに分割されました。 現在は、数式で検出されたゼロの数を報告するだけです。 バイナリ形式は Python コードで説明されています。 JMP はそれを読み取り、ここで 2 回使用されている blobtomatrix をロードします。 コメントを外して必要であれば、最後の再起動はおそらく機能します。コメントを外すのを忘れていたので必要ありませんでした。
// process output from one thread in the farm
process = function( {imagenumber},
{bindata, start, stop, balanceBytes, matdata, countArray,basinArray,zeros},
//show("process",imagenumber);
if( Contains( imgNumToRPresult[imagenumber], "sentinel" ),
// process a file. maybe not this one if it is out of order... FIXME...nextImageNumberToMakePicture
bindata = Load Text File( "\\VBOXSVR\nfsc\farm\binary" || char(imagenumber) || ".generated", blob() );
matdata = blobtomatrix(
bindata,
"uint",
2,
"little",
2
);
countArray = J( yresolution, xresolution, 0 );
basinArray = J( yresolution, xresolution, 0 );
countArray[0] = matdata[1::nrows(countArray)*ncols(countArray), 1];
basinArray[0] = matdata[1::nrows(countArray)*ncols(countArray), 2];
balanceBytes = length(bindata) - 4*nrows(countArray)*ncols(countArray);
zeros = blobtomatrix(blobpeek(bindata,length(bindata)-balanceBytes),"float",8,"little",4);
write("\!nimagenumber=",imagenumber, if(!ismatrix(zeros)," zeros ***"," zeros="||char(nrows(zeros))));
deletefile("\\VBOXSVR\nfsc\farm\testpython"||char(imagenumber)||".py"); // source no longer needed
sentinels += 1;//
, // else
Write( imgNumToRPresult[imagenumber] );
Write( "\!nrelaunch disabled" || char(imagenumber) );
//launch( imagenumber );
)
);launch 関数は別の RunProgram を開始します。 findmachine を使用して利用可能なマシンを待ち、カスタム .py ファイルとカスタム パラメータを Python コマンドに提供します。 .py ファイルはさらに下に作成されます。 以下の 5 行目のスローは健全性チェックです。 JSL は非常に複雑なので、ロジック エラーを監視するために追加のコードを記述する必要があると感じました。
// launch a thread in the farm
launch = function( {imagenumber},{command,machine},
machine = findMachine();
//show("launch",imagenumber,machine);
if(imgNumToMachine[imagenumber]!=0,throw("image number already on machine"||char(imagenumber)||","||char(machine)));
imgNumToMachine[imagenumber] = machine;
condat[machine][running] = condat[machine][running] + 1;
//command = "\[python3 /mnt/nfsc/farm/testpython]\"||char(imagenumber)||"\[.py]\";
command = "\[hostname -A;python3 /mnt/nfsc/farm/testpython]\"||char(imagenumber)||"\[.py]\";
imgNumToRP[imagenumber] = runprogram(
executable( "plink" ),
options(
localkey || /* -v */ " -hostkey " || condat[machine][hostkey] || " " || remoteuser || condat[machine][server] || " " || command || //
" -o " || Char( 0 ) || // offset
" -s " || Char( 1 ) || // step
" -x " || Char( xresolution ) || //
" -y " || Char( yresolution ) || //
" -t " || Char( ytop ) || //
" -l " || Char( xleft ) || //
" -r " || Char( xright ) || //
" -b " || Char( ybottom ) || //
" -f " || "/mnt/nfsc/farm/binary" || char(imagenumber) || ".generated" || //
""
),パラメータ キーワードを使用すると、その引数を読み取り関数で使用できるようになります。 これは、複数の RunProgram を並行して実行し、読み取り関数にそれらがどれであるかを認識させる方法です。
Parameter( evallist({imagenumber,machine}) ),read 関数は出力をキャプチャし、RunProgram が終了したことを検出しようとします (約 90% の確率で成功します)。 このコードの実行時、RunProgram はまだ終了していて実行中であるため、ハンドルは単に削除されるのではなく、imgNumToRPDone に移動されます。 そうすることで綺麗に仕上がります。 以下の Parm は、上記のパラメータ値を受け取ります。 以下に別の健全性チェック (スロー) があります。
readfunction(
function( {this, parm}, {txt, imagenumber,machine},
{imagenumber,machine} = parm ;
//show("readfunc",imagenumber,machine);
txt = "";
//write("readfun("||char(imagenumber)||")... ");
While( this << canread, txt = txt || (this << read) );
if( Length( txt ) > 0,
//show(txt);
imgNumToRPresult[imagenumber] = imgNumToRPresult[imagenumber] || txt
);
if( this << isreadeof & imgNumToRP << Contains( imagenumber ), // this *almost* always works, about 1:100 fail to get the readeof in the readrunction
imgNumToRPresult[imagenumber] = "\!n" || char(imagenumber) || ": " || imgNumToRPresult[imagenumber] || "\!n"; // NoInterleave save this with count and basin
process( imagenumber );
imgNumToRPdone[imagenumber] = imgNumToRP[imagenumber];
imgNumToRP << removeitem( imagenumber );
if(imgNumToMachine[imagenumber]!=machine,throw("a machine not match"||char(machine)||","||char(imgNumToMachine[imagenumber])));
condat[machine][running] = condat[machine][running] - 1;
imgNumToMachine[imagenumber] = 0;
);
)
)
);
(imgNumToRP[imagenumber]) << writeeof;
);makePythonCode 関数は、oneStep 方程式が組み込まれた .py ファイルを生成します。 Python プログラムには、コマンド ラインを解析して画像サイズと出力ファイル名情報を取得する init() 関数が含まれています。 データは、コマンド ライン パラメーターと方程式にベイクされた 2 つの方法で渡されます。 おそらくすべての情報が焼き込まれている可能性がありますが、パラメータをコードに焼き込む理由がない場合 (oneStep 方程式をコードにコンパイルするなど)、すべての RunProgram に対してコードのコピーが 1 つだけ必要になります。コマンドラインパラメータによって完全に制御されました。 Python コードは、複数のコンピューターで 1 つの画像を処理したい場合に、問題の一部を実行できるように記述されています。 後でパーツを再結合するのではなく、完全なイメージを一度に作成する方が、全体的に高速です。
makePythonCode = function({imagenumber,oneStep},{filename,python},
python =
"\[
import cmath, math, sys, getopt
import numpy as np
def main():
offset, step, ixdim, iydim, top, bottom, left, right, filename = init() # NoInterleave offset will be 0 and step will be 1
iymax = iydim - 1
ixmax = ixdim - 1
yrange = top - bottom
xrange = right - left
result = np.zeros( (int(math.ceil((iydim*ixdim-offset)/step)),2),dtype=np.int16) # count, basin # NoInterleave simpler, no change required though
iresult = 0
ix = offset # NoInterleave like iy, now begins at zero
iy = 0
while ix > ixmax: # normalize before loop starts
ix = ix - ixdim
iy = iy + 1
y = bottom + yrange * iy / iymax # after iy is normalized, before loop
roundedComplexToBasinId = {}
basinIdToBasinSize = {} # NoInterleave this might be useful, but needs to be appended to file (now a constant size for main chunk)
nextBasinId = 1 # reserve basin 1 for "did not converge"
while iy <= iymax:
x = left + xrange * ix / ixmax
xOld = complex(x,y) #xOld - (xOld ** 5 - 1) / (5 * xOld ** 4)
count = 0
try: ## catch zero divide, rarely
for irun in range(1,1000): # NoInterleave prev stopped at 100
###xNew = xOld - (xOld ** 5 - 1) / (5 * xOld ** 4)
]\" || // maintain the indent and newlines!次の行はベイクインビットです…OneStep は、2 行上の ### コメントのように、xOld 値とニュートン方程式から xNew を設定する有効な Python ステートメントの JSL 文字列です。
oneStep || "\[
count += 1
delta = abs(xNew - xOld)
dist = abs(xOld)
if delta < 1e-15 * dist:
break
xOld = xNew
xOld = complex( round(xOld.real,10), round(xOld.imag,10) )
if irun >= 999:
basinId = 1 # arbitrary, but available always. did not converge.
else:
try: ## catch new basin discovered, rarely
basinId = roundedComplexToBasinId[xOld]
except KeyError as e:
nextBasinId += 1
roundedComplexToBasinId[xOld] = nextBasinId
basinId = nextBasinId
basinIdToBasinSize[basinId] = 0
### print(offset, count, xOld, basinId)
except ZeroDivisionError as e:
basinId = -1
### print(offset, e)
try:
result[iresult,0] = count
except:
print("oops",ix,iy)
if basinId >= 0:
try:
basinIdToBasinSize[basinId] += 1
except KeyError as e:
basinIdToBasinSize[basinId] = 1 # happens with basinId==1
result[iresult,1] = basinId
iresult += 1
ix = ix + step # advance
### offset = offset + step # needed for return value
while ix > ixmax: # re-normalize
ix = ix - ixdim
iy = iy + 1
y = bottom + yrange * iy / iymaxこれは、整数と倍精度のバイナリ ファイルを書き込む Python コードです。 double は整数の後に続き、JMP が blobToMatrix ですべてを簡単に取得できるようにします。
# output results
fileobj = open(filename, mode='wb') # NoInterleave maybe prepend a descriptor
result.tofile(fileobj) # NoInterleave append after this constant size block
# NoInterleave append roundedComplexToBasinId and basinIdToBasinSize rather than printing them, they should be same dimension
# 4 8-byte doubles: real,imag,basin,size
temp = np.zeros( (len(roundedComplexToBasinId),4),dtype=np.float64)
itemp = 0
for iz in roundedComplexToBasinId.keys():
temp[itemp,0] = iz.real
temp[itemp,1] = iz.imag
temp[itemp,2] = roundedComplexToBasinId[iz]
temp[itemp,3] = basinIdToBasinSize[roundedComplexToBasinId[iz]]
itemp += 1
temp.tofile(fileobj)
fileobj.close()
print("sentinel"); # so JSL can test if this ran to the end...
def init():
offset = 0
step = 1
xdim = 3
ydim = 2
top = -1.0
bottom = 1.0
left = -1.0
right = 1.0
filename = "temp.binary"
options, remainder = getopt.getopt(sys.argv[1:], 's:x:y:o:t:b:l:r:f:')
for opt, arg in options:
if opt == '-o':
offset = int(arg) # offset is the starting point
elif opt == '-s':
step = int(arg) # step to the next point
elif opt == '-x':
xdim = int(arg) # xdim field int size
elif opt == '-y':
ydim = int(arg) # ydim field int size
elif opt == '-t':
top = float(arg) # top of field user coords
elif opt == '-b':
bottom = float(arg) # bot field user coords
elif opt == '-l':
left = float(arg) # left field user coords
elif opt == '-r':
right = float(arg) # right field user coords
elif opt == '-f':
filename = arg # write binary file
print(" xdim=",xdim,
" ydim=",ydim,
" offset=",offset,
" step=",step,
" top=",top,
" bottom=",bottom,
" left=",left,
" right=",right)
return offset, step, xdim, ydim, top, bottom, left, right, filename
if __name__ == "__main__":
main()
]\";Python プログラムは、一意の番号を付けて (Windows が認識している) NFS ディレクトリに保存されます。 返されたファイル名は使用されません。 Linux バージョンのファイル名は起動関数によって作成され、Plink 経由で渡されます。 (今はJSLに戻っています...)
filename = Save Text File( "\\VBOXSVR\nfsc\farm\testpython"||char(imagenumber)||".py", python ); // NoInterleave - unique file name for each picture because the equation changes
return(filename);
);
以下のループは 10,000 フレームを生成しました。
for( imagenumber = 0, imagenumber < 10000, imagenumber += 1, //<<<<< frame loop <<<<<<
write("\!nloop",imagenumber);
// for X^5 - 1 there are 5 roots; 1 real and 4 complex
// the python expression for x(n+1) = x(n) - F / F' is
// xNew = xOld - (xOld ** 5 - 1) / (5 * xOld ** 4)数式と OneStep から Python ステートメントへの変換 - 実際の値をパラメータ F1...F6 および E1...E6 に代入するため、これは複雑に見えます。 最後に隠されているのは、JSL の ^ から Python の ** への変更です。JSL には ** を式として表す方法がないため、式ではなくテキストで行われます。 F1...E6 は式として扱われます。 Sin( imagenumber / 200 ) は、-1 から 1 までのおよそ 10 個の完全な正弦波です。 正弦波は約数が異なるため周波数が異なります。 因数は -2 から 2 までスムーズに移動し、指数は 1...6 のターゲットを中心に +/- 1 ずつスムーズに移動します。 したがって、ある写真から次の写真への方程式はかなり似ていますが、少し予測できないほど変化します。
FF = Derivative( Name Expr( F ), xOld );
// onestep will be inserted into python below
oneStep = substitute( // "xNew = xOld - (xOld ** 5 - 1) / (5 * xOld ** 4)"
char( // "xNew = xOld - (xOld ^ 5 - 1) / (5 * xOld ^ 4)"
substitute( // char() adds the parens around F and FF
Expr( xNew = xOld - F / FF ), // build the statement with symbols, then
Expr( F ), Name Expr( F ), // replace F and FF symbols with the content
Expr( FF ), Name Expr( FF ), // of the function and derivative
Expr( F6 ), round(2 * Sin( imagenumber / (200+5) ) / 2,3),
Expr( F5 ), round(2 * Sin( imagenumber / (200+7) ) / 2,3),
Expr( F4 ), round(2 * Sin( imagenumber / (200+11) ) / 2,3),
Expr( F3 ), round(2 * Sin( imagenumber / (200+13) ) / 2,3),
Expr( F2 ), round(2 * Sin( imagenumber / (200+17) ) / 2,3),
Expr( F1 ), round(2 * Sin( imagenumber / (200+19) ) / 2,3),
Expr( E6 ), round(6.0 + Sin( imagenumber / (200+23) ) / 2,3),
Expr( E5 ), round(5.0 + Sin( imagenumber / (200+29) ) / 2,3),
Expr( E4 ), round(4.0 + Sin( imagenumber / (200+31) ) / 2,3),
Expr( E3 ), round(3.0 + Sin( imagenumber / (200+37) ) / 2,3),
Expr( E2 ), round(2.0 + Sin( imagenumber / (200+41) ) / 2,3),
Expr( E1 ), round(1.0 + Sin( imagenumber / (200+47) ) / 2,3)
) // sub F,FF
), // char
"^", "**"
); // substitute ^ in JSL to ** in pythonimgNumToLabel 連想配列は、後で画像にラベルを付けるために使用できます。 xOld->X... を単純化します。
imgNumToLabel[imagenumber]=substitute(regex(oneStep,"xNew = xOld - \((.*?)\) / ","\1")," ** ","^"," * ","·","xOld","X");
write("\!n",imgNumToLabel[imagenumber]);
//"/mnt/nfsc/farm/binary" || char(imagenumber) || ".generated"既存のファイルのテストは、プログラムを再起動する必要がある場合に処理を高速化するために開発中に使用されました。 最後の実行 (3 日間!) には使用されなかったので、完全に正しく機能しない可能性があります。
if(fileexists("\\VBOXSVR\nfsc\farm\binary" || char(imagenumber) || ".generated"),
write("Exists ","\\VBOXSVR\nfsc\farm\binary" || char(imagenumber) || ".generated");
continue();
);
makePythonCode(imagenumber,oneStep);これは場違いな放棄されたコードのように見えます。
n = 0; // eventually this becomes the offset // NoInterleave offset is always 0
sentinels = 0; // NoInterleave "sentinel" might be all it returns
//write("Launching... ");ここから 10,000 の RunPrograms が始まります。 はるかに上の condat 値を使用すると、50 または 60 が同時に実行されます。
launch( imagenumber );
write("Launched. ")
);まとめ...最後のいくつかの RunPrograms が終了するまで待ちます。Unstick が 1 つまたは 2 つ見つかる可能性があります。
while( N Items( imgNumToRP ) > 0,
Wait( .1 );
unstick();
);
write("\!ndone");ラベルは、後で画像ジェネレーター用に保存されます。 上記の JSL/Python はバイナリ データを既に保存していますが、.png 形式への変換がまだ必要です。
savetextfile("$desktop/imgNumToLabel.txt",char(imgNumToLabel));Rpdone は 10,000 の終了した RunProgram を保持しています。 手放す。
imgNumToRPdone = 0;// clean out // NoInterleave picture generation happens after all count and basin are created
画像ジェネレーターは今すぐ実行することも、後で実行することもできます。
include("pythonFarmDisplay.jsl");これで Beowulf コードは終わりです。 画像ジェネレーターはそれを使用することもできましたが、使用しませんでした。 ビデオでフレーム間で同じ色を使用したいと考えています。 色は引力の盆地を表し、盆地の任意の点が引き付けられるゼロは同じ色になります。 ただし、方程式はフレームごとに少しずつ変化するため、ゼロが動き回ります。 画像ジェネレーターは、前の画像のゼロを次の画像と一致させようとします。 時々ジャンプすることもありますが、十分です。
画像を作成するための JSL は次のとおりです。 まず洗面器のカラーテーブルを作成します。 これらは可能な限り明るい色のバージョンです。反復回数が多いと、後で暗くなります。
colors=
hlscolor(0,L,1)|| // dark red
hlscolor(.05,L,0.8)|| // red
hlscolor(.15,L,0.9)|| // orn
hlscolor(.35,L,1.0)|| // yel
hlscolor(.50,L,1.0)|| // grn
hlscolor(.65,L,1.0)|| // cyn
hlscolor(.80,L,0.7)|| // blu
hlscolor(.95,L,1.0)|| // mag
hlscolor(1.0,L,0.0); // gry
redmap=j(ncols(colors),1,0);
greenmap=j(ncols(colors),1,0);
bluemap=j(ncols(colors),1,0);
for(i=1,i<=ncols(colors),i+=1,
{r,g,b}=colortorgb(colors[i]);
m = max(r,g,b);
redmap[i] = r/m;
greenmap[i] = g/m;
bluemap[i] = b/m;
);以前に保存した画像ラベルを再ロードする必要があり、画像の寸法が一致する必要があります。 このコードは前のものと同じである必要があり、共通パラメータ ファイルに抽出されているはずです。 もう手遅れです!
imgNumToLabel=Include( "$desktop/pythonFarmDisplayLabels.jsl" );
scale = 2 / 1;
xresolution = 1920 * scale;
yresolution = 1080 * scale;
// keep aspect ratio by chopping shorter dimension...
xcenter = -1;
ycenter = 0;
delta = 3;
ytop = ycenter - delta * yresolution / xresolution;
xleft = xcenter - delta;
xright = xcenter + delta;
ybottom = ycenter + delta * yresolution / xresolution;
盆地の色は、わずかに異なる画像/方程式を追跡することになっています。 これは、前の画像のゼロが存在する場所です。
prevzeros = []; // what came before the first one
NFS ディスクが小さすぎました。 ここには 309GB の非圧縮ピクセル情報があります。
//filepat = "\\VBOXSVR\nfsc\farm\binary^imagenumber^.generated";
// manually moved the files (running out of space)...
filepat = "f:\newtonmethod\generated\binary^imagenumber^.generated";
countarray = J( yresolution, xresolution, 0 );
basinarray = J( yresolution, xresolution, 0 );
バイナリ ファイルは 2 つの JSL 行列に解凍されます。1 つはピクセルに必要な反復回数のカウント、もう 1 つはピクセルの領域を識別する小さな整数です。 その後にもう少しバイナリ データ (バランス バイト) があり、盆地番号を複素座標に変換します。複素座標は、ある写真から次の写真に照合されるものです。
For( imagenumber = 1, imagenumber < 10000, imagenumber += 1,
file = Eval Insert( filepat );
If( File Exists( file ),
bindata = Load Text File( file, blob() );
matdata = Blob To Matrix( bindata, "uint", 2, "little", 2 );
countArray[0] = matdata[1 :: yresolution * xresolution, 1];
basinArray[0] = matdata[1 :: yresolution * xresolution, 2];
balanceBytes = Length( bindata ) - 4 * yresolution * xresolution;
zeros = Blob To Matrix( Blob Peek( bindata, Length( bindata ) - balanceBytes ), "float", 8, "little", 4 );
//Show( zeros );
if(zeros[1, 3]==2,
zeros = [9e99 9e99 1 0] |/ zeros; // python code should have put this in
);
// re-assign the basin numbers to line up with prevzeros
If( N Rows( prevzeros ) == 0,
prevzeros = zeros
);
KDTable は最近傍を見つけます。複雑な平面は素晴らしい近隣です。
kdt = KDTable( prevzeros[0, 1 :: 2] ); // 1::2 means the real and imag cols
newbasin = (1 :: N Rows( zeros )) + 10000;
toomany = N Rows( zeros ) > N Rows( prevzeros );
For( i = 1, i <= N Rows( zeros ), i += 1,
If( zeros[i, 3] != i,
Throw( "basin index assumption wrong" )
); // zeros are sorted by basin number
{indx, dist} = kdt << knearestrows( 2, zeros[i, 1 :: 2] );
If( N Cols( dist ) > 1 & dist[1] < dist[2] / 10, // assume 10X closer is a good match
newbasin[i] = indx[1];
kdt << removerows( indx[1] );//
, /*else*/ !toomany, // if there are toomany new zeros, don't assign them this way
newbasin[i] = indx[1];
kdt << removerows( indx[1] );
);
);<
/*
zerobasin newbasin
1 4 change 1s to 4s in basinArray and zeros
2 3 2s to 3s
3 10001 too many case! no basin assigned. becomes 7, below
4 2
5 5
6 6
7 1
*/
unassigned = Loc( newbasin > 10000 );
If( N Rows( unassigned ) > 0,新しい写真には古い写真よりも多くのゼロがあり、それらを最後の盆地に入れます。
newbasin[unassigned] = newbasin[unassigned] - 10000 + N Rows( zeros )
);
basinArray = Shape( newbasin[basinArray], N Rows( basinArray ), N Cols( basinArray ) );
zeros[0, 3] = Shape( newbasin[zeros[0, 3]], N Rows( zeros ), 1 );
ri = Rank Index( zeros[0, 3] );
zeros[0, 0] = zeros[ri, 0]; // zeros are sorted by basin number
ゼロは上でソートされます。行 1 は色 1 を取得します。
prevzeros = zeros;
ビデオを滑らかにするために、カウントの高い画像は破棄されます。
countarray[loc(countarray>200)] = 200; // 150 is typical big count, 999 is not converged
rangecount = Range( countarray );
countarray=countarray-3; // 3 or 4 is typical minimum count, make it bright (0)
countarray[loc(countarray<0)]=0;
GrayArray はカウントからスケールされた闇です
grayArray = (1 - (countArray /*- rangeCount[1]*/) / (200 /*rangeCount[2] - rangeCount[1]*/)) ^ 8;
rangebasin = range(basinarray);
//basinArray = basinArray / (7);
nc=ncols(colors);
basinarray[loc(basinarray>nc)]=ncols(colors); // if these exist they could be mapped to gray, perhaps
basinarray[loc(basinarray<1)]=ncols(colors); // ditto
grey * redmap[basinarray] (など) はスケーリングされた RGB カラーコンポーネントです
red = grayArray :*</img> shape(redmap[basinarray], N Rows( basinArray ), N Cols( basinArray ) );//(1 + Sin( 2 * Pi() * basinArray + (0 * 2 * Pi() / 3) )) / 2;
green = grayArray :*</img> shape(greenmap[basinarray], N Rows( basinArray ), N Cols( basinArray ) );//(1 + Sin( 2 * Pi() * basinArray + (1 * 2 * Pi() / 3) )) / 2;
blue = grayArray :*</img> shape(bluemap[basinarray], N Rows( basinArray ), N Cols( basinArray ) );//(1 + Sin( 2 * Pi() * basinArray + (2 * 2 * Pi() / 3) )) / 2;
0..1 のカラー値のオーバーフロー/アンダーフローの修正…
red[Loc( red < 0 )] = 0;
red[Loc( red > 1 )] = 1;
green[Loc( green < 0 )] = 0;
green[Loc( green > 1 )] = 1;
blue[Loc( blue < 0 )] = 0;
blue[Loc( blue > 1 )] = 1;
+ マーカーはゼロの位置に追加されます…粗雑ではありますが、機能します。 エッジに描画しないでください。行列のインデックス付けでは、インデックスが 0 の行または列全体が実行されます。
crosssize = floor(yresolution / 100);
For( iz = 1, iz <= N Rows( zeros ), iz += 1,
real = Round( xresolution * Interpolate( Num( zeros[iz, 1] ), xleft, 0, xright, 1 ) );
imag = Round( yresolution * Interpolate( Num( zeros[iz, 2] ), ytop, 0, ybottom, 1 ) );
if(crosssize+5 < imag < nrows(red)-crosssize-5 & crosssize+5 < real < ncols(red)-crosssize-5,
red[imag - 1, real - crosssize :: real + crosssize] = 0.5;
red[imag + 1, real - crosssize :: real + crosssize] = 0.5;
red[imag - 2, real - crosssize :: real + crosssize] = 0.5;
red[imag + 2, real - crosssize :: real + crosssize] = 0.5;
red[imag - crosssize :: imag + crosssize, real - 1] = 0.5;
red[imag - crosssize :: imag + crosssize, real + 1] = 0.5;
red[imag - crosssize :: imag + crosssize, real - 2] = 0.5;
red[imag - crosssize :: imag + crosssize, real + 2] = 0.5;
green[imag - 1, real - crosssize :: real + crosssize] = 0.5;
green[imag + 1, real - crosssize :: real + crosssize] = 0.5;
green[imag - 2, real - crosssize :: real + crosssize] = 0.5;
green[imag + 2, real - crosssize :: real + crosssize] = 0.5;
green[imag - crosssize :: imag + crosssize, real - 1] = 0.5;
green[imag - crosssize :: imag + crosssize, real + 1] = 0.5;
green[imag - crosssize :: imag + crosssize, real - 2] = 0.5;
green[imag - crosssize :: imag + crosssize, real + 2] = 0.5;
blue[imag - 1, real - crosssize :: real + crosssize] = 0.5;
blue[imag + 1, real - crosssize :: real + crosssize] = 0.5;
blue[imag - 2, real - crosssize :: real + crosssize] = 0.5;
blue[imag + 2, real - crosssize :: real + crosssize] = 0.5;
blue[imag - crosssize :: imag + crosssize, real - 1] = 0.5;
blue[imag - crosssize :: imag + crosssize, real + 1] = 0.5;
blue[imag - crosssize :: imag + crosssize, real - 2] = 0.5;
blue[imag - crosssize :: imag + crosssize, real + 2] = 0.5;
red[imag, real - crosssize :: real + crosssize] = 1;
red[imag - crosssize :: imag + crosssize, real] = 1;
green[imag, real - crosssize :: real + crosssize] = 1;
green[imag - crosssize :: imag + crosssize, real] = 1;
blue[imag, real - crosssize :: real + crosssize] = 1;
blue[imag - crosssize :: imag + crosssize, real] = 1;
);
);
label = imgNumToLabel[imagenumber];ラベルは下部に追加されます。 ラベルの小さなビットマップを作成し、それを大きな画像にコピーします。
if(1,
//label = char(imagenumber)||" zeros="||char(N Rows( zeros ))||" "||label;
labelpic = textbox(label,setwrap(3000),<<SETFONTSIZE></SETFONTSIZE>
画像はこちらで作成しました。 次に、名前を付けてビデオに残すかどうかを決定します。
img = New Image( "rgb", {red, green, blue} );
if(6<=nrows(zeros)<=8 & 2<=rangebasin[1]<=4 & 6<=rangebasin[2]<=8 & 3<=rangecount[1]<=4 & rangecount[2]<200,
img << saveimage( "f:/newtonmethod/png3/keeper" || Char( 1e6 + imagenumber ) || ".png", "png" );
,
img << saveimage( "f:/newtonmethod/png3/zapped" || Char( 1e6 + imagenumber ) || ".png", "png" );
);
//img<<SCALE></SCALE>それでおしまい! こちらがビデオです。 私は、科学的なバージョンのみを作成することに決めました。科学的なバージョンが発生する箇所では暗いフレームがちらつき、その下には忙しいキャプションが表示されます。 きれいなバージョンでは、よりスムーズでさらに催眠的なものになる可能性があります。 ハードウェアと接続速度があれば、これは 4K ビデオですが、そうでない場合は YouTube が縮小します。
これを投稿している時点で、2020 年も残り 2 時間です。 2021年がとても楽しみです。