2025年4月23日水曜日

ユリウス日

前回のこのブログでは, 暦象年表のトピックスにあったグレゴリオ暦か らユリウス日(jdn)への変換, またその逆の変換のプログラムの仕掛けを 解明した. かなり巧妙に出来ていて, ビューティフルコードといってもよ さそうである.

しかし私は, ビューティフルコードとしては, 見る人が「このプログラム は確かにこの問題を解く」と確信出来るようでありたいと願う. そういう こともあって, また最近 グレゴリオ暦の年月日からユリウス日への変換とそ の逆変換のプログラムを書いたのでお見せしたい. 先頃のブログ, Fixed Day Numberで紹介した式も活用している.

グレゴリオ暦の年月日からユリウス日への変換
def greg2jdn(y,m,d):
    def leap(y):
        return y%400==0 if y%100==0 else y%4==0
    return(
        y*365+y//4-y//100+y//400+d+1721425  
        -((4777-367*m)//12
          -(0 if m>2 else(1 if leap(y)else 2))))
まぁこれは見た通りであろう. 局所関数leap(y)は, y年に閏日があるとい う真理値を返す.

本体はy*365がy年末まで平年としての日数. y//4がその間の閏年の 数. -y//100が, 100の倍数の年の数で, グレゴリオ暦で閏にしない年の数. しかしこれでは, 1600年や2000年なども引くので, y//400で修正する.

その後+dは, 前月末までの日数にその月内の日数を足す. +1721425は -4712年からの値にする為の補正. 次の行の長い式が, 前月末までの 日数を計算する. このプログラムは一目瞭然だと思う.

例題をやってみる.
print(greg2jdn(1582,10,15)) => 2299161
    (グレゴリオ暦の初日)
print(greg2jdn(1,1,1)) => 1721426
    (Fixed Day Numberの初日) 
逆問題の方は, 局所関数としてminx(x,xs)を使う. これはxからxsの数を 次々と引き, この項を引くと値が負になる直前で止め, 引けた項数と, 最後の剰余を返す.
def minx(x,xs):
    i=0;
    while(i<len(xs)and x>=xs[i]):x=x-xs[i];i=i+1
    return [i,x]
例えば, 8と[1,3,5,7,9,11,13,15,17,19]が与えられた時, 先ず1を引いて 7が残り, 3を引いて4が残り, 5を引くと-1になるので, これは引かず, 1 と3の2項をを引き, 4が残る場合なので, [2,4]が返る. つまり前の項2が整数 平方根; 後の4が剰余. 8=22+4を示す. xを0から19まで 変えて計算してみる.
list(map(lambda x: 
         minx(x,[1,3,5,7,9,11,13,15,17,19]),
         range(0,20)))
とすると
[[0, 0], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1],
 [2, 2], [2, 3], [2, 4], [3, 0], [3, 1], [3, 2],
 [3, 3], [3, 4], [3, 5], [3, 6], [4, 0], [4, 1],
 [4, 2], [4, 3]]
となる. この逆変換の基本方針では, 残りの年の先頭が平年なら, 2月28日の後に 無理やりに2月29日に相当する日を挿入する. つまり残りのユリウス日が >=59なら, それに1を足すのである. 一見面倒なようだが, これが結構う まくいく.

ユリウス日からグレゴリオ暦の年月日への変換
def jdn2greg(jd):
    def minx(x,xs):
        i=0;
        while(i<len(xs)and x>=xs[i]):
            x=x-xs[i];i=i+1
        return [i,x]
    jd=jd-1721060
    a,jd=divmod(jd,146097)
    b,jd=minx(jd,[36525,36524,36524,36524])
    if(b>0 and jd>=59):jd=jd+1
    c,jd=divmod(jd,1461)
    d,jd=minx(jd,[366,365,365,365])
    if(d>0 and jd>=59):jd=jd+1
    m,jd=minx(
        jd,[31,29,31,30,31,30,31,31,30,31,30,31])
    return([a*400+b*100+c*4+d,m+1,jd+1])
局所関数minxは前述のもの.

jd=jd-1721060の引く値は, 0年1月1日からにユリウス暦だったして, そこを 起源にする変換である.

