天泣記

2009-07-01 (Wed)

#1

Creating pipelines with subprocess

おぉ。Python の subprocess でパイプを作ると親プロセス側にパイプがオープンされたまま残ってしまうのか。気がついてなかった。(と思ったが、open3 に関するメモを読み直すとそれっぽいことが書いてあった。忘れていただけか。)

試してみる。

% python
Python 2.5.2 (r252:60911, Jan  4 2009, 17:40:26) 
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from subprocess import *
>>> p1 = Popen(["cat", "/usr/share/dict/words"], stdout=PIPE)
>>> p2 = Popen(["head"], stdin=p1.stdout)

A
A's
AOL
AOL's
Aachen
Aachen's
Aaliyah
Aaliyah's
Aaron
>>> p2.wait()
0
>>> p1.wait()
(終わらない)

まぁ、記事に書いてあるとおり手動で close すればいいのだが。

ちなみに、close しなくても、パイプラインの後ろから作れば cat は終了する。なんでかというと、パイプの読み出し側を親が持っているのが問題なので、後ろから作ると、親が持っているのは書き込み側になるからである。でもそうすると今度は読み出し側で EOF にならなくなるけれど。

>>> p2 = Popen(["head"], stdin=PIPE)
>>> p1 = Popen(["cat", "/usr/share/dict/words"], stdout=p2.stdin)

A
A's
AOL
AOL's
Aachen
Aachen's
Aaliyah
Aaliyah's
Aaron
cat: write error: Broken pipe

なお、subprocess も参考にしつつ大幅に拡充した ruby の open3 では残らないので、SIGPIPE で終わる。

% ruby -v -ropen3 -e 'p Open3.pipeline("cat /usr/share/dict/words", "head")'
ruby 1.9.2dev (2009-06-22 trunk 23820) [i686-linux]

A
A's
AOL
AOL's
Aachen
Aachen's
Aaliyah
Aaliyah's
Aaron
[#<Process::Status: pid 11123 SIGPIPE (signal 13)>, #<Process::Status: pid 11126 exit 0>]

ライブラリでちゃんと close できるのは、パイプラインを作りたいという意図を伝えられる API になっているからである。

subprocess の場合は、プリミティブに分解して提供しているため、ライブラリ側では close するタイミングを掴めない。

2009-07-02 (Thu)

#1

R Language Definition (の翻訳)をざっと読む。

やっぱレキシカルスコープだよなぁ

2009-07-03 (Fri)

#1

time.c の、find_time_t をひさしぶりにいじる。

これが何をやる関数かというと、年月日時分秒の値から time_t の値を得る関数である。

これは、夏時間と閏秒を考慮すると、そう簡単ではない。

たとえば、PST8PDT という、アメリカ西海岸のタイムゾーンの去年から今年にかけての夏時間の切り替わりと閏秒のところを図にすると以下のようになる。ちなみに、PST と PDT をあわせたものが PST8PDT である。(ついでに GMT と JST もちょっと描いてある)

ここで PST/PDT の関係にしたがって縦軸の値から横軸の値を求めるというのが find_time_t の機能である。(横軸は UTC な時刻で目盛をふってあるが、ここでは time_t だと考える。)

これをどうやるかというと、mktime をつかうというのも一つの方法であるが、それをやると (閏秒込みの) UTC/GMT に対応できないなどの問題があるので、localtime/gmtime を使う。

localtime/gmtime は図の横軸から縦軸の値を求める関数である。これの逆関数を作ればいい。

具体的にどうやるかと言えば、localtime/gmtime にいろんな値を食わせてみて、縦軸が与えられた値になるような横軸の値を探すわけである。

幸いにして localtime/gmtime は「だいたい」単調なので、二分探索でもなんとか動かせる。(秋に単調でない部分があるが、考えるとその場合でも二分探索でどちらかひとつは見つかることがわかる。)

さて、以前、最初に find_time_t を実装したときは二分探索を使っていたのだが、すぐに補間探索 (interpolation search) を実装して、長年それでやってきた。そうやったのは2001年だから、8年くらいになる。

それでとくに文句も出ていないのだが、補間探索というのはじつはよくないとは思っていた。

というのは、補間探索はうまくいくときには O(log log n) で速いのだが、うまくいかないときには O(n) になってしまうのである。そして、デバッグでステップ実行したときに、どうも長くかかる、という経験もあった。

で、思い立って測ってみると、春の切り替わりの周辺で 90回以上 localtime を呼び出しているケースが存在することがわかった。この環境の time_t は 32bit であり、素朴に二分探索しても 32回で終わるはずで、多すぎである。

で、考えた結果、この関数はほぼ直線である (そして傾きが一定である) ことを積極的に利用することにした。

二分探索と同じく、はさみうちにするのは変わらないが、端の値から、関数が直線であることを仮定して答えを推測し、その値で探索を進める。

端には下端と上端があるので両方でやる。下端から上端まで夏時間の切り替わりも閏秒もなければ一発で見つかるし、ひとつしかなければどちらかで見つかる。

あと、補間探索と同じくひどい目にあうかもしれないので、両端から試したら次は半分に分割して探索する。つまり、上端から推測し、下端から推測し、半分と推測する、というのを繰り返す。3回に 1回は探索範囲が半分になるので、最悪でも O(log n) で済む。

そうやって実装してみると、ずいぶんとマシになって、90回以上かかっていたものも 20回以下で済むようになった。

なお、そうやってもやはり春がいちばん手間がかかる。というのは、春は縦軸の時刻に対して横軸の時刻が存在しないことがあって、そうすると推測は確実に外れて、二分探索になってしまうからである。

time-system.png

time-system.asy:

import graph;

real gmt(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 00:00:00 GMT
  return t;
}

real jp(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  return t + 9*3600;
}

real nov1 = 0*24*3600;
real nov2 = 1*24*3600;
real nov2_1 = 1*24*3600+1*3600;
real nov2_2 = 1*24*3600+2*3600;
real nov2_3 = 1*24*3600+3*3600;
real nov2_4 = 1*24*3600+4*3600;
real nov2_5 = 1*24*3600+5*3600;
real nov2_6 = 1*24*3600+6*3600;
real nov2_7 = 1*24*3600+7*3600;
real nov2_8 = 1*24*3600+8*3600;
real nov2_9 = 1*24*3600+9*3600;
real nov2_10 = 1*24*3600+10*3600;
real nov2_11 = 1*24*3600+11*3600;
real nov2_12 = 1*24*3600+12*3600;
real nov2_20 = 1*24*3600+20*3600;
real nov3 = 2*24*3600;

real dec31 = 0*3600;
real dec31_12 = 12*3600;
real dec31_13 = 13*3600;
real dec31_14 = 14*3600;
real dec31_15 = 15*3600;
real dec31_16 = 16*3600;
real dec31_17 = 17*3600;
real dec31_18 = 18*3600;
real dec31_19 = 19*3600;
real dec31_20 = 20*3600;
real dec31_21 = 21*3600;
real dec31_22 = 22*3600;
real dec31_23 = 23*3600;
real dec31_23_59 = 23*3600 + 59*60;
real dec31_23_59_50 = 23*3600 + 59*60 + 50;
real dec31_23_59_55 = 23*3600 + 59*60 + 55;
real dec31_23_59_56 = 23*3600 + 59*60 + 56;
real dec31_23_59_57 = 23*3600 + 59*60 + 57;
real dec31_23_59_58 = 23*3600 + 59*60 + 58;
real dec31_23_59_59 = 23*3600 + 59*60 + 59;
real dec31_23_59_60 = 23*3600 + 59*60 + 60;
real dec31_24 = 24*3600;
real jan1 = dec31_24 + 1;
real jan1_0_0_1 = jan1+1;
real jan1_0_0_2 = jan1+2;
real jan1_0_0_3 = jan1+3;
real jan1_0_0_4 = jan1+4;
real jan1_0_0_5 = jan1+5;
real jan1_0_0_10 = jan1+10;
real jan1_0_1 = jan1+60;
real jan1_1 = jan1+1*3600;
real jan1_2 = jan1+2*3600;
real jan1_3 = jan1+3*3600;
real jan1_4 = jan1+4*3600;
real jan1_5 = jan1+5*3600;
real jan1_6 = jan1+6*3600;
real jan1_7 = jan1+7*3600;
real jan1_8 = jan1+8*3600;
real jan1_9 = jan1+9*3600;
real jan1_10 = jan1+10*3600;
real jan1_11 = jan1+11*3600;
real jan1_12 = jan1+12*3600;

real mar8 = 0*24*3600;
real mar8_1 = 1*3600;
real mar8_2 = 2*3600;
real mar8_3 = 3*3600;
real mar8_4 = 4*3600;
real mar8_5 = 5*3600;
real mar8_6 = 6*3600;
real mar8_7 = 7*3600;
real mar8_8 = 8*3600;
real mar8_9 = 9*3600;
real mar8_10 = 10*3600;
real mar8_11 = 11*3600;
real mar8_12 = 12*3600;
real mar8_13 = 13*3600;
real mar8_14 = 14*3600;
real mar8_15 = 15*3600;
real mar8_20 = 20*3600;
real mar9 = 1*24*3600;

real nov_us_pacific(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-10-31 17:00:00 -0700 (PDT)
  // 2008-11-02 08:59:59 UTC -> 2008-11-02 01:59:59 -0800 (PDT)
  // 2008-11-02 09:00:00 UTC -> 2008-11-02 01:00:00 -0800 (PST)
  if (t < (24+9)*3600)
    return t - 7*3600;
  else
    return t - 8*3600;
}

real dec_us_pacific(real t) {
  // right/PST8PDT  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
  // right/PST8PDT  Thu Jan  1 00:00:00 2009 UTC = Wed Dec 31 16:00:00 2008 PST isdst=0 gmtoff=-28800
  return t - 8*3600;
}

real mar_us_pacific(real t) {
  // PST8PDT  Sun Mar  8 09:59:59 2009 UTC = Sun Mar  8 01:59:59 2009 PST isdst=0 gmtoff=-28800
  // PST8PDT  Sun Mar  8 10:00:00 2009 UTC = Sun Mar  8 03:00:00 2009 PDT isdst=1 gmtoff=-25200
  //
  // 2009-03-08 00:00:00 UTC -> 2008-03-07 16:00:00 -0800 (PST)
  // 2009-03-08 09:59:59 UTC -> 2009-03-08 01:59:59 -0800 (PST)
  // 2009-03-08 10:00:00 UTC -> 2009-03-08 03:00:00 -0700 (PDT)
  if (t < 10*3600)
    return t - 8*3600;
  else
    return t - 7*3600;
}

string nov_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+1;
  return format("2008-11-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string nov_xticklabel(real x) {
  if (nov1 <= x)
    return nov_datefmt(x) + " UTC";
  else
    return format("$%g$", x);
}

string nov_yticklabel(real y) {
  if (nov1 <= y)
    return nov_datefmt(y);
  else
    return format("$%g$", y);
}

string dec_utc_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < jan1;
  if (x == dec31_24)
    return "2008-12-31 23:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = before_leap_second ? 31 : 1;
  int mon = before_leap_second ? 12 : 1;
  int year = before_leap_second ? 2008 : 2009;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_us_pacific_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < dec_us_pacific(jan1);
  if (x == dec_us_pacific(dec31_24))
    return "2008-12-31 15:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = 31;
  int mon = 12;
  int year = 2008;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_xticklabel(real x) {
  return dec_utc_datefmt(x) + " UTC";
}

string dec_utc_yticklabel(real y) {
  return dec_utc_datefmt(y);
}

string dec_us_pacific_yticklabel(real y) {
  return dec_us_pacific_datefmt(y);
}

string mar_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+8;
  return format("2009-03-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string mar_xticklabel(real x) {
  return mar_datefmt(x) + " UTC";
}

string mar_yticklabel(real y) {
  return mar_datefmt(y);
}

Label xlabel = shift(13mm,-3mm)*rotate(-20)*Label("$%g$");
Label ylabel = shift(0mm,0mm)*rotate(0)*Label("$%g$");

label("Autumn DST Switch", (50,170));
label("Leap Second", (350,170));
label("Spring DST Switch", (650,170));

draw((30,10)--(0,-115), palecyan);
draw((49,10)--(110,-115), palecyan);

draw((351,17)--(300,-90), palered);
draw((353,17)--(410,-90), palered);

draw((634,20)--(600,-60), palecyan);
draw((653,20)--(710,-60), palecyan);

picture nov_pic1;
size(nov_pic1, 10cm);
filldraw(nov_pic1, shift(nov2_7, nov2)*scale(3600*4,3600*3)*unitsquare, palecyan, black);
draw(nov_pic1, graph(gmt, nov2, nov3));
draw(nov_pic1, graph(nov_us_pacific, nov2, nov2+9*3600-1));
draw(nov_pic1, graph(nov_us_pacific, nov2+9*3600, nov3));
draw(nov_pic1, graph(jp, nov2, nov3));
real[] xmajorticks = { nov2, nov2_12, nov3 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = nov2+i*3600;
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(nov_pic1, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_12, nov3 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = nov2+i*3600;
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(nov_pic1, XEquals(nov2), ymin=nov2-11*3600, yticks);
label(nov_pic1, "JST", (nov2_20, jp(nov2_20)), E);
label(nov_pic1, "GMT", (nov2_20, gmt(nov2_20)), E);
label(nov_pic1, "PST", (nov2_20, nov_us_pacific(nov2_20)), E);
label(nov_pic1, "PDT", (nov2_5, nov_us_pacific(nov2_5)));
nov_pic1 = shift(-nov2,-nov2)*nov_pic1;
add(nov_pic1.scale(300,300),(0,0));

picture nov_pic2;
size(nov_pic2, 10cm);
draw(nov_pic2, graph(nov_us_pacific, nov2_7, nov2_9-1));
draw(nov_pic2, graph(nov_us_pacific, nov2_9, nov2_11));
fill(nov_pic2, shift(nov2_9, nov2_1)*scale(200)*unitcircle);
filldraw(nov_pic2, shift(nov2_9, nov2_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { nov2_6, nov2_7, nov2_8, nov2_9, nov2_10, nov2_11 };
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks);
xaxis(nov_pic2, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_1, nov2_2, nov2_3 };
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks);
yaxis(nov_pic2, XEquals(nov2_7), ymin=nov2, yticks);
label(nov_pic2, "PDT", (nov2_8+1800, nov_us_pacific(nov2_8+1800)), NW);
label(nov_pic2, "PST", (nov2_10, nov_us_pacific(nov2_10)), SE);
nov_pic2 = shift(-nov2_7,-nov_us_pacific(nov2_7))*nov_pic2;
add(nov_pic2.scale(300,300),(0,-200));

picture dec_pic1;
size(dec_pic1, 10cm);
draw(dec_pic1, graph(gmt, dec31_12, jan1_12));
draw(dec_pic1, graph(jp, dec31_12, jan1_12));
draw(dec_pic1, graph(dec_us_pacific, dec31_12, jan1_12));
real[] xmajorticks = { dec31_12, jan1, jan1_12};
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = dec31_12+i*3600;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic1, YEquals(dec31_12, false), xticks);
real[] ymajorticks = { dec31_12, jan1, jan1_12};
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = dec31_12+i*3600;
ticks yticks = Ticks(ylabel, dec_utc_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic1, XEquals(dec31_12), ymin=dec31_12-11*3600, yticks);
label(dec_pic1, "JST", (jan1_9, jp(jan1_9)), E);
label(dec_pic1, "GMT", (jan1_9, gmt(jan1_9)), E);
label(dec_pic1, "PST", (jan1_9, dec_us_pacific(jan1_9)), E);
draw(dec_pic1, (dec31_24,dec31_12-11*3600)--(dec31_24,jan1+21*3600), palered);
draw(dec_pic1, (dec31_12,gmt(dec31_24))--(jan1_12,gmt(dec31_24)), palered);
draw(dec_pic1, (dec31_12,jp(dec31_24))--(jan1_12,jp(dec31_24)), palered);
draw(dec_pic1, (dec31_12,dec_us_pacific(dec31_24))--(jan1_12,dec_us_pacific(dec31_24)), palered);
dot(dec_pic1, (dec31_24, jp(dec31_24)), red);
dot(dec_pic1, (dec31_24, gmt(dec31_24)), red);
dot(dec_pic1, (dec31_24, dec_us_pacific(dec31_24)), red);
dec_pic1 = shift(-dec31_12,-dec31_12)*dec_pic1;
add(dec_pic1.scale(300,300),(300,0));

picture dec_pic2;
size(dec_pic2, 10cm);
fill(dec_pic2, shift(dec31_23_59_60, dec_us_pacific(dec31_23_59_58))*scale(1, jan1_0_0_2-dec31_23_59_58)*unitsquare, palered);
fill(dec_pic2, shift(dec31_23_59_58, dec_us_pacific(dec31_23_59_60))*scale(jan1_0_0_2-dec31_23_59_58, 1)*unitsquare, palered);
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_58, jan1_0_0_2));
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_60, jan1), red+2);
real[] xmajorticks = { dec31_23_59_58, dec31_23_59_60, jan1, jan1_0_0_2 };
real[] xminorticks;
for (int i = -2; i <= 2; ++i) xminorticks[xminorticks.length] = dec31_23_59_60+i;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic2, YEquals(dec_us_pacific(dec31_23_59_58), false), xticks);
real[] ymajorticks = { dec_us_pacific(dec31_23_59_58), dec_us_pacific(dec31_23_59_60), dec_us_pacific(jan1), dec_us_pacific(jan1_0_0_2) };
real[] yminorticks;
for (int i = -2; i <= 2; ++i) yminorticks[yminorticks.length] = dec_us_pacific(dec31_23_59_60)+i;
ticks yticks = Ticks(ylabel, dec_us_pacific_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic2, XEquals(dec31_23_59_58), ymin=dec_us_pacific(dec31_23_59_58), yticks);
label(dec_pic2, "leap second", (dec31_23_59_60+0.5, dec_us_pacific(dec31_23_59_60+0.5)), E);
label(dec_pic2, "PST", (jan1_0_0_1, dec_us_pacific(jan1_0_0_1)), E);
dec_pic2 = shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58))*dec_pic2;
add(dec_pic2.scale(300,300),(300,-200));

