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行目のステップは、1サイクルのニュートン-ラフソン反復です。ステップはwhileループに表示され、次のX値に評価されます。 微分関数は単純な多項式でうまく機能し、この例では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スクリプトインデックスでは、複雑なものを検索すると、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を追跡し、おそらく等高線図としてグラフ化し、2次式ではなく3次式を使用してみましょう。
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>同じルートに反復する複素平面内のすべてのポイントが盆地を形成します。 上記のグラフビルダーは、実数値と虚数値を1つの値に結合して、ルートを識別します。
この画像は、赤、シアン、紺の3つの盆地を示しています。 すべての赤い開始点は同じ値に収束します。 ドットサイズが大きいほど、収束に時間がかかります。 灰色のスポットは、200回の反復後に収束しませんでした。
Three basins. Larger dots where the basins touch take longer to converge.3つの盆地。 盆地が接触する大きなドットは、収束するのに時間がかかります。
JSL複素数の実装では、画像のサイズを16Kから65Kポイントに2倍にするのに10分ほどかかります。 4Kx2Kポイントのビットマップを10,000個作成したいので、別のアプローチが必要です。
Beowulfクラスター
A Beowulfクラスターは古いコンピューターの山で、ほとんどがディスプレイやキーボードがありませんが、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.中央のイーサネットハブ、電源タップの下。 マウスとモニターとキーボードが足りないので、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;
上記のビットには、方程式といくつかのビデオサイズパラメータが含まれています。 以下のビットはネットワークについて説明しています。 Hostkey、server、cpus、running、gigsは、クラスターに関する情報の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(etc)サフィックスが付いたマシンをアドバタイズしていることがわかり、ディスプレイを接続せずに修正できます。
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は、条件リストを調べて、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
)
);JMPがRunProgramの終了を検出すると、プロセス関数が呼び出されます。 Pythonプログラムは、出力の最後に「センチネル」を書き込みます。このコードは、すべてがスムーズに実行されていることを大まかにチェックするものとして、それをチェックします。 Bindataは、PythonプログラムがNFSディスクのファームディレクトリに生成するバイナリファイルです。 WindowsとJMPは\\VBOXSVRパスでNFSディスクを認識しますが、LinuxとPythonプログラムは//mntパスでそれを認識します。 このコードはバイナリデータから画像を生成しようとしていましたが、それは後処理ステップに分けられました。 これで、数式で検出されたゼロの数が報告されるだけです。 バイナリ形式は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 );
)
);起動関数は、別の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" || //
""
),parameterキーワードは、その引数をreadfunctionで使用できるようにします。 これは、複数のRunProgramを並列に使用し、読み取り関数にそれらがどれであるかを認識させる方法です。
Parameter( evallist({imagenumber,machine}) ),readfunctionは出力をキャプチャし、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は、上記の###コメントのように、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コードです。 ダブルスは整数の後に続き、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バージョンのファイル名はlaunch関数によって作成され、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のRunProgramが始まります。 上記のcondat値を使用して、50または60が同時に実行されます。
launch( imagenumber );
write("Launched. ")
);まとめ...最後のいくつかのRunProgramが終了するのを待ちます。スティックを外すと、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マトリックスに解凍されます。ピクセルに必要な反復回数のカウントと、ピクセルの盆地を識別する小さな整数です。 その後、流域番号を複素座標にするバイナリデータ(バランスバイト)がもう少しあります。複雑な座標は、ある画像から次の画像に一致するものです。
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] );
);
);<< knearestrowsは、古い画像から新しい画像のゼロに最も近い2つのゼロを選択します。 最初の画像が少なくとも10倍近い場合、または新しい画像に古い画像よりも多くのゼロがない場合、返される最初のゼロは必要なものです。 完了すると、newbasinは、色をより適切に一致させるためにゼロを再配置する方法を説明します。
/*
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
灰色の*redmap[basinarray](etc)は、スケーリングされた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;
+マーカーはゼロの位置に追加されます…大雑把ですが、機能します。 エッジに描画しないでください。マトリックスインデックスは、インデックスがゼロの場合に行または列全体を処理します。
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年を本当に楽しみにしています。