1721060=greg2jdn(1600,1,1)*5-greg2jdn(2000,1,1)*4

a,jd=divmod(jd,146097) は, 146097=365*400+97が4世紀の日数で, aが 4世紀が何回あったの数, jdは現在の4世紀になってからの日数.

b,jd=minx(jd,[36525,36524,36524,36524]) は, 現在の4世紀で今はどの 世紀かを見る. 0<=b<4. jdは世紀内の日数. ここでminxを使う.

bが1,2,3の時は, 先頭の年が100年で割れて閏年なのに閏日なしなので, if(b>0 and jd>=59):jd=jd+1 で閏があったように補正する.

c,jd=divmod(jd,1461) は世紀をまた4年組に分ける. 1461=365*4+1; 0<=c<25. d,jd=minx(jd,[366,365,365,365])

if(d>0 and jd>=59):jd=jd+1 は閏を入れる補正である.

こちらも例題.
jdn2greg(2299161) => [1582, 10, 15]
jdn2greg(1721426) => [1, 1, 1]
初めの例題でのa,b,c,dの値はa=3, b=3, c=20, d=2であった.

2025年4月19日土曜日

ユリウス日

今回のブログも国立天文台, 暦象年表のトピックスにあったユリウス日のアルゴリズムの続きだ. 今回は反対に, ある日のユリウス日からそのグレゴリオ暦の年月日を計算するものである.

直感的にもこちらの方が面倒なのは分る. 3つの数を掛けた積から, 元の数を 探すようなものだから.

その方法はこうだ.
def jdn2greg(jdn):
    l = jdn + 68569
    n = (4 * l) // 146097
    l = l - (146097 * n + 3) // 4
    i = (4000 * (l + 1)) // 1461001
    l = l - (1461 * i) // 4 + 31
    j = 80 *l // 2447
    d = l - 2447 * j // 80 
    l = j // 11
    m = j + 2 - 12 * l
    y = 100 * (n - 49) + i + l
    return [y,m,d]
前回のプログラムもそうだったが, 今回もif文がなく, 切り捨て除算ばかり なのが目立つ. O'BeirneのPuzzles & Paradoxesにある復活祭公式を 見る気分だ. (そういえば, 今年は明日4月20日が復活祭.)

まず分りそうなところから調べると, 下の方に m = j + 2 - 12 * lというのがあり, 前のプログラムのように, 1,2月は前年の11, 12月にし, 3〜12月を1〜10月に していることが分る.

また, y = 100 * (n - 49) + i + lから, nは, -4900年を基準にした世紀数 らしい. そこで前回同様の絵を描く. この図の左下, 1500年3月1日の(まだユリウス暦の 時代だがレゴリオ暦とした)jdnは青文字の2268983. この箱内の日数は赤文字の 2337552. 従って, -4900年3月1日のjdnは-68569. プログラムの先頭の 定数はこれであった. つまりlは-4900年からの日数であった.

次の行 n = (4 * l) // 146097 の146097は, 1460が365の4倍で, その 100倍にグレゴリオ暦の400年間の閏日を足したものである. lを100年の日数で割って 世紀数を出す代りに, lを4倍して400年の日数で割る. 理由は分らず. l = l - (146097 * n + 3) // 4 は世紀内の日数の計算.
i = (4000 * (l + 1)) // 1461001 は世紀内の年数の計算. ここも誤差処理の 為か, lを365では割らずに, 4000倍してから365の4000倍で割る. このiを 最後の年の計算で n+i のように使う.

l = l - (1461 * i) // 4 + 31 は年内の日数を計算する. 1461は閏日を 最後にした4年の日数で, 世紀内数に4年分を掛けてから4で割る. 最後に31を 足すのは, 1月を0ではなく, 1と出す為と思われる.

j = 80 *l // 2447 は 367 * m // 12 と同じように, 30と31が, 7,8月と 1,12月で連続する他は, 交互に現れるような式である.

f(l)= 80*l // 2447を, f(l)が1増えるようなlがどこからかを調べた表を 下に示す.

l = j // 11 で11, 12月(元々の1,2月)ならl=1, そうでなければ l=0に する.