picture mar_pic1;
size(mar_pic1, 10cm);
filldraw(mar_pic1, shift(mar8_8, mar8)*scale(3600*4,3600*5)*unitsquare, palecyan, black);
draw(mar_pic1, graph(gmt, mar8, mar9));
draw(mar_pic1, graph(jp, mar8, mar9));
draw(mar_pic1, graph(mar_us_pacific, mar8, mar8_10-1));
draw(mar_pic1, graph(mar_us_pacific, mar8_10, mar9));
real[] xmajorticks = { mar8, mar8_12, mar9 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = mar8+i*3600;
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(mar_pic1, YEquals(mar8, false), xticks);
real[] ymajorticks = { mar8, mar8_12, mar9 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = mar8+i*3600;
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(mar_pic1, XEquals(mar8), ymin=mar8-11*3600, yticks);
label(mar_pic1, "JST", (mar8_20, jp(mar8_20)), E);
label(mar_pic1, "GMT", (mar8_20, gmt(mar8_20)), E);
label(mar_pic1, "PDT", (mar8_20, mar_us_pacific(mar8_20)), E);
label(mar_pic1, "PST", (mar8_6, mar_us_pacific(mar8_6)));
mar_pic1 = shift(-mar8,-mar8)*mar_pic1;
add(mar_pic1.scale(300,300),(600,0));

picture mar_pic2;
size(mar_pic2, 10cm);
draw(mar_pic2, graph(mar_us_pacific, mar8_8, mar8_10-1));
draw(mar_pic2, graph(mar_us_pacific, mar8_10, mar8_12));
fill(mar_pic2, shift(mar8_10, mar8_3)*scale(200)*unitcircle);
filldraw(mar_pic2, shift(mar8_10, mar8_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { mar8_8, mar8_9, mar8_10, mar8_11, mar8_12 };
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks);
xaxis(mar_pic2, YEquals(mar8, false), xticks);
real[] ymajorticks = { mar8, mar8_1, mar8_2, mar8_3, mar8_4, mar8_5 };
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks);
yaxis(mar_pic2, XEquals(mar8_8), ymin=mar8, yticks);
label(mar_pic2, "PST", (mar8_9+1800, mar_us_pacific(mar8_9+1800)), NW);
label(mar_pic2, "PDT", (mar8_11, mar_us_pacific(mar8_11)), SE);
mar_pic2 = shift(-mar8_8,-mar_us_pacific(mar8_8))*mar_pic2;
add(mar_pic2.scale(300,300),(600,-200));
#2

Asymptote で複数のグラフを一枚に描いたのだが、ふたつのグラフの関係を書き込むのが難しい。

個々のグラフの座標系で点を定義して、その間に線を引きたいのだが、やりかたがよくわからない。

グローバルな座標系で指定すればもちろん引けるが、指定するのが大変である。

2009-07-04 (Sat)

#1

閏秒は右端に描いたほうが自然か。

time-system2.png

time-system2.asy:

import graph;

real gmt(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 00:00:00 GMT
  return t;
}

real jp(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  return t + 9*3600;
}

real mar9 = 0*24*3600;
real mar9_1 = 1*3600;
real mar9_2 = 2*3600;
real mar9_3 = 3*3600;
real mar9_4 = 4*3600;
real mar9_5 = 5*3600;
real mar9_6 = 6*3600;
real mar9_7 = 7*3600;
real mar9_8 = 8*3600;
real mar9_9 = 9*3600;
real mar9_10 = 10*3600;
real mar9_11 = 11*3600;
real mar9_12 = 12*3600;
real mar9_13 = 13*3600;
real mar9_14 = 14*3600;
real mar9_15 = 15*3600;
real mar9_20 = 20*3600;
real mar10 = 1*24*3600;

real nov1 = 0*24*3600;
real nov2 = 1*24*3600;
real nov2_1 = 1*24*3600+1*3600;
real nov2_2 = 1*24*3600+2*3600;
real nov2_3 = 1*24*3600+3*3600;
real nov2_4 = 1*24*3600+4*3600;
real nov2_5 = 1*24*3600+5*3600;
real nov2_6 = 1*24*3600+6*3600;
real nov2_7 = 1*24*3600+7*3600;
real nov2_8 = 1*24*3600+8*3600;
real nov2_9 = 1*24*3600+9*3600;
real nov2_10 = 1*24*3600+10*3600;
real nov2_11 = 1*24*3600+11*3600;
real nov2_12 = 1*24*3600+12*3600;
real nov2_20 = 1*24*3600+20*3600;
real nov3 = 2*24*3600;

real dec31 = 0*3600;
real dec31_12 = 12*3600;
real dec31_13 = 13*3600;
real dec31_14 = 14*3600;
real dec31_15 = 15*3600;
real dec31_16 = 16*3600;
real dec31_17 = 17*3600;
real dec31_18 = 18*3600;
real dec31_19 = 19*3600;
real dec31_20 = 20*3600;
real dec31_21 = 21*3600;
real dec31_22 = 22*3600;
real dec31_23 = 23*3600;
real dec31_23_59 = 23*3600 + 59*60;
real dec31_23_59_50 = 23*3600 + 59*60 + 50;
real dec31_23_59_55 = 23*3600 + 59*60 + 55;
real dec31_23_59_56 = 23*3600 + 59*60 + 56;
real dec31_23_59_57 = 23*3600 + 59*60 + 57;
real dec31_23_59_58 = 23*3600 + 59*60 + 58;
real dec31_23_59_59 = 23*3600 + 59*60 + 59;
real dec31_23_59_60 = 23*3600 + 59*60 + 60;
real dec31_24 = 24*3600;
real jan1 = dec31_24 + 1;
real jan1_0_0_1 = jan1+1;
real jan1_0_0_2 = jan1+2;
real jan1_0_0_3 = jan1+3;
real jan1_0_0_4 = jan1+4;
real jan1_0_0_5 = jan1+5;
real jan1_0_0_10 = jan1+10;
real jan1_0_1 = jan1+60;
real jan1_1 = jan1+1*3600;
real jan1_2 = jan1+2*3600;
real jan1_3 = jan1+3*3600;
real jan1_4 = jan1+4*3600;
real jan1_5 = jan1+5*3600;
real jan1_6 = jan1+6*3600;
real jan1_7 = jan1+7*3600;
real jan1_8 = jan1+8*3600;
real jan1_9 = jan1+9*3600;
real jan1_10 = jan1+10*3600;
real jan1_11 = jan1+11*3600;
real jan1_12 = jan1+12*3600;

real mar_us_pacific(real t) {
  // PST8PDT  Sun Mar  9 09:59:59 2008 UTC = Sun Mar  9 01:59:59 2008 PST isdst=0 gmtoff=-28800
  // PST8PDT  Sun Mar  9 10:00:00 2008 UTC = Sun Mar  9 03:00:00 2008 PDT isdst=1 gmtoff=-25200
  //
  // 2008-03-09 00:00:00 UTC -> 2008-03-08 16:00:00 -0800 (PST)
  // 2008-03-09 08:00:00 UTC -> 2008-03-09 00:00:00 -0800 (PST)
  // 2008-03-09 09:59:59 UTC -> 2008-03-09 01:59:59 -0800 (PST)
  // 2008-03-09 10:00:00 UTC -> 2008-03-09 03:00:00 -0700 (PDT)
  // 2008-03-10 00:00:00 UTC -> 2008-03-09 17:00:00 -0700 (PDT)
  //
  if (t < 10*3600)
    return t - 8*3600;
  else
    return t - 7*3600;
}

real nov_us_pacific(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-10-31 17:00:00 -0700 (PDT)
  // 2008-11-02 08:59:59 UTC -> 2008-11-02 01:59:59 -0800 (PDT)
  // 2008-11-02 09:00:00 UTC -> 2008-11-02 01:00:00 -0800 (PST)
  if (t < (24+9)*3600)
    return t - 7*3600;
  else
    return t - 8*3600;
}

real dec_us_pacific(real t) {
  // right/PST8PDT  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
  // right/PST8PDT  Thu Jan  1 00:00:00 2009 UTC = Wed Dec 31 16:00:00 2008 PST isdst=0 gmtoff=-28800
  return t - 8*3600;
}

string mar_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+9;
  return format("2008-03-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string mar_xticklabel(real x) {
  return mar_datefmt(x) + " UTC";
}

string mar_yticklabel(real y) {
  return mar_datefmt(y);
}

string nov_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+1;
  return format("2008-11-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string nov_xticklabel(real x) {
  if (nov1 <= x)
    return nov_datefmt(x) + " UTC";
  else
    return format("$%g$", x);
}

string nov_yticklabel(real y) {
  if (nov1 <= y)
    return nov_datefmt(y);
  else
    return format("$%g$", y);
}

string dec_utc_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < jan1;
  if (x == dec31_24)
    return "2008-12-31 23:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = before_leap_second ? 31 : 1;
  int mon = before_leap_second ? 12 : 1;
  int year = before_leap_second ? 2008 : 2009;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_us_pacific_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < dec_us_pacific(jan1);
  if (x == dec_us_pacific(dec31_24))
    return "2008-12-31 15:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = 31;
  int mon = 12;
  int year = 2008;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_xticklabel(real x) {
  return dec_utc_datefmt(x) + " UTC";
}

string dec_utc_yticklabel(real y) {
  return dec_utc_datefmt(y);
}

string dec_us_pacific_yticklabel(real y) {
  return dec_us_pacific_datefmt(y);
}

Label xlabel = shift(13mm,-3mm)*rotate(-20)*Label("$%g$");
Label ylabel = shift(0mm,0mm)*rotate(0)*Label("$%g$");

label("Spring DST Switch", (50,170));
label("Autumn DST Switch", (350,170));
label("Leap Second", (650,170));

draw((34,20)--(00,-60), palecyan);
draw((53,20)--(110,-60), palecyan);

draw((330,10)--(300,-115), palecyan);
draw((349,10)--(410,-115), palecyan);

draw((651,17)--(600,-90), palered);
draw((653,17)--(710,-90), palered);

picture mar_pic1;
size(mar_pic1, 10cm);
filldraw(mar_pic1, shift(mar9_8, mar9)*scale(3600*4,3600*5)*unitsquare, palecyan, black);
draw(mar_pic1, graph(gmt, mar9, mar10));
draw(mar_pic1, graph(jp, mar9, mar10));
draw(mar_pic1, graph(mar_us_pacific, mar9, mar9_10-1));
draw(mar_pic1, graph(mar_us_pacific, mar9_10, mar10));
real[] xmajorticks = { mar9, mar9_12, mar10 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = mar9+i*3600;
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(mar_pic1, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_12, mar10 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = mar9+i*3600;
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(mar_pic1, XEquals(mar9), ymin=mar9-11*3600, yticks);
label(mar_pic1, "JST", (mar9_20, jp(mar9_20)), E);
label(mar_pic1, "GMT", (mar9_20, gmt(mar9_20)), E);
label(mar_pic1, "PDT", (mar9_20, mar_us_pacific(mar9_20)), E);
label(mar_pic1, "PST", (mar9_6, mar_us_pacific(mar9_6)));
mar_pic1 = shift(-mar9,-mar9)*mar_pic1;
add(mar_pic1.scale(300,300),(0,0));

picture mar_pic2;
size(mar_pic2, 10cm);
draw(mar_pic2, graph(mar_us_pacific, mar9_8, mar9_10-1));
draw(mar_pic2, graph(mar_us_pacific, mar9_10, mar9_12));
fill(mar_pic2, shift(mar9_10, mar9_3)*scale(200)*unitcircle);
filldraw(mar_pic2, shift(mar9_10, mar9_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { mar9_8, mar9_9, mar9_10, mar9_11, mar9_12 };
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks);
xaxis(mar_pic2, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_1, mar9_2, mar9_3, mar9_4, mar9_5 };
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks);
yaxis(mar_pic2, XEquals(mar9_8), ymin=mar9, yticks);
label(mar_pic2, "PST", (mar9_9+1800, mar_us_pacific(mar9_9+1800)), NW);
label(mar_pic2, "PDT", (mar9_11, mar_us_pacific(mar9_11)), SE);
mar_pic2 = shift(-mar9_8,-mar_us_pacific(mar9_8))*mar_pic2;
add(mar_pic2.scale(300,300),(0,-200));


picture nov_pic1;
size(nov_pic1, 10cm);
filldraw(nov_pic1, shift(nov2_7, nov2)*scale(3600*4,3600*3)*unitsquare, palecyan, black);
draw(nov_pic1, graph(gmt, nov2, nov3));
draw(nov_pic1, graph(nov_us_pacific, nov2, nov2+9*3600-1));
draw(nov_pic1, graph(nov_us_pacific, nov2+9*3600, nov3));
draw(nov_pic1, graph(jp, nov2, nov3));
real[] xmajorticks = { nov2, nov2_12, nov3 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = nov2+i*3600;
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(nov_pic1, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_12, nov3 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = nov2+i*3600;
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(nov_pic1, XEquals(nov2), ymin=nov2-11*3600, yticks);
label(nov_pic1, "JST", (nov2_20, jp(nov2_20)), E);
label(nov_pic1, "GMT", (nov2_20, gmt(nov2_20)), E);
label(nov_pic1, "PST", (nov2_20, nov_us_pacific(nov2_20)), E);
label(nov_pic1, "PDT", (nov2_5, nov_us_pacific(nov2_5)));
nov_pic1 = shift(-nov2,-nov2)*nov_pic1;
add(nov_pic1.scale(300,300),(300,0));

picture nov_pic2;
size(nov_pic2, 10cm);
draw(nov_pic2, graph(nov_us_pacific, nov2_7, nov2_9-1));
draw(nov_pic2, graph(nov_us_pacific, nov2_9, nov2_11));
fill(nov_pic2, shift(nov2_9, nov2_1)*scale(200)*unitcircle);
filldraw(nov_pic2, shift(nov2_9, nov2_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { nov2_6, nov2_7, nov2_8, nov2_9, nov2_10, nov2_11 };
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks);
xaxis(nov_pic2, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_1, nov2_2, nov2_3 };
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks);
yaxis(nov_pic2, XEquals(nov2_7), ymin=nov2, yticks);
label(nov_pic2, "PDT", (nov2_8+1800, nov_us_pacific(nov2_8+1800)), NW);
label(nov_pic2, "PST", (nov2_10, nov_us_pacific(nov2_10)), SE);
nov_pic2 = shift(-nov2_7,-nov_us_pacific(nov2_7))*nov_pic2;
add(nov_pic2.scale(300,300),(300,-200));

picture dec_pic1;
size(dec_pic1, 10cm);
draw(dec_pic1, graph(gmt, dec31_12, jan1_12));
draw(dec_pic1, graph(jp, dec31_12, jan1_12));
draw(dec_pic1, graph(dec_us_pacific, dec31_12, jan1_12));
real[] xmajorticks = { dec31_12, jan1, jan1_12};
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = dec31_12+i*3600;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic1, YEquals(dec31_12, false), xticks);
real[] ymajorticks = { dec31_12, jan1, jan1_12};
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = dec31_12+i*3600;
ticks yticks = Ticks(ylabel, dec_utc_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic1, XEquals(dec31_12), ymin=dec31_12-11*3600, yticks);
label(dec_pic1, "JST", (jan1_9, jp(jan1_9)), E);
label(dec_pic1, "GMT", (jan1_9, gmt(jan1_9)), E);
label(dec_pic1, "PST", (jan1_9, dec_us_pacific(jan1_9)), E);
draw(dec_pic1, (dec31_24,dec31_12-11*3600)--(dec31_24,jan1+21*3600), palered);
draw(dec_pic1, (dec31_12,gmt(dec31_24))--(jan1_12,gmt(dec31_24)), palered);
draw(dec_pic1, (dec31_12,jp(dec31_24))--(jan1_12,jp(dec31_24)), palered);
draw(dec_pic1, (dec31_12,dec_us_pacific(dec31_24))--(jan1_12,dec_us_pacific(dec31_24)), palered);
dot(dec_pic1, (dec31_24, jp(dec31_24)), red);
dot(dec_pic1, (dec31_24, gmt(dec31_24)), red);
dot(dec_pic1, (dec31_24, dec_us_pacific(dec31_24)), red);
dec_pic1 = shift(-dec31_12,-dec31_12)*dec_pic1;
add(dec_pic1.scale(300,300),(600,0));

picture dec_pic2;
size(dec_pic2, 10cm);
fill(dec_pic2, shift(dec31_23_59_60, dec_us_pacific(dec31_23_59_58))*scale(1, jan1_0_0_2-dec31_23_59_58)*unitsquare, palered);
fill(dec_pic2, shift(dec31_23_59_58, dec_us_pacific(dec31_23_59_60))*scale(jan1_0_0_2-dec31_23_59_58, 1)*unitsquare, palered);
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_58, jan1_0_0_2));
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_60, jan1), red+2);
real[] xmajorticks = { dec31_23_59_58, dec31_23_59_60, jan1, jan1_0_0_2 };
real[] xminorticks;
for (int i = -2; i <= 2; ++i) xminorticks[xminorticks.length] = dec31_23_59_60+i;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic2, YEquals(dec_us_pacific(dec31_23_59_58), false), xticks);
real[] ymajorticks = { dec_us_pacific(dec31_23_59_58), dec_us_pacific(dec31_23_59_60), dec_us_pacific(jan1), dec_us_pacific(jan1_0_0_2) };
real[] yminorticks;
for (int i = -2; i <= 2; ++i) yminorticks[yminorticks.length] = dec_us_pacific(dec31_23_59_60)+i;
ticks yticks = Ticks(ylabel, dec_us_pacific_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic2, XEquals(dec31_23_59_58), ymin=dec_us_pacific(dec31_23_59_58), yticks);
label(dec_pic2, "leap second", (dec31_23_59_60+0.5, dec_us_pacific(dec31_23_59_60+0.5)), E);
label(dec_pic2, "PST", (jan1_0_0_1, dec_us_pacific(jan1_0_0_1)), E);
dec_pic2 = shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58))*dec_pic2;
add(dec_pic2.scale(300,300),(600,-200));
#2

いろいろ調べた結果、unitsize を使ってスケール変換を固定してしまえば、グラフの座標系からグローバルな座標系への変換を計算できるようだ。変換が判明すれば、ふたつのグラフの間に線を引くのも簡単にできる。

最初に固定するとき、pic.fit() だったらどういうスケール変換をするかを pic.calculateTransform() で調べて、それにあわせて設定すると、うまく出ているグラフを壊さなくて済む。

time-system3.png

time-system3.asy:

import graph;

real gmt(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 00:00:00 GMT
  return t;
}

real jp(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  return t + 9*3600;
}

real mar9 = 0*24*3600;
real mar9_1 = 1*3600;
real mar9_2 = 2*3600;
real mar9_3 = 3*3600;
real mar9_4 = 4*3600;
real mar9_5 = 5*3600;
real mar9_6 = 6*3600;
real mar9_7 = 7*3600;
real mar9_8 = 8*3600;
real mar9_9 = 9*3600;
real mar9_10 = 10*3600;
real mar9_11 = 11*3600;
real mar9_12 = 12*3600;
real mar9_13 = 13*3600;
real mar9_14 = 14*3600;
real mar9_15 = 15*3600;
real mar9_20 = 20*3600;
real mar10 = 1*24*3600;

real nov1 = 0*24*3600;
real nov2 = 1*24*3600;
real nov2_1 = 1*24*3600+1*3600;
real nov2_2 = 1*24*3600+2*3600;
real nov2_3 = 1*24*3600+3*3600;
real nov2_4 = 1*24*3600+4*3600;
real nov2_5 = 1*24*3600+5*3600;
real nov2_6 = 1*24*3600+6*3600;
real nov2_7 = 1*24*3600+7*3600;
real nov2_8 = 1*24*3600+8*3600;
real nov2_9 = 1*24*3600+9*3600;
real nov2_10 = 1*24*3600+10*3600;
real nov2_11 = 1*24*3600+11*3600;
real nov2_12 = 1*24*3600+12*3600;
real nov2_20 = 1*24*3600+20*3600;
real nov3 = 2*24*3600;

real dec31 = 0*3600;
real dec31_12 = 12*3600;
real dec31_13 = 13*3600;
real dec31_14 = 14*3600;
real dec31_15 = 15*3600;
real dec31_16 = 16*3600;
real dec31_17 = 17*3600;
real dec31_18 = 18*3600;
real dec31_19 = 19*3600;
real dec31_20 = 20*3600;
real dec31_21 = 21*3600;
real dec31_22 = 22*3600;
real dec31_23 = 23*3600;
real dec31_23_59 = 23*3600 + 59*60;
real dec31_23_59_50 = 23*3600 + 59*60 + 50;
real dec31_23_59_55 = 23*3600 + 59*60 + 55;
real dec31_23_59_56 = 23*3600 + 59*60 + 56;
real dec31_23_59_57 = 23*3600 + 59*60 + 57;
real dec31_23_59_58 = 23*3600 + 59*60 + 58;
real dec31_23_59_59 = 23*3600 + 59*60 + 59;
real dec31_23_59_60 = 23*3600 + 59*60 + 60;
real dec31_24 = 24*3600;
real jan1 = dec31_24 + 1;
real jan1_0_0_1 = jan1+1;
real jan1_0_0_2 = jan1+2;
real jan1_0_0_3 = jan1+3;
real jan1_0_0_4 = jan1+4;
real jan1_0_0_5 = jan1+5;
real jan1_0_0_10 = jan1+10;
real jan1_0_1 = jan1+60;
real jan1_1 = jan1+1*3600;
real jan1_2 = jan1+2*3600;
real jan1_3 = jan1+3*3600;
real jan1_4 = jan1+4*3600;
real jan1_5 = jan1+5*3600;
real jan1_6 = jan1+6*3600;
real jan1_7 = jan1+7*3600;
real jan1_8 = jan1+8*3600;
real jan1_9 = jan1+9*3600;
real jan1_10 = jan1+10*3600;
real jan1_11 = jan1+11*3600;
real jan1_12 = jan1+12*3600;

real mar_us_pacific(real t) {
  // PST8PDT  Sun Mar  9 09:59:59 2008 UTC = Sun Mar  9 01:59:59 2008 PST isdst=0 gmtoff=-28800
  // PST8PDT  Sun Mar  9 10:00:00 2008 UTC = Sun Mar  9 03:00:00 2008 PDT isdst=1 gmtoff=-25200
  //
  // 2008-03-09 00:00:00 UTC -> 2008-03-08 16:00:00 -0800 (PST)
  // 2008-03-09 08:00:00 UTC -> 2008-03-09 00:00:00 -0800 (PST)
  // 2008-03-09 09:59:59 UTC -> 2008-03-09 01:59:59 -0800 (PST)
  // 2008-03-09 10:00:00 UTC -> 2008-03-09 03:00:00 -0700 (PDT)
  // 2008-03-10 00:00:00 UTC -> 2008-03-09 17:00:00 -0700 (PDT)
  //
  if (t < 10*3600)
    return t - 8*3600;
  else
    return t - 7*3600;
}

real nov_us_pacific(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-10-31 17:00:00 -0700 (PDT)
  // 2008-11-02 08:59:59 UTC -> 2008-11-02 01:59:59 -0800 (PDT)
  // 2008-11-02 09:00:00 UTC -> 2008-11-02 01:00:00 -0800 (PST)
  if (t < (24+9)*3600)
    return t - 7*3600;
  else
    return t - 8*3600;
}

real dec_us_pacific(real t) {
  // right/PST8PDT  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
  // right/PST8PDT  Thu Jan  1 00:00:00 2009 UTC = Wed Dec 31 16:00:00 2008 PST isdst=0 gmtoff=-28800
  return t - 8*3600;
}

string mar_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+9;
  return format("2008-03-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string mar_xticklabel(real x) {
  return mar_datefmt(x) + " UTC";
}

string mar_yticklabel(real y) {
  return mar_datefmt(y);
}

string nov_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+1;
  return format("2008-11-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string nov_xticklabel(real x) {
  if (nov1 <= x)
    return nov_datefmt(x) + " UTC";
  else
    return format("$%g$", x);
}

string nov_yticklabel(real y) {
  if (nov1 <= y)
    return nov_datefmt(y);
  else
    return format("$%g$", y);
}

string dec_utc_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < jan1;
  if (x == dec31_24)
    return "2008-12-31 23:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = before_leap_second ? 31 : 1;
  int mon = before_leap_second ? 12 : 1;
  int year = before_leap_second ? 2008 : 2009;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_us_pacific_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < dec_us_pacific(jan1);
  if (x == dec_us_pacific(dec31_24))
    return "2008-12-31 15:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = 31;
  int mon = 12;
  int year = 2008;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_xticklabel(real x) {
  return dec_utc_datefmt(x) + " UTC";
}

string dec_utc_yticklabel(real y) {
  return dec_utc_datefmt(y);
}

string dec_us_pacific_yticklabel(real y) {
  return dec_us_pacific_datefmt(y);
}

Label xlabel = shift(8mm,-3mm)*rotate(-30)*Label("$%g$");
Label ylabel = shift(0mm,0.3mm)*rotate(0)*Label("$%g$");

label("Spring DST Switch", (50,170));
label("Autumn DST Switch", (350,170));
label("Leap Second", (650,170));

real usize1 = 0.0012;
real usize2 = 0.008;
real usize3 = 24;

pair mar_orig1 = (0,0);
pair mar_orig2 = (0,-230);
pair nov_orig1 = (300,0);
pair nov_orig2 = (300,-230);
pair dec_orig1 = (600,0);
pair dec_orig2 = (600,-230);

transform mar_trans1 = shift(mar_orig1)*scale(usize1)*shift(-mar9,-mar9);
transform mar_trans2 = shift(mar_orig2)*scale(usize2)*shift(-mar9_8,-mar_us_pacific(mar9_8));

transform nov_trans1 = shift(nov_orig1)*scale(usize1)*shift(-nov2,-nov2);
transform nov_trans2 = shift(nov_orig2)*scale(usize2)*shift(-nov2_7,-nov_us_pacific(nov2_7));

transform dec_trans1 = shift(dec_orig1)*scale(usize1)*shift(-dec31_12,-dec31_12);
transform dec_trans2 = shift(dec_orig2)*scale(usize3)*shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58));

draw(mar_trans1*(mar9_8,mar9_5)--mar_trans2*(mar9_8,mar9_5), palecyan);
draw(mar_trans1*(mar9_12,mar9_5)--mar_trans2*(mar9_12,mar9_5), palecyan);

draw(nov_trans1*(nov2_7,nov2_3)--nov_trans2*(nov2_7,nov2_3), palecyan);
draw(nov_trans1*(nov2_11,nov2_3)--nov_trans2*(nov2_11,nov2_3), palecyan);

draw(dec_trans1*(dec31_23_59_58,dec_us_pacific(jan1_0_0_2))--dec_trans2*(dec31_23_59_58,dec_us_pacific(jan1_0_0_2)), palered);
draw(dec_trans1*(jan1_0_0_2,dec_us_pacific(jan1_0_0_2))--dec_trans2*(jan1_0_0_2,dec_us_pacific(jan1_0_0_2)), palered);