最後の2行で年と月が正しく得られるという仕掛けであった.

誤差対策までは詳しく追わなかったが, このプログラムがどういう計算を しているかが判明したわけである. 中々面白い.

2025年4月17日木曜日

ユリウス日

国立天文台の暦計算室のウェブページには興味ある記事が満載だ. その中に2023年の暦象年表のトピックスとして「ユリウス日について」>というのがあった.

その最後の方に, グレゴリオ暦の年月日からその日のユリウス日(Julian Day Number)を 計算する式があり, その計算の仕方が面白かったので, 説明したい.

Pythonで書いたその式はこうだ. //は切り捨て除算である.
  def jdn(y, m, d):
    k = (14 - m) // 12
    return (
        (- k + y + 4800) * 1461 // 4 
        + (k * 12 + m -2) * 367 // 12 
        - ((- k + y + 4900) // 100) * 3 // 4
        + d - 32075)
私は普段, Schemeを使うので, Schemeで書くとこうだ.
  (define (jdn y m d)
  (let ((k (quotient (- 14 m) 12)))
  (+ (quotient (* (+ (- k) y 4800) 1461) 4)
     (quotient (* (+ (* k 12) m -2) 367) 12)
     (- (quotient (* (quotient (+ (- k) y 4900) 100) 3) 4))
     d -32075)))
暦の計算でなんとも鬱陶しいのは2月の末にあったりなかったりする閏日だ. 今回話題にする プログラムでは, 1年は3月から始まり, 1月と2月は前年の13月と14月にする扱いである.

また紀元前(BC, BCE)はマイナス何年の表記と1年ずれるのも面倒で, 暦の計算ではBCなど は使わないのが普通である.

さて, 「ある日」dのユリウス日jdn(d)の計算には, どこか基準になる日d0とjdnの分かって いる日d1を決め, d0からd1までの通日n(d1)と jdn(d1)の差(Δd)を, dの通日n(d)に足す.

Δd=jdn(d1)-n(d1), jdn(d)=n(d)+Δd

このプログラムでは, d0をユリウス日の開始日の-4712年より前で, 400で整除出来る年の -4800年3月1日として, その日からの通日を使う.

通日の計算は次の図を見て欲しい. いまjdnの分かっている日d1を, グレゴリオ暦への改暦 (1572年)後の1600年3月1日にし, 「ある日」dを1600年10月21日にして, ユリウス日の計算の進行を見よう. この日は 日本の歴史では, 旧暦の慶長5年9月15日, 関ヶ原の合戦があった.

私の「個人用電卓」 で10月21日のjdnを予め計算すると2305742である.

まず-4800年3月1日から1599年14月28日までの, 閏日を無視した長方形を 描く. 閏日は, 4行ごとの最後に入る. 100年毎や400年毎の補正は, 100行ごとの最後, 400行ごとの最後に入る. 長方形の最下行の右端に 飛び出しているのが, 1600年2月(1599年14月)29日で, 1600年は100 で整除出来るが, 400年でも整除出来るから, 閏日は入れる.

365*6400=2336000
4年毎の閏日は 6400/4=1600,
100年毎の閏日は 6400/100=64,
400年毎の閏日は 6400/400=16.

従ってこの(多少の飛出しのある)長方形は, 233600+1600-64+16=2337552. 左下の赤字の値で, 3月1日の 通日である.

次は1600年内の計算で, 3月0日から「ある日」までの通日を計算する. それには3月0日から m月0日までの通日が必要になる. これに,

f(n)=367*n//12

が使えるかもしれない. nを0,1,2,...と替えながらこの値と, f(n+1)とf(n)との差Δfを 計算してみると, 次のようになる.

Δfは赤線のように30と31の12個の列が循環し, 途中31が連続する箇所が2回ある. これが7月, 8月および, 12月, 13月に相当するわけで, mの行に対応する月を記入した. f(n)が各月までの通日になる. 3月までの通日を 0にしたいところだが, それは最後の補正の時にやることにして, これを使う.

大体の様子が分ったので, もう一度プログラムを見る.
  def jdn(y, m, d):
    k = (14 - m) // 12
    return (
        (- k + y + 4800) * 1461 // 4 
        + (k * 12 + m -2) * 367 // 12 
        - ((- k + y + 4900) // 100) * 3 // 4
        + d - 32075)
最初のkの値は, mの1から12に対して, 1,1,0,0,0,0,0,0,0,0,0,0,0,0 となり, 1月, 2月を別扱いにする時に使う.
次の行, (- k + y + 4800) * 1461 // 4 は, y - k で, 1,2月を前年に繰上げ, -4800を足して基準を-4800年にし, 1461(=365+365+365+366)を掛けて4で 切り捨て除算し, 4年毎の閏日を処理する.

次の行, + (k * 12 + m -2) * 367 // 12 は. m=3, 4, ..., 12を. 1, 2, ..., 14に変換して, m月0日までの通日を得る. 但し3月の欄で見るように30日多い. これは Δに組み込むことにする.

その次の行は100年の補正である. 年を100で割り世紀ごとの補正にする. 0,1,2,3,...,7 について, * 3 // 4をやると, 0, 0, 1, 2, 3, 3, 4, 5,...が得られる. 1,2,3と 増えるのは, 100で割った値が4で割れないで, 閏にしない年, 2行上では4で割れる年はいつも閏にしていたのから これを引いて補正した. 0,0や3,3のように繰り返すのは閏にする年で, 補正にはならない. ただこれを見ると, 補正しないのは-4800年の次の世紀年で, これでは1世紀遅すぎる. その修正のため, ここでは4800ではなく, 4900を足すのである.

改で左下を見ると赤字は前年までの通日, オレンジ色は3月1日までの通日, 従ってn(d1)は 2337583になる. 一方3月1日のユリウス日jdn(d1)は2305508でΔは プログラムにあるように, 32075になる.

1600年10月21のjdnは 2337552+244+21-32075=2305742 と得られた.

2025年3月14日金曜日

暦の使い方

私は暦が好きなので, Shakespeareの劇を読んでいても「暦」の文字があると立ち止ってしまう.

例えば, 誰でも思い出すであろう「夏の夜の夢」3幕1場で, 6人の職人が森の中で芝居の稽古をする場面:
スナウト おれたちが芝居をやる晩に月は出るのか?
ボトム 暦だ, 暦だ! 今年一年の暦を見て, 月が出るか調べるんだ, 月は出るか.
クインス うん, その晩月は出る.
(ここでの引用は, 私と同時代人である小田島さんの訳による.)

旧暦の国の人なら, 日付けで月のありなしは分る.
1日の前後は月出は夜明, 月入は夕方
8日の前後は月出は夜中, 月入は昼間
15日の前後は月出は夕方, 月入は夜明
22日の前後は月出は昼間, 月入は夜中
である. だから明治5年12月2日, 旧暦の最後の日は, 地球照で鎌のように細く光る三日月が 西の空にあったに違いない.

英国は, というかヨーロッパの殆んどは太陽暦だったから, 月の様子を知るには 暦が必要だったらしい.
しかしまだ疑問がある. 今我々の手元にある普通の暦は, 日付と曜日の対応を示 すのが主目的で, 次に祝日, 二十四節気, 雑節, さらに詳しいのには大安や友引の六曜の日付 も記載がある程度. 日出, 日入, 月出, 月入の時刻や月相は殆んど書いて ない. 天体の情報があるのは天文暦almanacだが, 英文「夏の夜の夢」の 当該部分はcalendarになっている.

東京の毎日の日出入, 月出入は, 理科年表の暦部や 暦象年表あるいは新聞の隅をよく見なければならない.

ところが1幕1場では:
シーシュース 楽しい日々をあと四日すごせば
新月の宵となる.
ヒポリタ そうすれば新月が, 天高く引きしぼられた
銀の弓のようにかかり, 私たちの婚儀の夜を
見まもってくれるでしょう.

だから, いつが新月か知る人は知っていた. 婚儀の日は新月なのに 「うん, その晩月は出る」というShakespeareの無頓着ぶりも楽しい.

ユリウス暦を採用した張本人「ジュリアス・シーザー」2幕1場では:

ブルータス そうだ, 明日は三月十五日であったかな?
ルーシャス さあどうでしたか.
ブルータス 暦を調べて, すぐ知らせてくれぬか.
ルーシャス かしこまりました.
ルーシャス 旦那様, 三月は十四日すんだところです.

正確に管理された日めくりカレンダーでなければ, 今日が何日かを知るの も普通には難しい. 最近ならスマホが手元にあれば分るが... さらにローマの暦の 日付は非常にややこしいから, 何日経過したかを彼等はどう計算したのか 興味がある.
ローマ暦のややこしい日付けに関しては この解説を見て欲しい.
その3幕1場で:
シーザー 三月十五日がきたぞ.
占い師 きましたが, シーザー, まだすぎさってはいません.

この「まだすぎさってはいません」がずっしり重い.

Shakespeareの史劇に登場する薔薇戦争時代で最も有名で残忍で話題も多いのは 「リチャード三世」であろう. 最近でも遺骨が発見されて改葬されたり, エドワード四世 の2人の王子はロンドン塔で殺害されたのではないというニュースがあったりした.

「リチャード三世」3幕8場:
リチャード おい, 暦をとってくれ.
だれか今日太陽を見たものはおるか?

これは日出の時刻を知ろうとしたものらしい.

「冬物語」の4幕4場, ごろつきのオートリカスが道化に, お前は酷い目に会うぞと驚かす場面 では:
オートリカス 一年中で一番暑い日と暦に出ている日に煉瓦塀にもたせかけられ, 太陽の カッと燃える目に見つめられながら, ...

7月22日に大暑と書いてあるようなことをオートリカスはいったのだろう か. 理科年表の「気温の最高および最低記録」のようなものを指すのか.
ついでだが, 昔の人は時刻を何で知ったか? ヨーロッパの町にいると, 教会の 鐘の音で「ヨーロッパに来たなぁ」と思う. でも教会から遠い戦場では, 太陽の 方向以外に時刻を知るのは難しかったであろう.

ロミオとジュリエット 1幕1場:
ベンヴォーリオ おはよう, ロミオ.
ロミオ 早いのか, まだ?
ベンヴォーリオ 九時だよ.

近くの教会の鐘が鳴ったか.

再び暦に戻って:
「マクベス」 4幕1場, マクベスといい合った魔女や幻影が消えた後:
マクベス どうした? 行ってしまったか? 畜生, この忌ましい時め, 暦の つづくかぎり呪われるがいい!

自然の「天地の続く限り」ではなく, 人工の「暦の続く限り」というのが 意外な発想である. Shakespeareの暦についての考えが少しは分ったように思う.

2025年1月26日日曜日

月齢カレンダー

私がこの数年愛用している予定表の話はこのブログに何回か書いた. ところで, 今年の予定表の表紙にある週日定数も月齢定数も共に0である. つまり下のようなのだ.

そうするとこの次にこうなるのは何年後かも気になる.

y年の週日定数, week(y)と月齢定数, moon(y)は次の式で計算する.

def week(y): return (y-2000+(y-2000)//4 
    +4)%7
def moon(y): return (((y-11)%19)*11)%29
そこで次に(0,0)になる年を計算してみたら, なんと2272年; 247サロス周期の後であった. 今年は昭和100年, 戦後80年, 阪神淡路大震災30年といわれるが, 暦屋にとっても誠に貴重な年なのである.

この表を見た序でに, 今年の復活祭を調べよう. まず3月21日の月齢は21. 次の満月は月齢45として4月14日. その日の曜日は1で月曜だから, 日曜はその6日後で4月20日になる.

もうひとつ序でに. Winning WaysのDoomsdayは3月0日の曜日なので, 今年は5, つまり金曜日である.

2024年8月4日日曜日

Heronの式

前回のブログ「四面体の体積」に名称だけ出てきたHeronの式というのがある. 三角形の3辺の長さをそれぞれa, b, cとし, (a+b+c)/2をSとすると, その三角形の 面積は√S(S-a)(S-b)(S-c)というのである.

たとえば, 辺の長さが3,4,5のPythagoras三角形ではS=6だから, 平方根号の中は 6×3×2×1=36で, 面積は6だ. 右の図, 1辺が2の正三角形では, S=3で, 根号内は3×1×1×1=3で, 面積は√3である.

Heronの式を覚えた時, これが正しいという証明を見たかどうかは分らない. 勿論, Sの次元は長さ, S-a,S-b,S-cも長さなので, 根号内の次元は長さの4乗; 開平すると 2乗になり, つまり面積だ. そういえば, 前回のブログにあった, 四面体の体積を6本の 辺の長さから求める式も体積の次元になっている. あたりまえだ.

でも定量的に合っているかを調べるのが今回の頭の体操である. 方針としては, 根号の あるのがウンシャンだから, 面積の二乗の値を考えることにする. 例題の三角形は こういう頂点名と辺名を持つ. 計算の途中で時々数値的にチェックするのにもこれを 使う. この三角形は∠Aが直角なので, 面積は√8×√18/2=6だ.

式を使わざるを得ないから, Texの出力のpdfにも助けてもらおう.

式(0)はHeronの式で, 2乗すると(1). Sを定義で戻し, 展開したのが式(2)である. この程度 の計算はWolframAlphaにやって貰う.

三角形ABCの各頂点の対辺をa,b,cとすると, その各々の2乗もpdfにある通り. これを 式(2)の分子に入れて展開するわけだが, これは中々面倒である. WolframAlphaも 時間がかかるとか文句をいうし, 我が家のRapsberry PiのMathematicaは今は ネットに繋っていないので, 使い勝手が悪い. 結局は多項式の乗算のプログラムを Schemeでちょこっと作り, それで展開した. 時々例題の値を入れて験算する.

そして展開した結果は項数21のpdfにある式である. これも目がくらくらするから, xixjykylについて, ijと klの対の項の係数を表にしてみた. さらに例題の座標で験算すると576. 16で 割ると36だ. しめしめ.

さてこの式は本当に三角形の面積であろうか. 頂点の座標(x0y0), (x1y1),(x2y2)の三角形の 面積(頂点の回り方によってはその負数)の式は, 幸いにも私は高校生の頃から知っている. 次のpdfにある式だ. 行列式を計算式にしてその分子の2乗を展開すると, またもや項数21の多項式が得られた. 今回もその係数の表を作るとブラボー! 先の表の1/4の値である. という訳けで Heronの式は面積になるのであった.

だが待てよ. この行列式はなぜ正しいのか. こちらも気になったので, 計算してみた.

図の三角形の, 座標軸に平行な線での外接長方形を作る. それから長方形の辺と 三角形の辺で作る3個の三角形の面積を引く. そういう式を作ってみる.

WoflramAlpha様に

Expand[(x2-x1)(y0-y2)-(x0-x1)(y0-y1)/2-(x2-x1)(y1-y2)/2-(x2-x0)(y0-y2)/2]

と伺いを立てたら

(x0y1)/2-(x0y2)/2-(x1y0)/2+(x1y2)/2+(x2 y0)/2-(x2y1)/2

のお告げが出た. 整理すると

(x0y1+x1y2+x2y0-x0y2-x1y0-x2y1)/2

となり, 行列式の値と一致している. もう一度ブラボー! ちゃんとした証明でもないが, 私にとっては十分納得出来る頭の体操であった.

2024年8月1日木曜日

四面体の体積

筑波大の三谷さんのツィッターに四面体の体積を計算する話があった.

角封筒の上の辺の中央と下の辺の両端を結ぶ折目をつけ, それを山折りにすると, 合同な 二等辺三角形4枚による四面体が出来る. 元の封筒の横と縦が1と√2の時の 体積を計算せよという問題である. ちょっとやってみたところ, 存外簡単であった.

下の図で, 赤い線は元の封筒を展開したもの. 下の辺と上の辺は繋っている. この斜めの 線を谷折りにすると, ABCを底面, ACD'を手前の面, ABDを向うの面, 辺DEとD'E'は 繋っていて斜面だが上の面になる.

錐体の体積を計算するには, 底面積と高さが必要だが, 底面積は簡単だ. 辺BCが1, 辺OAは辺ABが√2だから, √7/2である.

高さについては, 三角形ABDは, 辺ABを折目として頂点Dが起き上がるから, Dに対応する 平面上の点はABと直交する破線で示す線に沿って下がり, AO上のGまで来ると下から上って来たD’と 出会って頂点になる. GAの距離はADの距離にtan αを掛けたものだが, tan αはOB/OAだしADは1だから, GA=1/√7である. よって 高さは√6/√7.

求める体積は (√7/2)*(√6)/((√7)*2*3)=1/2√6

三谷さんのツィッターにはヒントと称して, 四面体の6辺の長さからその体積を 求める式があった. 平面三角形のHeronの公式の三次元版である. そこにある式の 添字の付け方を, 私流に修正したのが次の図である.

このように添字を付けると, 四面体の体積Vの2乗は
の一番上の式で得られる. しかしこれも目がくらくらするので, 始めの3項と終りの4項を それぞれ6変数のtfと3変数のtgに分け, 2行目以下のように定義する. 最初のV2を計算する式を書いたLatexは次の様だ. 計算式と同様に分解してある.
\newcommand{\sq}[1]{l_#1^2}
\newcommand{\tf}[6]{\sq#1\sq#2(\sq#3+
\sq#4+\sq#5+\sq#6-\sq#1-\sq#2)}
\newcommand{\tg}[3]{-\sq#1\sq#2\sq#3}
$V^2=\frac{1}{144}[\tf012345+\tf234501\\
+\tf450123\tg025\tg034\tg124\tg135]$
これを使ってpythonで計算したのが次だ.
import math
def sq(x):
  return x*x
def tf(a,b,c,d,e,f):
  return sq(a)*sq(b)
    *(sq(c)+sq(d)+sq(e)+sq(f)-sq(a)-sq(b))
def tg(a,b,c):
  return -sq(a)*sq(b)*sq(c)
def v2(l0,l1,l2,l3,l4,l5):
  return (tf(l0,l1,l2,l3,l4,l5)+tf(l2,l3,l4,l5,l0,l1)
    +tf(l4,l5,l0,l1,l2,l3)
    +tg(l0,l2,l5)+tg(l0,l3,l4)+tg(l1,l2,l4)
    +tg(l1,l3,l5))/144
l0=1;l1=1;l2=math.sqrt(2);l3=math.sqrt(2)
l4=math.sqrt(2);l5=math.sqrt(2)
print(math.sqrt(v2(l0,l1,l2,l3,l4,l5)))

=> 0.20412414523193143
この値は確かに1/2√6 である.

ところで, 最初の封筒の四面体の体積は, 形状が特殊であったせいか, 簡単であった. 底面の3点の座標と, その各点から頂点までの距離が与えられている時にも体積は 得られるだろうか. 以下はその計算である. 式が沢山あるので, Latexでpdfに したもので示す. 底辺の3点, A, B, Cの座標をそれぞれ(x0,y0,0), (x1,y1,0),(x2,y2,0); 頂点Dの座標を(x,y,z)とする. A,B,Cから頂点までの距離をそれぞれ l0,l1,l2とする.
今回もpythonで計算すると
x0=0;y0=-1/2;x1=math.sqrt(7)/2;y1=0;x2=0;y2=1/2
l0=math.sqrt(2);l1=1;l2=math.sqrt(2)
a0=2*(x0-x1); b0=2*(y0-y1)
c0=sq(l1)-sq(l0)+sq(x0)-sq(x1)+sq(y0)-sq(y1)
a1=2*(x1-x2); b1=2*(y1-y2)
c1=sq(l2)-sq(l1)+sq(x1)-sq(x2)+sq(y1)-sq(y2)

x=(c0*b1-c1*b0)/(a0*b1-a1*b0)
y=(a0*c1-a1*c0)/(a0*b1-a1*b0)

z=math.sqrt(sq(l0)-sq(x0-x)-sq(y0-y))
print(x,y,z)

#=> 0.9449111825230684 0.0 0.9258200997725514
期待通りの値が得られた. でもこんな実数の値より, 1/2√6 の方が嬉しい.