picture mar_pic1;
unitsize(mar_pic1, usize1);
filldraw(mar_pic1, shift(mar9_8, mar9)*scale(3600*4,3600*5)*unitsquare, palecyan, black);
draw(mar_pic1, graph(gmt, mar9, mar10));
draw(mar_pic1, graph(jp, mar9, mar10));
draw(mar_pic1, graph(mar_us_pacific, mar9, mar9_10-1));
draw(mar_pic1, graph(mar_us_pacific, mar9_10, mar10));
real[] xmajorticks = { mar9, mar9_12, mar10 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = mar9+i*3600;
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(mar_pic1, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_12, mar10 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = mar9+i*3600;
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(mar_pic1, XEquals(mar9), ymin=mar9-11*3600, yticks);
label(mar_pic1, "JST", (mar9_20, jp(mar9_20)), E);
label(mar_pic1, "GMT", (mar9_20, gmt(mar9_20)), E);
label(mar_pic1, "PDT", (mar9_20, mar_us_pacific(mar9_20)), E);
label(mar_pic1, "PST", (mar9_6, mar_us_pacific(mar9_6)));
mar_pic1 = shift(-mar9,-mar9)*mar_pic1;
add(mar_pic1.fit(),mar_orig1);

picture mar_pic2;
unitsize(mar_pic2, usize2);
draw(mar_pic2, graph(mar_us_pacific, mar9_8, mar9_10-1));
draw(mar_pic2, graph(mar_us_pacific, mar9_10, mar9_12));
fill(mar_pic2, shift(mar9_10, mar9_3)*scale(200)*unitcircle);
filldraw(mar_pic2, shift(mar9_10, mar9_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { mar9_8, mar9_9, mar9_10, mar9_11, mar9_12 };
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks);
xaxis(mar_pic2, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_1, mar9_2, mar9_3, mar9_4, mar9_5 };
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks);
yaxis(mar_pic2, XEquals(mar9_8), ymin=mar9, yticks);
label(mar_pic2, "PST", (mar9_9+1800, mar_us_pacific(mar9_9+1800)), NW);
label(mar_pic2, "PDT", (mar9_11, mar_us_pacific(mar9_11)), SE);
mar_pic2 = shift(-mar9_8,-mar_us_pacific(mar9_8))*mar_pic2;
add(mar_pic2.fit(),mar_orig2);


picture nov_pic1;
unitsize(nov_pic1, usize1);
filldraw(nov_pic1, shift(nov2_7, nov2)*scale(3600*4,3600*3)*unitsquare, palecyan, black);
draw(nov_pic1, graph(gmt, nov2, nov3));
draw(nov_pic1, graph(nov_us_pacific, nov2, nov2+9*3600-1));
draw(nov_pic1, graph(nov_us_pacific, nov2+9*3600, nov3));
draw(nov_pic1, graph(jp, nov2, nov3));
real[] xmajorticks = { nov2, nov2_12, nov3 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = nov2+i*3600;
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(nov_pic1, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_12, nov3 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = nov2+i*3600;
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(nov_pic1, XEquals(nov2), ymin=nov2-11*3600, yticks);
label(nov_pic1, "JST", (nov2_20, jp(nov2_20)), E);
label(nov_pic1, "GMT", (nov2_20, gmt(nov2_20)), E);
label(nov_pic1, "PST", (nov2_20, nov_us_pacific(nov2_20)), E);
label(nov_pic1, "PDT", (nov2_5, nov_us_pacific(nov2_5)));
nov_pic1 = shift(-nov2,-nov2)*nov_pic1;
add(nov_pic1.fit(),nov_orig1);

picture nov_pic2;
unitsize(nov_pic2, usize2);
draw(nov_pic2, graph(nov_us_pacific, nov2_7, nov2_9-1));
draw(nov_pic2, graph(nov_us_pacific, nov2_9, nov2_11));
fill(nov_pic2, shift(nov2_9, nov2_1)*scale(200)*unitcircle);
filldraw(nov_pic2, shift(nov2_9, nov2_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { nov2_6, nov2_7, nov2_8, nov2_9, nov2_10, nov2_11 };
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks);
xaxis(nov_pic2, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_1, nov2_2, nov2_3 };
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks);
yaxis(nov_pic2, XEquals(nov2_7), ymin=nov2, yticks);
label(nov_pic2, "PDT", (nov2_8+1800, nov_us_pacific(nov2_8+1800)), NW);
label(nov_pic2, "PST", (nov2_10, nov_us_pacific(nov2_10)), SE);
nov_pic2 = shift(-nov2_7,-nov_us_pacific(nov2_7))*nov_pic2;
add(nov_pic2.fit(),nov_orig2);

picture dec_pic1;
unitsize(dec_pic1, usize1);
draw(dec_pic1, graph(gmt, dec31_12, jan1_12));
draw(dec_pic1, graph(jp, dec31_12, jan1_12));
draw(dec_pic1, graph(dec_us_pacific, dec31_12, jan1_12));
real[] xmajorticks = { dec31_12, jan1, jan1_12};
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = dec31_12+i*3600;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic1, YEquals(dec31_12, false), xticks);
real[] ymajorticks = { dec31_12, jan1, jan1_12};
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = dec31_12+i*3600;
ticks yticks = Ticks(ylabel, dec_utc_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic1, XEquals(dec31_12), ymin=dec31_12-11*3600, yticks);
label(dec_pic1, "JST", (jan1_9, jp(jan1_9)), E);
label(dec_pic1, "GMT", (jan1_9, gmt(jan1_9)), E);
label(dec_pic1, "PST", (jan1_9, dec_us_pacific(jan1_9)), E);
draw(dec_pic1, (dec31_24,dec31_12-11*3600)--(dec31_24,jan1+21*3600), palered);
draw(dec_pic1, (dec31_12,gmt(dec31_24))--(jan1_12,gmt(dec31_24)), palered);
draw(dec_pic1, (dec31_12,jp(dec31_24))--(jan1_12,jp(dec31_24)), palered);
draw(dec_pic1, (dec31_12,dec_us_pacific(dec31_24))--(jan1_12,dec_us_pacific(dec31_24)), palered);
dot(dec_pic1, (dec31_24, jp(dec31_24)), red);
dot(dec_pic1, (dec31_24, gmt(dec31_24)), red);
dot(dec_pic1, (dec31_24, dec_us_pacific(dec31_24)), red);
dec_pic1 = shift(-dec31_12,-dec31_12)*dec_pic1;
add(dec_pic1.fit(),dec_orig1);

picture dec_pic2;
unitsize(dec_pic2, usize3);
fill(dec_pic2, shift(dec31_23_59_60, dec_us_pacific(dec31_23_59_58))*scale(1, jan1_0_0_2-dec31_23_59_58)*unitsquare, palered);
fill(dec_pic2, shift(dec31_23_59_58, dec_us_pacific(dec31_23_59_60))*scale(jan1_0_0_2-dec31_23_59_58, 1)*unitsquare, palered);
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_58, jan1_0_0_2));
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_60, jan1), red+2);
real[] xmajorticks = { dec31_23_59_58, dec31_23_59_60, jan1, jan1_0_0_2 };
real[] xminorticks;
for (int i = -2; i <= 2; ++i) xminorticks[xminorticks.length] = dec31_23_59_60+i;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic2, YEquals(dec_us_pacific(dec31_23_59_58), false), xticks);
real[] ymajorticks = { dec_us_pacific(dec31_23_59_58), dec_us_pacific(dec31_23_59_60), dec_us_pacific(jan1), dec_us_pacific(jan1_0_0_2) };
real[] yminorticks;
for (int i = -2; i <= 2; ++i) yminorticks[yminorticks.length] = dec_us_pacific(dec31_23_59_60)+i;
ticks yticks = Ticks(ylabel, dec_us_pacific_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic2, XEquals(dec31_23_59_58), ymin=dec_us_pacific(dec31_23_59_58), yticks);
label(dec_pic2, "leap second", (dec31_23_59_60+0.5, dec_us_pacific(dec31_23_59_60+0.5)), E);
label(dec_pic2, "PST", (jan1_0_0_1, dec_us_pacific(jan1_0_0_1)), E);
dec_pic2 = shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58))*dec_pic2;
//write(dec_pic2.calculateTransform());
add(dec_pic2.fit(),dec_orig2);
#3

こう並べると頭の中のイメージに合う。

time-system4.png

time-system4.asy:

import graph;

real hour=3600;
real min=60;

real gmt(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 00:00:00 GMT
  return t;
}

real jp(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  return t + 9hour;
}

real mar9 = 0;
real mar9_1 = 1hour;
real mar9_2 = 2hour;
real mar9_3 = 3hour;
real mar9_4 = 4hour;
real mar9_5 = 5hour;
real mar9_6 = 6hour;
real mar9_7 = 7hour;
real mar9_8 = 8hour;
real mar9_9 = 9hour;
real mar9_10 = 10hour;
real mar9_11 = 11hour;
real mar9_12 = 12hour;
real mar9_13 = 13hour;
real mar9_14 = 14hour;
real mar9_15 = 15hour;
real mar9_20 = 20hour;
real mar10 = 24hour;

real nov2 = 0;
real nov2_1 = 1hour;
real nov2_2 = 2hour;
real nov2_3 = 3hour;
real nov2_4 = 4hour;
real nov2_5 = 5hour;
real nov2_6 = 6hour;
real nov2_7 = 7hour;
real nov2_8 = 8hour;
real nov2_9 = 9hour;
real nov2_10 = 10hour;
real nov2_11 = 11hour;
real nov2_12 = 12hour;
real nov2_20 = 20hour;
real nov3 = 24hour;

real dec31 = 0hour;
real dec31_12 = 12hour;
real dec31_13 = 13hour;
real dec31_14 = 14hour;
real dec31_15 = 15hour;
real dec31_16 = 16hour;
real dec31_17 = 17hour;
real dec31_18 = 18hour;
real dec31_19 = 19hour;
real dec31_20 = 20hour;
real dec31_21 = 21hour;
real dec31_22 = 22hour;
real dec31_23 = 23hour;
real dec31_23_59 = 23hour + 59min;
real dec31_23_59_50 = 23hour + 59min + 50;
real dec31_23_59_55 = 23hour + 59min + 55;
real dec31_23_59_56 = 23hour + 59min + 56;
real dec31_23_59_57 = 23hour + 59min + 57;
real dec31_23_59_58 = 23hour + 59min + 58;
real dec31_23_59_59 = 23hour + 59min + 59;
real dec31_23_59_60 = 23hour + 59min + 60;
real dec31_24 = 24hour;
real jan1 = dec31_24 + 1;
real jan1_0_0_1 = jan1+1;
real jan1_0_0_2 = jan1+2;
real jan1_0_0_3 = jan1+3;
real jan1_0_0_4 = jan1+4;
real jan1_0_0_5 = jan1+5;
real jan1_0_0_10 = jan1+10;
real jan1_0_1 = jan1+60;
real jan1_1 = jan1+1hour;
real jan1_2 = jan1+2hour;
real jan1_3 = jan1+3hour;
real jan1_4 = jan1+4hour;
real jan1_5 = jan1+5hour;
real jan1_6 = jan1+6hour;
real jan1_7 = jan1+7hour;
real jan1_8 = jan1+8hour;
real jan1_9 = jan1+9hour;
real jan1_10 = jan1+10hour;
real jan1_11 = jan1+11hour;
real jan1_12 = jan1+12hour;

real mar_us_pacific(real t) {
  // PST8PDT  Sun Mar  9 09:59:59 2008 UTC = Sun Mar  9 01:59:59 2008 PST isdst=0 gmtoff=-28800
  // PST8PDT  Sun Mar  9 10:00:00 2008 UTC = Sun Mar  9 03:00:00 2008 PDT isdst=1 gmtoff=-25200
  //
  // 2008-03-09 00:00:00 UTC -> 2008-03-08 16:00:00 -0800 (PST)
  // 2008-03-09 08:00:00 UTC -> 2008-03-09 00:00:00 -0800 (PST)
  // 2008-03-09 09:59:59 UTC -> 2008-03-09 01:59:59 -0800 (PST)
  // 2008-03-09 10:00:00 UTC -> 2008-03-09 03:00:00 -0700 (PDT)
  // 2008-03-10 00:00:00 UTC -> 2008-03-09 17:00:00 -0700 (PDT)
  //
  if (t < 10hour)
    return t - 8hour;
  else
    return t - 7hour;
}

real nov_us_pacific(real t) {
  // 2008-11-01 00:00:00 UTC -> 2008-10-31 17:00:00 -0700 (PDT)
  // 2008-11-02 08:59:59 UTC -> 2008-11-02 01:59:59 -0800 (PDT)
  // 2008-11-02 09:00:00 UTC -> 2008-11-02 01:00:00 -0800 (PST)
  if (t < nov2_9)
    return t - 7hour;
  else
    return t - 8hour;
}

real dec_us_pacific(real t) {
  // right/PST8PDT  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
  // right/PST8PDT  Thu Jan  1 00:00:00 2009 UTC = Wed Dec 31 16:00:00 2008 PST isdst=0 gmtoff=-28800
  return t - 8hour;
}

string mar_datefmt(real t) {
  int x = floor(t);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+9;
  return format("2008-03-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string mar_xticklabel(real x) {
  return mar_datefmt(x) + " UTC";
}

string mar_yticklabel(real y) {
  return mar_datefmt(y);
}

string nov_datefmt(real t) {
  int x = floor(t-nov2);
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int d = quotient(x, 86400)+2;
  return format("2008-11-%02d", d) +
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string nov_xticklabel(real x) {
  return nov_datefmt(x) + " UTC";
}

string nov_yticklabel(real y) {
  return nov_datefmt(y);
}

string dec_utc_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < jan1;
  if (x == dec31_24)
    return "2008-12-31 23:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = before_leap_second ? 31 : 1;
  int mon = before_leap_second ? 12 : 1;
  int year = before_leap_second ? 2008 : 2009;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_us_pacific_datefmt(real t) {
  int x = floor(t);
  bool before_leap_second = x < dec_us_pacific(jan1);
  if (x == dec_us_pacific(dec31_24))
    return "2008-12-31 15:59:60";
  else if (!before_leap_second)
    x = x-1;
  int s = x % 60;
  int m = quotient(x, 60) % 60;
  int h = quotient(x, 3600) % 24;
  int day = 31;
  int mon = 12;
  int year = 2008;
  return format("%d", year) +
         format("-%02d", mon) + 
         format("-%02d", day) + 
         format(" %02d", h) + 
         format(":%02d", m) + 
         format(":%02d", s);
}

string dec_xticklabel(real x) {
  return dec_utc_datefmt(x) + " UTC";
}

string dec_utc_yticklabel(real y) {
  return dec_utc_datefmt(y);
}

string dec_us_pacific_yticklabel(real y) {
  return dec_us_pacific_datefmt(y);
}

Label xlabel = shift(8mm,-3mm)*rotate(-30)*Label("$%g$");
Label ylabel = shift(0mm,0.3mm)*rotate(0)*Label("$%g$");

real usize1 = 0.0012;
real usize2 = 0.008;
real usize3 = 24;

pair mar_orig1 = (0,0);
pair mar_orig2 = (150,-250);
pair nov_orig1 = (180,180);
pair nov_orig2 = (370-usize2*hour,-30);
pair dec_orig1 = (360,360);
pair dec_orig2 = (520,120);

transform mar_trans1 = shift(mar_orig1)*scale(usize1)*shift(-mar9,-mar9);
transform mar_trans2 = shift(mar_orig2)*scale(usize2)*shift(-mar9_8,-mar_us_pacific(mar9_8));

transform nov_trans1 = shift(nov_orig1)*scale(usize1)*shift(-nov2,-nov2);
transform nov_trans2 = shift(nov_orig2)*scale(usize2)*shift(-nov2_7,-nov_us_pacific(nov2_7));

transform dec_trans1 = shift(dec_orig1)*scale(usize1)*shift(-dec31_12,-dec31_12);
transform dec_trans2 = shift(dec_orig2)*scale(usize3)*shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58));

draw(mar_trans1*(mar9_8,mar9)--mar_trans2*(mar9_8,mar9), palecyan);
draw(mar_trans1*(mar9_12,mar9_5)--mar_trans2*(mar9_12,mar9_5), palecyan);

pair pos_spring_dst_switch = mar_trans2*(mar9_10,mar9-3.1hour);
pair pos_autumn_dst_switch = nov_trans2*(nov2_9,nov2-3.1hour);
pair pos_leap_second = dec_trans2*(dec31_23_59_60+0.5,dec_us_pacific(dec31_23_59_58)-4);
label("Spring DST Switch", pos_spring_dst_switch, fontsize(18));
label("Autumn DST Switch", pos_autumn_dst_switch, fontsize(18));
label("Leap Second", pos_leap_second, fontsize(18));

label("PST8PDT in 2008", (pos_leap_second.x,pos_spring_dst_switch.y), fontsize(18));

draw(nov_trans1*(nov2_7,nov2)--nov_trans2*(nov2_7,nov2), palecyan);
draw(nov_trans1*(nov2_11,nov2_3)--nov_trans2*(nov2_11,nov2_3), palecyan);

draw(dec_trans1*(dec31_23_59_58,dec_us_pacific(dec31_23_59_58))--dec_trans2*(dec31_23_59_58,dec_us_pacific(dec31_23_59_58)), palered);
draw(dec_trans1*(jan1_0_0_2,dec_us_pacific(jan1_0_0_2))--dec_trans2*(jan1_0_0_2,dec_us_pacific(jan1_0_0_2)), palered);

draw(mar_trans1*(mar10,mar10)--nov_trans1*(nov2,nov2), Dotted);
draw(mar_trans1*(mar10,jp(mar10))--nov_trans1*(nov2,jp(nov2)), Dotted);
draw(mar_trans1*(mar10,mar_us_pacific(mar10))--nov_trans1*(nov2,nov_us_pacific(nov2)), Dotted);

draw(nov_trans1*(nov3,nov3)--dec_trans1*(dec31_12,dec31_12), Dotted);
draw(nov_trans1*(nov3,jp(nov3))--dec_trans1*(dec31_12,jp(dec31_12)), Dotted);
draw(nov_trans1*(nov3,nov_us_pacific(nov3))--dec_trans1*(dec31_12,dec_us_pacific(dec31_12)), Dotted);

draw(mar_trans2*(mar9_12,mar9_5)--nov_trans2*(nov2_7, nov2), Dotted);
draw(nov_trans2*(nov2_11,nov2_3)--dec_trans2*(dec31_23_59_58, dec_us_pacific(dec31_23_59_58)), Dotted);

picture mar_pic1;
unitsize(mar_pic1, usize1);
filldraw(mar_pic1, shift(mar9_8, mar9)*scale(3600*4,3600*5)*unitsquare, palecyan, black);
draw(mar_pic1, graph(gmt, mar9, mar10));
draw(mar_pic1, graph(jp, mar9, mar10));
draw(mar_pic1, graph(mar_us_pacific, mar9, mar9_10-1));
draw(mar_pic1, graph(mar_us_pacific, mar9_10, mar10));
real[] xmajorticks = { mar9, mar9_12, mar10 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = mar9+i*3600;
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(mar_pic1, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_12, mar10 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = mar9+i*3600;
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(mar_pic1, XEquals(mar9), ymin=mar9-11hour, yticks);
label(mar_pic1, "JST", (mar9_20, jp(mar9_20)), E);
label(mar_pic1, "GMT", (mar9_20, gmt(mar9_20)), E);
label(mar_pic1, "PDT", (mar9_20, mar_us_pacific(mar9_20)), E);
label(mar_pic1, "PST", (mar9_6, mar_us_pacific(mar9_6)));
mar_pic1 = shift(-mar9,-mar9)*mar_pic1;
add(mar_pic1.fit(),mar_orig1);

picture mar_pic2;
unitsize(mar_pic2, usize2);
draw(mar_pic2, graph(mar_us_pacific, mar9_8, mar9_10-1));
draw(mar_pic2, graph(mar_us_pacific, mar9_10, mar9_12));
fill(mar_pic2, shift(mar9_10, mar9_3)*scale(200)*unitcircle);
filldraw(mar_pic2, shift(mar9_10, mar9_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { mar9_8, mar9_9, mar9_10, mar9_11, mar9_12 };
ticks xticks = Ticks(xlabel, mar_xticklabel, Ticks=xmajorticks);
xaxis(mar_pic2, YEquals(mar9, false), xticks);
real[] ymajorticks = { mar9, mar9_1, mar9_2, mar9_3, mar9_4, mar9_5 };
ticks yticks = Ticks(ylabel, mar_yticklabel, Ticks=ymajorticks);
yaxis(mar_pic2, XEquals(mar9_8), ymin=mar9, yticks);
label(mar_pic2, "PST", (mar9_9+1800, mar_us_pacific(mar9_9+1800)), SE);
label(mar_pic2, "PDT", (mar9_11, mar_us_pacific(mar9_11)), NW);
mar_pic2 = shift(-mar9_8,-mar_us_pacific(mar9_8))*mar_pic2;
add(mar_pic2.fit(),mar_orig2);


picture nov_pic1;
unitsize(nov_pic1, usize1);
filldraw(nov_pic1, shift(nov2_7, nov2)*scale(3600*4,3600*3)*unitsquare, palecyan, black);
draw(nov_pic1, graph(gmt, nov2, nov3));
draw(nov_pic1, graph(nov_us_pacific, nov2, nov2+9hour-1));
draw(nov_pic1, graph(nov_us_pacific, nov2+9hour, nov3));
draw(nov_pic1, graph(jp, nov2, nov3));
real[] xmajorticks = { nov2, nov2_12, nov3 };
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = nov2+i*3600;
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(nov_pic1, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_12, nov3 };
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = nov2+i*3600;
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(nov_pic1, XEquals(nov2), ymin=nov2-11hour, yticks);
label(nov_pic1, "JST", (nov2_20, jp(nov2_20)), E);
label(nov_pic1, "GMT", (nov2_20, gmt(nov2_20)), E);
label(nov_pic1, "PST", (nov2_20, nov_us_pacific(nov2_20)), E);
label(nov_pic1, "PDT", (nov2_5, nov_us_pacific(nov2_5)));
nov_pic1 = shift(-nov2,-nov2)*nov_pic1;
add(nov_pic1.fit(),nov_orig1);

picture nov_pic2;
unitsize(nov_pic2, usize2);
draw(nov_pic2, graph(nov_us_pacific, nov2_7, nov2_9-1));
draw(nov_pic2, graph(nov_us_pacific, nov2_9, nov2_11));
fill(nov_pic2, shift(nov2_9, nov2_1)*scale(200)*unitcircle);
filldraw(nov_pic2, shift(nov2_9, nov2_2)*scale(200)*unitcircle, white, black);
real[] xmajorticks = { nov2_6, nov2_7, nov2_8, nov2_9, nov2_10, nov2_11 };
ticks xticks = Ticks(xlabel, nov_xticklabel, Ticks=xmajorticks);
xaxis(nov_pic2, YEquals(nov2, false), xticks);
real[] ymajorticks = { nov2, nov2_1, nov2_2, nov2_3 };
ticks yticks = Ticks(ylabel, nov_yticklabel, Ticks=ymajorticks);
yaxis(nov_pic2, XEquals(nov2_7), ymin=nov2, yticks);
label(nov_pic2, "PDT", (nov2_8+1800, nov_us_pacific(nov2_8+1800)), NW);
label(nov_pic2, "PST", (nov2_10, nov_us_pacific(nov2_10)), SE);
nov_pic2 = shift(-nov2_7,-nov_us_pacific(nov2_7))*nov_pic2;
add(nov_pic2.fit(),nov_orig2);

picture dec_pic1;
unitsize(dec_pic1, usize1);
draw(dec_pic1, graph(gmt, dec31_12, jan1_12));
draw(dec_pic1, graph(jp, dec31_12, jan1_12));
draw(dec_pic1, graph(dec_us_pacific, dec31_12, jan1_12));
real[] xmajorticks = { dec31_12, jan1, jan1_12};
real[] xminorticks;
for (int i = 1; i <= 23; ++i) xminorticks[xminorticks.length] = dec31_12+i*3600;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic1, YEquals(dec31_12, false), xticks);
real[] ymajorticks = { dec31_12, jan1, jan1_12};
real[] yminorticks;
for (int i = -11; i <= 35; ++i) yminorticks[yminorticks.length] = dec31_12+i*3600;
ticks yticks = Ticks(ylabel, dec_utc_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic1, XEquals(dec31_12), ymin=dec31_12-11hour, yticks);
label(dec_pic1, "JST", (jan1_9, jp(jan1_9)), E);
label(dec_pic1, "GMT", (jan1_9, gmt(jan1_9)), E);
label(dec_pic1, "PST", (jan1_9, dec_us_pacific(jan1_9)), E);
draw(dec_pic1, (dec31_24,dec31_12-11hour)--(dec31_24,jan1+21hour), palered);
draw(dec_pic1, (dec31_12,gmt(dec31_24))--(jan1_12,gmt(dec31_24)), palered);
draw(dec_pic1, (dec31_12,jp(dec31_24))--(jan1_12,jp(dec31_24)), palered);
draw(dec_pic1, (dec31_12,dec_us_pacific(dec31_24))--(jan1_12,dec_us_pacific(dec31_24)), palered);
dot(dec_pic1, (dec31_24, jp(dec31_24)), red);
dot(dec_pic1, (dec31_24, gmt(dec31_24)), red);
dot(dec_pic1, (dec31_24, dec_us_pacific(dec31_24)), red);
dec_pic1 = shift(-dec31_12,-dec31_12)*dec_pic1;
add(dec_pic1.fit(),dec_orig1);

picture dec_pic2;
unitsize(dec_pic2, usize3);
fill(dec_pic2, shift(dec31_23_59_60, dec_us_pacific(dec31_23_59_58))*scale(1, jan1_0_0_2-dec31_23_59_58)*unitsquare, palered);
fill(dec_pic2, shift(dec31_23_59_58, dec_us_pacific(dec31_23_59_60))*scale(jan1_0_0_2-dec31_23_59_58, 1)*unitsquare, palered);
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_58, jan1_0_0_2));
draw(dec_pic2, graph(dec_us_pacific, dec31_23_59_60, jan1), red+2);
real[] xmajorticks = { dec31_23_59_58, dec31_23_59_60, jan1, jan1_0_0_2 };
real[] xminorticks;
for (int i = -2; i <= 2; ++i) xminorticks[xminorticks.length] = dec31_23_59_60+i;
ticks xticks = Ticks(xlabel, dec_xticklabel, Ticks=xmajorticks, ticks=xminorticks);
xaxis(dec_pic2, YEquals(dec_us_pacific(dec31_23_59_58), false), xticks);
real[] ymajorticks = { dec_us_pacific(dec31_23_59_58), dec_us_pacific(dec31_23_59_60), dec_us_pacific(jan1), dec_us_pacific(jan1_0_0_2) };
real[] yminorticks;
for (int i = -2; i <= 2; ++i) yminorticks[yminorticks.length] = dec_us_pacific(dec31_23_59_60)+i;
ticks yticks = Ticks(ylabel, dec_us_pacific_yticklabel, Ticks=ymajorticks, ticks=yminorticks);
yaxis(dec_pic2, XEquals(dec31_23_59_58), ymin=dec_us_pacific(dec31_23_59_58), yticks);
label(dec_pic2, "leap second", (dec31_23_59_60+0.5, dec_us_pacific(dec31_23_59_60+0.5)), E);
label(dec_pic2, "PST", (jan1_0_0_1, dec_us_pacific(jan1_0_0_1)), E);
dec_pic2 = shift(-dec31_23_59_58,-dec_us_pacific(dec31_23_59_58))*dec_pic2;
//write(dec_pic2.calculateTransform());
add(dec_pic2.fit(),dec_orig2);
#4

このグラフの縦軸はなかなか怪しい。

閏秒の拡大部分を見ると、縦軸に 2008-12-31 15:59:60 という目盛がある。

これは PST8PDT においてはまったくもって正しい。しかし、GMT や JST に 2008-12-31 15:59:60 があるかというと、それはない。

GMT での閏秒は 2008-12-31 23:59:60 だし、JST の閏秒は 2009-01-01 08:59:60 である。

では、PST8PDT, GMT, JST をひとつのグラフに描くと、その縦軸はなんだろう?

まぁ、縦軸は struct tm のようなもので、秒は 0 から 60 までとれるということだろうな。そして、閏秒以外の所では、60 は存在しない秒で (夏時間の春のところみたいに) 飛ぶ、と。

#5

整理して描き直すとこうか。

縦軸を毎分61秒のスケールにした結果、グラフ全部で epoch を共通化でき、時刻のフォーマットもまとめられた。

time-system5.png

time-system5.asy:

import graph;

int min = 60;
int hour = 3600;
int day = 24hour;

real feb1 = 31day;
real mar1 = feb1 + 29day;
real apr1 = mar1 + 31day;
real may1 = apr1 + 30day;
real jun1 = may1 + 31day;
real jul1 = jun1 + 30day;
real aug1 = jul1 + 31day;
real sep1 = aug1 + 31day;
real oct1 = sep1 + 30day;
real nov1 = oct1 + 31day;
real dec1 = nov1 + 30day;
real dec31_23_59_60 = dec1 + 31day; // the leap second
real jan1 = dec31_23_59_60 + 1;

real mar8 = mar1 + 7day;
real mar8_12 = mar8 + 12hour;
real mar8_13 = mar8 + 13hour;
real mar9 = mar1 + 8day;
real mar9_1 = mar9 + 1hour;
real mar9_2 = mar9 + 2hour;
real mar9_3 = mar9 + 3hour;
real mar9_4 = mar9 + 4hour;
real mar9_5 = mar9 + 5hour;
real mar9_6 = mar9 + 6hour;
real mar9_7 = mar9 + 7hour;
real mar9_8 = mar9 + 8hour;
real mar9_9 = mar9 + 9hour;
real mar9_10 = mar9 + 10hour;
real mar9_11 = mar9 + 11hour;
real mar9_12 = mar9 + 12hour;
real mar9_20 = mar9 + 20hour;
real mar10 = mar1 + 9day;
real mar10_12 = mar10 + 12hour;

real nov1_12 = nov1 + 12hour;
real nov1_13 = nov1 + 13hour;
real nov2 = nov1 + 1day;
real nov2_1 = nov2 + 1hour;
real nov2_2 = nov2 + 2hour;
real nov2_3 = nov2 + 3hour;
real nov2_4 = nov2 + 4hour;
real nov2_5 = nov2 + 5hour;
real nov2_6 = nov2 + 6hour;
real nov2_7 = nov2 + 7hour;
real nov2_8 = nov2 + 8hour;
real nov2_9 = nov2 + 9hour;
real nov2_10 = nov2 + 10hour;
real nov2_11 = nov2 + 11hour;
real nov2_12 = nov2 + 12hour;
real nov2_20 = nov2 + 20hour;
real nov3 = nov1 + 2day;
real nov3_12 = nov3 + 12hour;

real dec31 = dec1 + 30day;
real dec31_1 = dec31 + 1hour;
real dec31_12 = dec31 + 12hour;
real dec31_15 = dec31 + 15hour;
real dec31_15_59 = dec31_15 + 59min;
real dec31_15_59_58 = dec31_15_59 + 58;
real dec31_15_59_59 = dec31_15_59 + 59;
real dec31_16 = dec31 + 16hour;
real dec31_16_0_1 = dec31_16 + 1;
real dec31_16_0_2 = dec31_16 + 2;
real dec31_23_59_58 = dec31 + 23hour + 59min + 58;
real dec31_23_59_59 = dec31 + 23hour + 59min + 59;
real jan1_0_0_1 = jan1 + 1;
real jan1_0_0_2 = jan1 + 2;
real jan1_8 = jan1 + 8hour;
real jan1_12 = jan1 + 12hour;
real jan2 = jan1 + 1day;

int lmin = 61;
int lhour = 60*lmin;
int lday = 24*lhour;

string x_datefmt(int t) {
  if (t < dec31_23_59_60) {
    int s = t % min;
    int m = quotient(t, min) % 60;
    int h = quotient(t, hour) % 24;
    int d = quotient(t, day)+1;
    string hms = format(" %02d", h) + format(":%02d", m) + format(":%02d", s);
    if (d <= 31)
      return format("2008-01-%02d", d) + hms;
    d -= 31;
    if (d <= 29)
      return format("2008-02-%02d", d) + hms;
    d -= 29;
    if (d <= 31)
      return format("2008-03-%02d", d) + hms;
    d -= 31;
    if (d <= 30)
      return format("2008-04-%02d", d) + hms;
    d -= 30;
    if (d <= 31)
      return format("2008-05-%02d", d) + hms;
    d -= 31;
    if (d <= 30)
      return format("2008-06-%02d", d) + hms;
    d -= 30;
    if (d <= 31)
      return format("2008-07-%02d", d) + hms;
    d -= 31;
    if (d <= 31)
      return format("2008-08-%02d", d) + hms;
    d -= 31;
    if (d <= 30)
      return format("2008-09-%02d", d) + hms;
    d -= 30;
    if (d <= 31)
      return format("2008-10-%02d", d) + hms;
    d -= 31;
    if (d <= 30)
      return format("2008-11-%02d", d) + hms;
    d -= 30;
    return format("2008-12-%02d", d) + hms;
  }
  else if (t < jan1) {
    return "2008-12-31 23:59:60";
  }
  else {
    --t;
    int s = t % 60;
    int m = quotient(t, 60) % 60;
    int h = quotient(t, 3600) % 24;
    int d = quotient(t, 86400)+1-366;
    string hms = format(" %02d", h) + format(":%02d", m) + format(":%02d", s);
    return format("2009-01-%02d", d) + hms;
  }
}

string xticklabel(real x) {
  int t = floor(x);
  return x_datefmt(t) + " UTC";
}

string y_datefmt(int t) {
  int s = t % lmin;
  int m = quotient(t, lmin) % 60;
  int h = quotient(t, lhour) % 24;
  int d = quotient(t, lday)+1;
  string hms = format(" %02d", h) + format(":%02d", m) + format(":%02d", s);
  if (d <= 31)
    return format("2008-01-%02d", d) + hms;
  d -= 31;
  if (d <= 29)
    return format("2008-02-%02d", d) + hms;
  d -= 29;
  if (d <= 31)
    return format("2008-03-%02d", d) + hms;
  d -= 31;
  if (d <= 30)
    return format("2008-04-%02d", d) + hms;
  d -= 30;
  if (d <= 31)
    return format("2008-05-%02d", d) + hms;
  d -= 31;
  if (d <= 30)
    return format("2008-06-%02d", d) + hms;
  d -= 30;
  if (d <= 31)
    return format("2008-07-%02d", d) + hms;
  d -= 31;
  if (d <= 31)
    return format("2008-08-%02d", d) + hms;
  d -= 31;
  if (d <= 30)
    return format("2008-09-%02d", d) + hms;
  d -= 30;
  if (d <= 31)
    return format("2008-10-%02d", d) + hms;
  d -= 31;
  if (d <= 30)
    return format("2008-11-%02d", d) + hms;
  d -= 30;
  if (d <= 31)
    return format("2008-12-%02d", d) + hms;
  d -= 31;
  return format("2009-01-%02d", d) + hms;
}

string yticklabel(real y) {
  int t = floor(y);
  return y_datefmt(t);
}

real gmt(real x) {
  // 2008-01-01 00:00:00 UTC -> 2008-01-01 00:00:00 GMT
  int t = floor(x);
  real subsec = x-t;
  if (t < dec31_23_59_60)
    return t + quotient(t, 60) + subsec;
  if (t < jan1)
    return dec31_23_59_60 + quotient(floor(dec31_23_59_60), 60) + subsec;
  return (t-1) + quotient(t-1, 60) + subsec;
}

real jp(real x) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  int t = floor(x);
  real subsec = x-t;
  if (t < dec31_23_59_60)
    return t + quotient(t, 60) + subsec + 9lhour;
  if (t < jan1)
    return dec31_23_59_60 + quotient(floor(dec31_23_59_60), 60) + subsec + 9lhour;
  return (t-1) + quotient(t-1, 60) + subsec + 9lhour;
}

real us_pacific(real x) {
  // 2008-11-01 00:00:00 UTC -> 2008-11-01 09:00:00 +0900 (JST)
  int t = floor(x);
  real subsec = x-t;
  if (t < mar9_10)
    return t + quotient(t, 60) + subsec - 8lhour;
  if (t < nov2_9)
    return t + quotient(t, 60) + subsec - 7lhour;
  if (t < dec31_23_59_60)
    return t + quotient(t, 60) + subsec - 8lhour;
  if (t < jan1)
    return dec31_23_59_60 + quotient(floor(dec31_23_59_60), 60) - 1 + subsec - 8lhour;
  return (t-1) + quotient(t-1, 60) + subsec - 8lhour;
}

real reg1_beg = mar9-1min;
real reg1_end = mar10+1min;
real reg2_beg = nov2-1min;
real reg2_end = nov3+1min;
real reg3_beg = dec31_12-1min;
real reg3_end = jan1_12+1min;

real aspect = 60/61;
real usize1x = 0.0010;
real usize2x = 0.007;
real usize3x = 22;

real usize1y = usize1x*aspect;
pair usize1 = (usize1x,usize1y);
real usize2y = usize2x*aspect;
pair usize2 = (usize2x,usize2y);
real usize3y = usize3x*aspect;
pair usize3 = (usize3x,usize3y);

pair step = (180,180);

pair mar_pos1 = (0,0);
pair nov_pos1 = mar_pos1 + step;
pair dec_pos1 = mar_pos1 + 2step;

pair mar_pos2 = (-170,240);
pair nov_pos2 = mar_pos2 + step - (usize2.x*hour,0);
pair dec_pos2 = mar_pos2 + 2step;
pair dec_pos3 = (mar_pos2.x,dec_pos2.y);

pair mar_orig1 = (mar9, gmt(mar8_13));
pair nov_orig1 = (nov2, gmt(nov1_13));
pair dec_orig1 = (dec31_12, gmt(dec31_1));

pair mar_orig2 = (mar9_8, gmt(mar9));
pair nov_orig2 = (nov2_7, gmt(nov2));
pair dec_orig2 = (dec31_23_59_58, gmt(dec31_15_59_58));
pair dec_orig3 = (dec31_15_59_58, gmt(dec31_15_59_58));

transform scalep(pair p) {
  return scale(p.x, p.y);
}

transform mar_trans1 = shift(mar_pos1)*scalep(usize1)*shift(-mar_orig1);
transform mar_trans2 = shift(mar_pos2)*scalep(usize2)*shift(-mar_orig2);

transform nov_trans1 = shift(nov_pos1)*scalep(usize1)*shift(-nov_orig1);
transform nov_trans2 = shift(nov_pos2)*scalep(usize2)*shift(-nov_orig2);

transform dec_trans1 = shift(dec_pos1)*scalep(usize1)*shift(-dec_orig1);
transform dec_trans2 = shift(dec_pos2)*scalep(usize3)*shift(-dec_orig2);
transform dec_trans3 = shift(dec_pos3)*scalep(usize3)*shift(-dec_orig3);

path rect(pair min, pair max) {
  return min--(max.x,min.y)--max--(min.x,max.y)--cycle;
}
fill(rect(dec_trans3*(dec31_15_59_58,gmt(dec31_16)-1), dec_trans2*(jan1_0_0_2,gmt(dec31_16))),lightred);

draw(mar_trans1*(mar9_8,gmt(mar9))--mar_trans2*(mar9_8,gmt(mar9)), lightblue);
draw(mar_trans1*(mar9_12,gmt(mar9_5))--mar_trans2*(mar9_12,gmt(mar9_5)), lightblue);

draw(nov_trans1*(nov2_7,gmt(nov2))--nov_trans2*(nov2_7,gmt(nov2)), lightblue);
draw(nov_trans1*(nov2_11,gmt(nov2_3))--nov_trans2*(nov2_11,gmt(nov2_3)), lightblue);

draw(dec_trans1*(dec31_23_59_58,gmt(dec31_15_59_58))--dec_trans2*(dec31_23_59_58,gmt(dec31_15_59_58)), lightblue);
draw(dec_trans1*(jan1_0_0_2,gmt(dec31_16_0_2))--dec_trans2*(jan1_0_0_2,gmt(dec31_16_0_2)), lightblue);

draw(dec_trans1*(dec31_15_59_58,gmt(dec31_15_59_58))--dec_trans3*(dec31_15_59_58,gmt(dec31_15_59_58)), lightblue);
draw(dec_trans1*(dec31_16_0_2,gmt(dec31_16_0_2))--dec_trans3*(dec31_16_0_2,gmt(dec31_16_0_2)), lightblue);

draw(mar_trans1*(mar10,gmt(mar10))--nov_trans1*(nov2,gmt(nov2)), Dotted);
draw(mar_trans1*(mar10,jp(mar10))--nov_trans1*(nov2,jp(nov2)), Dotted);
draw(mar_trans1*(mar10,us_pacific(mar10))--nov_trans1*(nov2,us_pacific(nov2)), Dotted);

draw(nov_trans1*(nov3,gmt(nov3))--dec_trans1*(dec31_12,gmt(dec31_12)), Dotted);
draw(nov_trans1*(nov3,jp(nov3))--dec_trans1*(dec31_12,jp(dec31_12)), Dotted);
draw(nov_trans1*(nov3,us_pacific(nov3))--dec_trans1*(dec31_12,us_pacific(dec31_12)), Dotted);

draw(mar_trans2*(mar9_12,gmt(mar9_5))--nov_trans2*(nov2_7, gmt(nov2)), Dotted);
draw(nov_trans2*(nov2_11,gmt(nov2_3))--dec_trans2*(dec31_23_59_58, us_pacific(dec31_23_59_58)), Dotted);

pair pos_spring_dst_switch = mar_trans1*(mar9_12,gmt(mar9-36hour));
pair pos_autumn_dst_switch = nov_trans1*(nov2_12,gmt(nov2-36hour));
pair pos_leap_second = dec_trans1*(jan1+0.5,gmt(dec31_12-36hour));
label("Spring DST Switch", pos_spring_dst_switch, RightSide, fontsize(18));
label("Autumn DST Switch", pos_autumn_dst_switch, RightSide, fontsize(18));
label("Leap Second", pos_leap_second, RightSide, fontsize(18));

label("PST8PDT in 2008", (pos_leap_second.x,pos_spring_dst_switch.y), fontsize(18));

Label xlabel = shift(8mm,-3mm)*rotate(-30)*Label();
Label ylabel = shift(0mm,0.3mm)*rotate(0)*Label();

picture mar_pic1;
filldraw(mar_pic1, shift(mar9_8, gmt(mar9))*scale(4hour,5lhour)*unitsquare, palecyan, black);
draw(mar_pic1, graph(gmt,mar9,mar10));
draw(mar_pic1, graph(jp,mar9,mar10));
draw(mar_pic1, graph(us_pacific,mar9,mar9_10-1));
draw(mar_pic1, graph(us_pacific,mar9_10,mar10));
real[] xmajor = {mar9,mar9_12,mar10};
real[] xminor; for (int i = 0; i < 24; ++i) xminor.push(mar9 + i * hour);
xaxis(mar_pic1, YEquals(gmt(mar8_13),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor, ticks=xminor));
real[] ymajor = {gmt(mar9),gmt(mar9_12),gmt(mar10),gmt(mar10_12)};
real[] yminor; for (int i = -12; i < 36; ++i) yminor.push(gmt(mar9) + i * lhour);
yaxis(mar_pic1, XEquals(mar9,false), RightTicks(ylabel, yticklabel, Ticks=ymajor, ticks=yminor));
label(mar_pic1, "JST", (mar9_20, jp(mar9_20)), E);
label(mar_pic1, "GMT", (mar9_20, gmt(mar9_20)), E);
label(mar_pic1, "PDT", (mar9_20, us_pacific(mar9_20)), E);
label(mar_pic1, "PST", (mar9_6, us_pacific(mar9_6)), 3S);
unitsize(mar_pic1, usize1x, usize1y);
add((shift(-mar_orig1)*mar_pic1).fit(),mar_pos1);

picture mar_pic2;
draw(mar_pic2, graph(us_pacific,mar9_8,mar9_10-1));
draw(mar_pic2, graph(us_pacific,mar9_10,mar9_12));
fill(mar_pic2, shift(mar9_10, gmt(mar9_3))*scale(1mm/usize2x, 1mm/usize2y)*unitcircle);
filldraw(mar_pic2, shift(mar9_10, gmt(mar9_2))*scale(1mm/usize2x, 1mm/usize2y)*unitcircle, white, black);
real[] xmajor = {mar9_8,mar9_9,mar9_10,mar9_11,mar9_12};
xaxis(mar_pic2, YEquals(gmt(mar9),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor));
real[] ymajor = {gmt(mar9),gmt(mar9_1),gmt(mar9_2),gmt(mar9_3),gmt(mar9_4),gmt(mar9_5)};
yaxis(mar_pic2, XEquals(mar9_8,false), RightTicks(ylabel, yticklabel, Ticks=ymajor));
label(mar_pic2, "PST", (mar9_9+1800, us_pacific(mar9_9+1800)), SE);
label(mar_pic2, "PDT", (mar9_11, us_pacific(mar9_11)), NW);
unitsize(mar_pic2, usize2x, usize2y);
add((shift(-mar_orig2)*mar_pic2).fit(),mar_pos2);

picture nov_pic1;
filldraw(nov_pic1, shift(nov2_7, gmt(nov2))*scale(4hour,3lhour)*unitsquare, palecyan, black);
draw(nov_pic1, graph(gmt,nov2,nov3));
draw(nov_pic1, graph(jp,nov2,nov3));
draw(nov_pic1, graph(us_pacific,nov2,nov2_9-1));
draw(nov_pic1, graph(us_pacific,nov2_9,nov3));
real[] xmajor = {nov2,nov2_12,nov3};
real[] xminor; for (int i = 0; i < 24; ++i) xminor.push(nov2 + i * hour);
xaxis(nov_pic1, YEquals(gmt(nov1_13),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor, ticks=xminor));
real[] ymajor = {gmt(nov2),gmt(nov2_12),gmt(nov3),gmt(nov3_12)};
real[] yminor; for (int i = -12; i < 36; ++i) yminor.push(gmt(nov2) + i * lhour);
yaxis(nov_pic1, XEquals(nov2,false), RightTicks(ylabel, yticklabel, Ticks=ymajor, ticks=yminor));
label(nov_pic1, "JST", (nov2_20, jp(nov2_20)), E);
label(nov_pic1, "GMT", (nov2_20, gmt(nov2_20)), E);
label(nov_pic1, "PST", (nov2_20, us_pacific(nov2_20)), E);
label(nov_pic1, "PDT", (nov2_5, us_pacific(nov2_5)), 3S);
unitsize(nov_pic1, usize1x, usize1y);
add((shift(-nov_orig1)*nov_pic1).fit(),nov_pos1);

picture nov_pic2;
draw(nov_pic2, graph(us_pacific,nov2_7,nov2_9-1));
draw(nov_pic2, graph(us_pacific,nov2_9,nov2_11));
fill(nov_pic2, shift(nov2_9, gmt(nov2_1))*scale(1mm/usize2x, 1mm/usize2y)*unitcircle);
filldraw(nov_pic2, shift(nov2_9, gmt(nov2_2))*scale(1mm/usize2x, 1mm/usize2y)*unitcircle, white, black);
real[] xmajor = {nov2_7,nov2_8,nov2_9,nov2_10,nov2_11};
xaxis(nov_pic2, YEquals(gmt(nov2),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor));
real[] ymajor = {gmt(nov2),gmt(nov2_1),gmt(nov2_2),gmt(nov2_3)};
yaxis(nov_pic2, XEquals(nov2_7,false), RightTicks(ylabel, yticklabel, Ticks=ymajor));
label(nov_pic2, "PDT", (nov2_8+1800, us_pacific(nov2_8+1800)), NW);
label(nov_pic2, "PST", (nov2_10, us_pacific(nov2_10)), SE);
unitsize(nov_pic2, usize2x, usize2y);
add((shift(-nov_orig2)*nov_pic2).fit(),nov_pos2);

picture dec_pic1;
draw(dec_pic1, graph(gmt,dec31_12,jan1_12));
draw(dec_pic1, graph(jp,dec31_12,jan1_12));
draw(dec_pic1, graph(us_pacific,dec31_12,jan1_12));
draw(dec_pic1, (dec31_23_59_60,gmt(dec31))--(dec31_23_59_60,gmt(jan1+21hour)), lightred);
draw(dec_pic1, (dec31_12,gmt(dec31_23_59_60))--(jan1_12,gmt(dec31_23_59_60)), lightred);
draw(dec_pic1, (dec31_12,jp(dec31_23_59_60))--(jan1_12,jp(dec31_23_59_60)), lightred);
draw(dec_pic1, (dec31_12,us_pacific(dec31_23_59_60))--(jan1_12,us_pacific(dec31_23_59_60)), lightred);
dot(dec_pic1, (dec31_23_59_60, gmt(dec31_23_59_60)), brown);
dot(dec_pic1, (dec31_23_59_60, jp(dec31_23_59_60)), brown);
dot(dec_pic1, (dec31_23_59_60, us_pacific(dec31_23_59_60)), brown);
real[] xmajor = {dec31_12,jan1,jan1_12};
real[] xminor; for (int i = 0; i < 24; ++i) xminor.push(dec31_12 + i * hour);
xaxis(dec_pic1, YEquals(gmt(dec31_1),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor, ticks=xminor));
real[] ymajor = {gmt(dec31_12), gmt(jan1),gmt(jan1_12),gmt(jan2)};
real[] yminor; for (int i = -12; i < 36; ++i) yminor.push(gmt(dec31_12) + i * lhour);
yaxis(dec_pic1, XEquals(dec31_12,false), RightTicks(ylabel, yticklabel, Ticks=ymajor, ticks=yminor));
label(dec_pic1, "JST", (jan1_8, jp(jan1_8)), E);
label(dec_pic1, "GMT", (jan1_8, gmt(jan1_8)), E);
label(dec_pic1, "PST", (jan1_8, us_pacific(jan1_8)), E);
unitsize(dec_pic1, usize1x, usize1y);
add((shift(-dec_orig1)*dec_pic1).fit(),dec_pos1);

picture dec_pic2;
fill(dec_pic2, shift(dec31_23_59_60, us_pacific(dec31_23_59_58))*scale(1, jan1_0_0_2-dec31_23_59_58)*unitsquare, lightred);
draw(dec_pic2, graph(us_pacific,dec31_23_59_58,jan1_0_0_2));
draw(dec_pic2, graph(us_pacific,dec31_23_59_60,jan1),brown+2);
real[] xmajor = {dec31_23_59_58, dec31_23_59_59, dec31_23_59_60, jan1, jan1_0_0_1, jan1_0_0_2};
xaxis(dec_pic2, YEquals(gmt(dec31_15_59_58),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor));
real[] ymajor = {gmt(dec31_15_59_58),gmt(dec31_15_59_59),gmt(dec31_15_59_59)+1,gmt(dec31_16),gmt(dec31_16_0_1),gmt(dec31_16_0_2)};
yaxis(dec_pic2, XEquals(dec31_23_59_58,false), RightTicks(ylabel, yticklabel, Ticks=ymajor));
label(dec_pic2, "leap second", (dec31_23_59_60+0.5, us_pacific(dec31_23_59_60+0.5)), E);
label(dec_pic2, "PST", (jan1_0_0_1, us_pacific(jan1_0_0_1)), SE);
unitsize(dec_pic2, usize3x, usize3y);
add((shift(-dec_orig2)*dec_pic2).fit(),dec_pos2);

picture dec_pic3;
;
draw(dec_pic3, graph(gmt,dec31_15_59_58,dec31_16-0.01));
draw(dec_pic3, graph(gmt,dec31_16,dec31_16_0_2));
fill(dec_pic3, shift(dec31_16, gmt(dec31_16))*scale(1mm/usize3x, 1mm/usize3y)*unitcircle);
filldraw(dec_pic3, shift(dec31_16, (gmt(dec31_16))-1)*scale(1mm/usize3x, 1mm/usize3y)*unitcircle, white, black);
real[] xmajor = {dec31_15_59_58, dec31_15_59_59, dec31_16, dec31_16_0_1, dec31_16_0_2};
xaxis(dec_pic3, YEquals(gmt(dec31_15_59_58),false), LeftTicks(xlabel, xticklabel, Ticks=xmajor));
real[] ymajor = {gmt(dec31_15_59_58),gmt(dec31_15_59_59),gmt(dec31_15_59_59)+1,gmt(dec31_16),gmt(dec31_16_0_1),gmt(dec31_16_0_2)};
yaxis(dec_pic3, XEquals(dec31_15_59_58,false), RightTicks(ylabel, yticklabel, Ticks=ymajor));
label(dec_pic3, "GMT", (dec31_16_0_1, gmt(dec31_16_0_1)), SE);
unitsize(dec_pic3, usize3x, usize3y);
add((shift(-dec_orig3)*dec_pic3).fit(),dec_pos3);

2009-07-05 (Sun)

#1

理屈はそれはそれとして、そういう状況の中でどう改善したかをプロットしてみる。

2008-03-09 の 00:00:00 から 23:59:60 までの 24*60*61=87840秒で、それぞれ何回 localtime() を呼び出して範囲を削っていくか。 (61 なのは Time.local の引数の秒は常に 60 を受けつけ、その場合も測定しているから)

numguess1.png

gnuplot で with dot にすると凡例の視認性が悪いな。赤が変更前 (old) で、緑が変更後 (new) である。

ふむ。今見直すと 100 を越えてるところがあったか。

それが、最悪でも 20 を越えなくなっている。

#2

しかし、8万以上測定点があると、個々の点はほとんど見えない。問題の、存在しない時刻のあたりを拡大してみる。

numguess2.png

#3

しかし、重なっている点がそれなりに多そうなのが気になる。

しかも、縦軸は整数値なので隙間が開いていて空間の無駄である。そこを使えば点がいくつ重なっているか表示できるのではないか。

というわけでやってみた。8個単位にして、その中で同じ値があったら上下交互に広げていく。

N = 8
h = Hash.new(0)
ARGF.each {|line|
  x, y = line.split
  g = x.to_i/N
  yy = y.to_i + (h[[g,y]].odd? ? 0.5 : -0.5) * h[[g,y]] / N
  puts "#{x} #{yy}"
  h[[g,y]] += 1
}

8個というのは、1ピクセルに入る測定点の数である。png にしたときに横幅が約580ピクセルで、測定点は 01:50:00-03:09:60 までの 80*61=4880個なので、1ピクセルあたり 4880/580=約8.4個入っている。

numguess3.png

しかし、ピクセルというのは最終出力の話なので、gnuplot 側でよろしくやってほしいところだ、と思ったが、PostScript とか解像度非依存なケースではうまくいかないな。考え方としては半透明なマークを重ね塗りするとかのほうが適切か。

2009-07-06 (Mon)

#1

そういえば、赤と緑って色弱だと区別できない組み合わせじゃなかったっけ... と思って Vischeck で調べてみると、たしかに区別できない感じである。

うぅむ。gnuplot がデフォルトで選ぶ色なのだが。

dot だと形でも区別できないしなぁ。

でも gnuplot でどうやって色を変えるんだっけかと思って調べてみると、with dot の後に linestyle 4 とつけたら紫になった。

numguess4.png

#2

gnuplot の ML でもそういう話はあったようだ。

互換性か...

上記のスレッドの中に出ているように、とりあえず ~/.gnuplot に設定を入れてみた。

set style line 1 lc rgb "red"
set style line 2 lc rgb "blue"
set style line 3 lc rgb "magenta"
set style line 4 lc rgb "cyan"
set style line 5 lc rgb "forest-green"
set style increment user 
#3

デフォルト:

color0.png

color0.gp:

plot -x, 1-x, 2-x, 3-x, 4-x, 5-x, 6-x, 7-x, 8-x, 9-x

ML にあった red, blue, magenta, cyan, forest-green という設定。

color1.png

color1.gp:

set style line 1 lc rgb "red"
set style line 2 lc rgb "blue"
set style line 3 lc rgb "magenta"
set style line 4 lc rgb "cyan"
set style line 5 lc rgb "forest-green"
set style increment user 
plot -x, 1-x, 2-x, 3-x, 4-x, 5-x, 6-x, 7-x, 8-x, 9-x
#4

餅は餅屋ということで、そっちの玄人が選んだカラーパレットがいいか、と思ってちょっと探してみたが、見つからなかった。

CUDO とかを見ても、紹介されているのはツールとか大げさなものになってしまう。

単に色がいくつか並んだカラーパレットでいいのになぁ

2009-07-09 (Thu)

#1

ふと数学関数の誤差はどのくらいか気になった

といってもどう調べたらいいかよくわからないのだが、とりあえず exp(log(x)) は x のどのくらい近くに戻ってくるのかを調べてみた。

まず Float#next として「次に大きな Float 値」を得るものを作って、100000.0 から 30個、exp(log(x)) して、x からいくつ離れているかをやはり Float#next で数える。

% cat exp-log.rb
class Float
  def next
    raise FloatDomainError, "No next value for NaN" if self.nan?
    raise FloatDomainError, "No next value for infinite" if self.infinite?
    inc = 1.0
    if self + inc == self
      begin
        inc *= 2
      end while self + inc == self
    else
      while self + (inc2 = inc / 2) != self
        inc = inc2
      end
    end
    self + inc
  end
end

include Math

x = 100000.0
30.times {
  y = exp(log(x))
  m = 0
  a = [x, y].min
  b = [x, y].max
  while a != b
    a = a.next
    m += 1
  end
  m = -m if y < x
  p [x,y,m]
  x = x.next
}
% ruby exp-log.rb 
[100000.0, 100000.00000000001, 1]
[100000.00000000001, 100000.00000000001, 0]
[100000.00000000003, 100000.00000000001, -1]
[100000.00000000004, 100000.00000000001, -2]
[100000.00000000006, 100000.00000000001, -3]
[100000.00000000007, 100000.00000000001, -4]
[100000.00000000009, 100000.00000000001, -5]
[100000.0000000001, 100000.00000000001, -6]
[100000.00000000012, 100000.0000000002, 6]
[100000.00000000013, 100000.0000000002, 5]
[100000.00000000015, 100000.0000000002, 4]
[100000.00000000016, 100000.0000000002, 3]
[100000.00000000017, 100000.0000000002, 2]
[100000.00000000019, 100000.0000000002, 1]
[100000.0000000002, 100000.0000000002, 0]
[100000.00000000022, 100000.0000000002, -1]
[100000.00000000023, 100000.0000000002, -2]
[100000.00000000025, 100000.0000000002, -3]
[100000.00000000026, 100000.0000000002, -4]
[100000.00000000028, 100000.0000000002, -5]
[100000.00000000029, 100000.00000000038, 6]
[100000.0000000003, 100000.00000000038, 5]
[100000.00000000032, 100000.00000000038, 4]
[100000.00000000033, 100000.00000000038, 3]
[100000.00000000035, 100000.00000000038, 2]
[100000.00000000036, 100000.00000000038, 1]
[100000.00000000038, 100000.00000000038, 0]
[100000.0000000004, 100000.00000000038, -1]
[100000.00000000041, 100000.00000000038, -2]
[100000.00000000042, 100000.00000000038, -3]

で、グラフにするとこうなって、このへんの値だと±6つくらい隣までで収まっている。正確に求まることも、ある。

exp-log.png

exp-log.gp:

plot '-'
1
0
-1
-2
-3
-4
-5
-6
6
5
4
3
2
1
0
-1
-2
-3
-4
-5
6
5
4
3
2
1
0
-1
-2
-3
e

2009-07-18 (Sat)

#1

socks を使って見ようと思って、

をインストールしてみた。

#2

さかいさんがいっていた論文は Equality and hashing for (almost) free: Generating implementations from abstraction functions かなぁ。

えー、cyclic なのが入ってたら hash 値を定数にしちゃうのか。

もっと賢いなにかを期待していたのだが...

たとえば、強連結成分でないところについては普通にやるとか。(まぁ、強連結成分を求めるのにメモリを使いすぎる可能性はあるか)

2009-07-19 (Sun)

#1

以下のように cyclic なデータが eql? では等しいのに、hash 値が異なるのがある。

% ruby -v -e '
def c(n,m)
  a = a0 = []
  (m-1).times { a = [a] }
  a0 << a
  n.times { a = [a] }
  a
end
arys = []
3.times {|n|
  3.times {|m|
    arys << c(n,m)
  }
}
arys.each {|a1|
  arys.each {|a2|
    raise if !a1.eql?(a2)
  }
}
arys.each {|a|
  p [a, a.hash]
}
'
ruby 1.9.2dev (2009-06-22 trunk 23820) [i686-linux]
[[[...]], -512881344]
[[[...]], -512881344]
[[[[...]]], -569680768]
[[[[...]]], -569680768]
[[[[...]]], -569680768]
[[[[[...]]]], 215753664]
[[[[[...]]]], 215753664]
[[[[[...]]]], 215753664]
[[[[[[...]]]]], -560839936]

いくつか図示してみよう。

cyclic-arrays.png

cyclic-arrays.dot:

digraph g {
  a01_1 [shape=box, label="a"];
  a01_1 -> a01_1

  a11_1 [shape=box, label="a"];
  a11_2 [shape=box, label="a"];
  a11_1 -> a11_2
  a11_2 -> a11_2

  a02_1 [shape=box, label="a"];
  a02_2 [shape=box, label="a"];
  a02_1 -> a02_2
  a02_2 -> a02_1

  a21_1 [shape=box, label="a"];
  a21_2 [shape=box, label="a"];
  a21_3 [shape=box, label="a"];
  a21_1 -> a21_2
  a21_2 -> a21_3
  a21_3 -> a21_3

  a12_1 [shape=box, label="a"];
  a12_2 [shape=box, label="a"];
  a12_3 [shape=box, label="a"];
  a12_1 -> a12_2
  a12_2 -> a12_3
  a12_3 -> a12_2

  a03_1 [shape=box, label="a"];
  a03_2 [shape=box, label="a"];
  a03_3 [shape=box, label="a"];
  a03_1 -> a03_2
  a03_2 -> a03_3
  a03_3 -> a03_1
}

どれも展開すると、長さ 1 の配列の中に長さ 1 の配列が入っていて、そのなかにまた長さ 1 の配列が入っていて... という無限構造になる。その無限構造はどれも等しいので eql? が true になるが、hash 値が異なるものがあるために、hash の key としてはうまく動かない。

これを解決するために件の論文では cycle があったら hash 値はぜんぶ同じ値にしてしまう (そしてリニアサーチになる) というやりかたが書いてあるが、まぁ動くことは動くがそれを動くというのだろうか、という感じである。

#2

Ruby会議2009 で発表した。

セッションの時間が (40分ほど) 余ったので時間を貰い、最初に下書きして長すぎてあきらめて削った部分を話させてもらう。もともと、「なぜ」こういうデザインにしたのかという design decision を話したかったので、そこを中心に話す。

2009-07-20 (Mon)

#1

cyclic な構造の特徴をどうとらえるかということを考えると、「個々のノードのつながり」と「同じ特徴を持つノードの数」を無視するというのはどうか。

たとえば、以下を考えよう。

cyclic-arrays2.png

cyclic-arrays2.dot:

digraph g {
  a -> b
  a -> c
  a -> d
  c -> a

  a1 [label="a"]
  b1 [label="b"]
  c1 [label="c"]
  d1 [label="d"]
  a2 [label="a"]
  d2 [label="d"]
  a1 -> b1
  a1 -> c1
  a1 -> d1
  c1 -> a2
  a2 -> b1
  a2 -> c1
  a2 -> d2
}

(ごく最近の) ruby で等価性を調べると、true になる。

% ruby -ve '   
a0 = [:a]
b0 = [:b]
c0 = [:c]
d0 = [:d]
a0[1] = b0
a0[2] = c0
a0[3] = d0
c0[1] = a0

a1 = [:a]
b1 = [:b]
c1 = [:c]
d1 = [:d]
a2 = [:a]
d2 = [:d]
a1[1] = b1
a1[2] = c1
a1[3] = d1
c1[1] = a2
a2[1] = b1
a2[2] = c1
a2[3] = d2

p a0 == a1
'
ruby 1.9.2dev (2009-06-22 trunk 23820) [i686-linux]
true

ここで図に示したグラフで、ノードの中に書いてある名前をノードの特徴だとすると、a, b, c, d という種類のノードがある。

それが、つながりかたは気にしないことにして特徴を multi set にまとめると、{a,b,c,d} と {a, c, d, a, b, d} になる。ここで、数を気にしないことにして集合にすると、どちらも {a,b,c,d} になる。

そういう、ノードごとに特徴を調べて、すべてのノードの特徴の集合を作って hash を得る、のは動きそうである。

が、集合がノード数に比例したサイズになる可能性があるのが嫌だよなぁ。

どうせ最終的にはハッシュ値にしてしまうのだから、同じ値を何回混ぜ込んでも影響がでないようなうまい関数はないだろうか。

そういう関数としては、たとえば個々の特徴を bitwise or で混ぜていくというのが考えられるが、ハッシュとしてはあまり良いものになりそうにない。

なので、条件としてはもうひとつ、ハッシュとしてうまく使えるものというのが付け加わる。

2009-07-21 (Tue)

#1

色覚の多様性に配慮した案内・サイン・図表等用のカラーユニバーサルデザイン推奨配色セット(色のバリアフリーに配慮した色見本)

というのを見つけて、gnuplot の設定を変える。

color2.png

color2.gp:

set style line 1 lc rgb "#eb6110"
set style line 2 lc rgb "#68c8f2"
set style line 3 lc rgb "#f0908a"
set style line 4 lc rgb "#316ab3"
set style line 5 lc rgb "#a53d92"
set style line 6 lc rgb "#06af7a"
set style line 7 lc rgb "#fff100"
set style increment user 
plot -x, 1-x, 2-x, 3-x, 4-x, 5-x, 6-x, 7-x, 8-x, 9-x

推奨配色に RGB な値が書いてない理由は分かる、分かるのだが、RGB の適当な世界に住んでいるので直接適用できなくて不便ではある。

2009-07-22 (Wed)

#1

いままで、/proc にある fd を open すると他のプロセスがその fd を得られると思っていたのだが、ソケットは open できないようだ。

% uname -a
Linux nute 2.6.26-1-486 #1 Fri Mar 13 17:25:45 UTC 2009 i686 GNU/Linux
% ruby -v -rpp -rsocket -e '
s1, s2 = UNIXSocket.pair
n = "/proc/#{$$}/fd/#{s1.fileno}"
pp File.stat(n)
open(n)
'
ruby 1.9.2dev (2009-06-22 trunk 23820) [i686-linux]
#<File::Stat
 dev=0x4,
 ino=39817,
 mode=0140777 (socket rwxrwxrwx),
 nlink=1,
 uid=1000 (akr),
 gid=1000 (akr),
 rdev=0x0 (0, 0),
 size=0,
 blksize=4096,
 blocks=0,
 atime=1970-01-01 09:00:00 +0900 (0),
 mtime=1970-01-01 09:00:00 +0900 (0),
 ctime=1970-01-01 09:00:00 +0900 (0)>
-e:5:in `initialize': No such device or address - /proc/8423/fd/3 (Errno::ENXIO)
        from -e:5:in `open'
        from -e:5:in `<main>'
#2

ちょっと、reset packet を自分で横から出して TCP を切断することをやってみた。

まず、tcpdump を起動

client% sudo tcpdump -S port 8888

サーバを起動

server% socket -sqv 8888

クライアントを起動

client% telnet server 8888
Trying ddd.dd.ddd.dd...
Connected to server.
Escape character is '^]'.

改行を送ると、tcpdump がパケットを表示する

14:48:48.659615 IP client.40241 > server.8888: P 470802595:470802597(2) ack 294619377 win 92 <nop,nop,timestamp 4091273 1106022673>
14:48:48.659824 IP server.8888 > client.40241: . ack 470802597 win 5792 <nop,nop,timestamp 1106022886 4091273>

パケットの表示から sequence number を取り出し、hping2 を使って reset packet を送る

client% sudo hping2 -s 40241 -p 8888 -M 470802597 -R server

tcpdump では、reset packet は以下のように観察される

14:49:04.756468 IP client.40241 > server.8888: R 470802597:470802597(0) win 512

そうすると、サーバが終了する

socket: read: Connection reset by peer

サーバは (メッセージから想像できなくもないが) read が ECONNRESET なっており、それはサーバに strace をかけておくと観察できる

...
read(4, "\r\n", 8192)                   = 2
write(1, "\r\n", 2
)                     = 2
select(5, [0 4], NULL, NULL, NULL)      = 1 (in [4])
read(4, 0xbfffd910, 8192)               = -1 ECONNRESET (Connection reset by peer)
write(2, "socket: ", 8socket: )                 = 8
write(2, "read: Connection reset by peer\n", 31read: Connection reset by peer
) = 31
...
#3

そういえば、/proc とかの fd を open したときに、それが dup になるかどうかは OS によって違うんだよな。

試してみよう。

... FreeBSD には /proc が (マウントしてなければ) ないか。/dev/fd/1 を使おう。

GNU/Linux では、/dev/fd/1 を open した fd は file offset が共有されない。すなわち、これは dup ではない。

GNU/Linux% ruby -e '     
f1 = STDOUT
open("/dev/fd/1", "w") {|f2|
  f1.write "abcdef\n"
  f1.flush
  f2.write "123\n"
  f2.flush
}
' > /tmp/foo
GNU/Linux% cat /tmp/foo
123
ef

FreeBSD では、/dev/fd/1 を open した fd は file offset が共有される。すなわち、これは dup である (たぶん)。

FreeBSD% ruby -e '
f1 = STDOUT
open("/dev/fd/1", "w") {|f2|
  f1.write "abcdef\n"
  f1.flush
  f2.write "123\n"
  f2.flush
}
' > /tmp/foo
FreeBSD% cat /tmp/foo  
abcdef
123

このへんが、GNU/Linux でソケットが ENXIO になる理由に関連しているように思えなくもない。

2009-07-23 (Thu)

#1

思い立って、だまし絵展にいってきた。

パトリック・ヒューズの「水の都」という作品にはとても驚かされた。

2009-07-24 (Fri)

#1

しかし考えてみると、/dev/fd/1 などを open したときに dup 相当の動作になると、open に指定された flags が無視されるので、それはよろしくないかもしれない...

ふむ。FreeBSD はちょっと検査してるか。/dev/fd/1 (標準出力) を読み込みモードで open すると EACCESS になる。

FreeBSD% ruby -e 'STDERR.puts open("/dev/fd/1", "w")' > /tmp/nnn       
#<File:0x551d60>
FreeBSD% ruby -e 'STDERR.puts open("/dev/fd/1", "r")' > /tmp/nnn
-e:1:in `initialize': Permission denied - /dev/fd/1 (Errno::EACCES)
        from -e:1:in `open'
        from -e:1

だが、APPEND かどうかは検査していないようだ。追加モードで open したあと、てきとうに seek してから書き込むと、 seek したところに書き込まれる。追加モードになっていれば、write 時にファイル末尾に移動するのでこうはならない。

FreeBSD% ruby -e 'f = open("/dev/fd/1", "a"); f.seek 10; f.write "a"' > /tmp/nnn
FreeBSD% od -c /tmp/nnn
0000000   \0  \0  \0  \0  \0  \0  \0  \0  \0  \0   a                    
0000013

GNU/Linux では、/dev/fd/1 (標準出力) を読み込みモードで open できるし、実際に読み込める

GNU/Linux% ruby -e 'STDERR.puts open("/dev/fd/1").read.inspect' > /tmp/nnn
""

dup だと思うか、open し直しだと思うか、という違いなのだろうな

2009-07-28 (Tue)

#1

RubyKaigi2009 の咳さんの発表を (録画で) 見て、「コントロールブレイク」という言葉をはじめて聞いた。

しばらく調べて、思ったこと:

#2

単語数を数えることを考えよう。ファイル単位の処理後、以下のようなデータが生成されたとする。

apple 3               # ファイル A 中の apple の数
banana 2              # ファイル A 中の banana の数
apple 1               # ファイル B 中の apple の数
banana 2              # ファイル B 中の banana の数
banana 3              # ファイル C 中の banana の数

これを全体で集計すると以下になる。

apple 4
banana 7

これを行うのに、ふつうは Hash を使うわけであるが、単語がすごく多いことを想定して、Hash は使わずに済ますことを考える。どうするかというと、単語でソートしたデータを用意して集計するわけである。単語でソートしてあれば、順に読み込んで単語の変わり目で一つの項目の集計が終わるので、すべての単語をメモリに持つ必要がない。

もし、ソート結果が [[単語, [数, 数, ...]], ...] という木構造な形式になるのであれば、以下のように集計できる。

a = [
  ["apple", [3, 1]],
  ["banana", [2, 2, 3]],
]

a.each {|word, nums|
  n = 0
  nums.each {|num|
    n += num
  }
  p [word, n]
}

しかし、ソート結果が [[単語, 数], ...] という平坦な形式になるならどうしたらいいか。

a = [
  ["apple", 3],
  ["apple", 1],
  ["banana", 2],
  ["banana", 2],
  ["banana", 3],
]

つまりここでコントロールブレイクが必要になるのだが、それをやると、木構造な場合の (わかりやすい) プログラムよりずいぶんと厄介になる。

ここで、平坦なソート結果は、木構造なソート結果をトラバースした結果で、同じ情報を表現している。そして、集計はソート結果をまさにその順序でトラバースして行う。そうすると、平坦なソート結果のデータを使って、木構造前提な構造の集計プログラムを動作させることができてもおかしくないのではないか。

と、思って、ちょっと作って見たところ、以下のようなプログラムが動かせるようになった。

a = [
  ["apple", 3],
  ["apple", 1],
  ["banana", 2],
  ["banana", 2],
  ["banana", 3],
]

e = a.each
e.each_key(:first) {|word|
  n = 0
  e.each_value {|rec|
    n += rec.last
  }
  p [word, n]
}

内部的には、Enumerator#next (つまり Fiber) を使って、読み込みのループと集計のループを並行に動かしている。集計プログラムからみると、多重ループの各レベルで要素の読み込み (ないし先読み) が必要になるので Enumerator#next が必要になるのはやむを得ない、と思う。

class Enumerator
  # peek implementation

  alias original_next next

  def next
    if defined?(@unget_buf) && !@unget_buf.empty?
      return @unget_buf.pop
    end
    if defined?(@end_of_enumeration)
      raise StopIteration
    end
    begin
      val = original_next
    rescue StopIteration
      @end_of_enumeration = true
      raise
    end
    val
  end

  def unget(value)
    @unget_buf = [] if !defined?(@unget_buf)
    @unget_buf.push(value)
  end

  def peek
    val = self.next
    unget val
    val
  end

  # 

  def nested_keys
    if !Thread.current[:__enumerator_each_key__]
      Thread.current[:__enumerator_each_key__] = {}
    end
    h = Thread.current[:__enumerator_each_key__] 
    if !h[self]
      h[self] = []
    end
    a = h[self]
    begin
      yield a
    ensure
      h.delete self if a.empty?
    end
  end

  def push_key(extr, key)
    h = Thread.current[:__enumerator_each_key__] 
    a = h[self]
    begin
      a.push [extr, key]
      yield a
    ensure
      a.pop
    end
  end

  def each_key(key_extractor)
    if key_extractor.kind_of? Symbol
      sym_key_extractor = key_extractor
      key_extractor = lambda {|v| v.send sym_key_extractor }
    end

    nested_keys {|curr_keys|
      while true
        begin
          val = peek
        rescue StopIteration
          break
        end
        if curr_keys.any? {|extr, k| extr.call(val) != k }
          return
        end
        key = key_extractor.call(val)
        push_key(key_extractor, key) {|keys|
          yield key
          each_value {}
        }
      end
    }
  end

  def each_value
    nested_keys {|curr_keys|
      while true
        begin
          val = peek
        rescue StopIteration
          break
        end
        if curr_keys.any? {|key_extractor, key| key_extractor.call(val) != key }
          return
        end
        yield self.next
      end
    }
  end
end

しかし、配列やファイルでは外部イテレータを作るのは簡単なんだから、そういうインターフェースを用意しておけば、Enumerator#next はそっちを優先して使えるのではないだろうか。そうすれば、Fiber を避けられる

#3

ついでにマッチングも作ってみる。

a1 = [
  ["apple", 3],
  ["banana", 2],
]

a2 = [
  ["apple", 1],
  ["banana", 2],
]

a3 = [
  ["banana", 3],
]

Enumerator.sync_each(a1, :first, a2, :first, a3, :first) {|word, v1, v2, v3|
  v1.each {|v| p v }
  v2.each {|v| p v }
  v3.each {|v| p v }
}

#=>

["apple", 3]
["apple", 1]
["banana", 2]
["banana", 2]
["banana", 3]

まぁ、マージソートの 1ステップである。これまた複数の Enumerator を並行に動かすので Enumerator#next を避けられない。

def Enumerator.sync_each(*arguments)
  args = []
  while !arguments.empty?
    enum = arguments.shift
    key_extractor = arguments.shift
    enum = enum.to_enum if !enum.kind_of?(Enumerator)
    if key_extractor == nil
      key_extractor = lambda {|v| v }
    elsif key_extractor.kind_of? Symbol
      key_extractor = lambda {|sym| lambda {|v| v.send sym } }.call(key_extractor)
    end
    args << [enum, key_extractor]
  end

  curr = []
  index_list = []
  args.each_with_index {|arg, i|
    if arg
      enum, extr = arg
      begin
        val = enum.next
      rescue StopIteration
        arg = nil
      end
    end
    if arg
      curr << [val, extr.call(val)]
      index_list << i
    else
      curr << nil
    end
  }

  while !index_list.empty?
    min_index = index_list.min_by {|i| curr[i][1] }
    min_key = curr[min_index][1]
    values = []
    curr.each_with_index {|vk, i|
      if !vk
        values << []
      else
        v, k = vk
        if k != min_key
          values << []
        else
          a = [v]
          enum, extr = args[i]
          while true
            begin
              val = enum.next
            rescue StopIteration
              curr[i] = nil
              index_list -= [i]
            end
            break if !curr[i]
            key = extr.call(val)
            if min_key != key
              curr[i] = [val, key]
              break
            end
            a << val
          end
          values << a
        end
      end
    }
    yield min_key, *values
  end
end

毎回 min_by してるがこれはやりすぎで、優先順位付キューが欲しいところだ

#4

そうか、葉の並びから木を作り出すというのは、まさにパーザではないか。それまでの要素と異なるキーの要素が来たときにそれまでの部分を処理するというのは、先読みによってそれまでをまとめて部分木にまとめるとみなせる。

コントロールブレイクはパーザ。

2009-07-29 (Wed)

#1

「コントロールブレイク」という用語の起源を調べてみようと思って、Google Scholar で調べて見たところ、いくつかそれっぽい用例が見つかった

でも、用語に参照もついてないし、起源はよく分からない

2009-07-30 (Thu)

#1

ふと、バイナリヒープを書いてみる

#= binheap - binary heap library
#
#== Usage
#
#  require 'binheap'
# 
#  bh = BinHeap.new
#  bh.put 8
#  bh.put 2
#  bh.put 4
#  bh.get #=> 2
#  bh.get #=> 4
#  bh.get #=> 8
# 

class BinHeap
  private

  # 0 : root
  # 1 : 0 * 2 + 1
  # 2 : 0 * 2 + 2
  # 3 : 1 * 2 + 1
  # 4 : 1 * 2 + 2
  # 5 : 2 * 2 + 1
  # 6 : 2 * 2 + 2
  # 7 : 3 * 2 + 1
  # 8 : 3 * 2 + 2
  # 9 : 4 * 2 + 1
  #
  # 7 = 3 * 2 + 1
  # 8 = 3 * 2 + 2
  # 3 = (7 - 1) >> 1
  # 3 = (8 - 1) >> 1
  #
  # child1 = parent * 2 + 1
  # child2 = parent * 2 + 2
  # parent = (child - 1) >> 1

  def initialize(op=:<=>)
    @ary = []
    @op = op
  end

  def le(i,j)
    @ary[i].send(@op, @ary[j]) <= 0
  end

  def swap(i,j)
    tmp = @ary[i]
    @ary[i] = @ary[j]
    @ary[j] = tmp
  end

  def move_to_root(j)
    while 0 < j
      i = (j-1) >> 1
      return if le(i, j)
      swap(j, i)
      j = i
    end
  end

  def move_to_leaf(i)
    while true
      j = i*2+1
      k = i*2+2
      return if @ary.length <= j
      if @ary.length == k
        return if le(i, j)
        swap(i, j)
        i = j
      else
        return if le(i, j) && le(i, k)
        l = le(j, k) ? j : k
        swap(i, l)
        i = l
      end
    end
  end

  def get_internal
    data = @ary[0]
    @ary[0] = @ary.pop
    move_to_leaf(0)
    data
  end

  public

  def inspect
    "\#<#{self.class}: #{@ary.map {|v| v.inspect }.join(" ")}>"
  end

  def put(*args)
    args.each {|data|
      @ary << data
      move_to_root(@ary.length-1)
    }
  end

  def get
    return nil if @ary.empty?
    get_internal
  end

  class EmptyError < StandardError
  end

  def fetch
    raise BinHeap::EmptyError, "empty heap" if @ary.empty?
    get_internal
  end

end

put はともかくとして、get/fetch が破壊的というのはよろしくない。

伝統的には insert と delete_min なのか? だが、Ruby ではなんか感じが違う?

#2

wikipedia の Binary heap の項にリンクされていた、"Min-max heaps and generalized priority queues.". を眺めると、最小だけじゃなくて最大も取り出せるようにするのもそんなにむずかしくないようだ。

ふむ。もっと新しい論文もいろいろあるか。

ふたつの queue をマージする Meld という操作も考察されているようだ。

Insert, FindMin, FindMax, Delete, DeleteMin, DeleteMax, Meld あたりが高速にできるデータ構造は、けっこう便利かもしれない?


[latest]


田中哲