
AIチップを作る「光の工場」
半導体工場の真ん中に、「光の工場」を置く。
巨大な工場の中央に、巨大なリングがある。

そこでは、ただ機械が回っているわけではない。
電子を加速する。
光速に限りなく近い速度まで電子を加速し、その電子を強力な磁場の中へ送り込む。
そして電子を周期的に「蛇行」させる。
すると――
電子の運動から、光が生まれる。
しかも、その光は普通の光ではない。
半導体の最先端を切り拓く、極端紫外線――EUVだ。
これが、イーロン・マスクが構想する巨大半導体工場「Terafab」をめぐって注目されている、自由電子レーザー(FEL)という発想である。
現在のEUVリソグラフィーでは、レーザーで微小な錫の液滴をプラズマ化し、13.5 nmのEUVを作り出す方式が使われている。EUVは半導体の微細なパターンを形成するための重要な光だ。
一方、FELはまったく違う。
電子を加速する。
磁場で電子を揺さぶる。
その電子から生まれる光を利用する。
しかも、電子のエネルギーやアンジュレータの条件を変えることで、発生する光の波長を制御できる。
ここには、とても美しい物理がある。
一つの巨大な光源から、無数の露光装置へ
そして、私がこの構想で特に面白いと思ったのが、光源そのものよりも**「配置」**だ。
普通に考えれば、露光装置があれば、その装置ごとに光源が必要になる。
一台。
また一台。
さらに一台。
露光装置を増やせば、光源も増えていく。
ところが巨大なFEL光源を工場の中央に置いたらどうだろう。
中央に、
巨大な「光の発電所」
を作る。
そこからEUVを取り出し、複数の露光設備へ供給する。
つまり、
一つの巨大な光源で、工場全体を動かす。
これは発電所と電力網に少し似ている。
一軒一軒に発電所を置くのではない。
巨大な発電所を作り、そこから必要な場所へエネルギーを配る。
同じように、
一つの巨大な「光の源」を作り、そこから大量の半導体製造ラインへ光を配る。
この発想が、ものすごく面白い。
そして、ここからが本当のSFだ
もしこの考え方が量産工場で成立したら、半導体工場の姿そのものが変わるかもしれない。
工場の中央には加速器。
その中を電子が走る。
アンジュレータの磁石列を通過する。
そして、
13.5 nmの光が生まれる。
その光が巨大な工場の中へ供給される。
その先では、何台もの露光装置が一斉に動く。
シリコンウェハーが流れる。
回路が刻まれる。
さらに次の工程へ進む。
そしてまた、次のウェハーへ。
電子を走らせる。
光を作る。
その光で半導体を刻む。
なんとも不思議な製造工程だ。
物質を削るだけではない。
巨大な加速器で作った「光」を使って、ナノメートルの世界に回路を書き込んでいく。
FELによるEUV光源は非常に魅力的な可能性を持っている一方、量産半導体工場で実証済みの技術ではない。FELをEUVリソグラフィーの光源として使う研究・開発は進んでいるものの、商用量産で必要となる信頼性、安定性、コスト、光学系との統合など、越えるべき壁はまだ残っている。実際、米国では2026年にFEL-EUVプロトタイプ開発への1.5億ドルの支援が正式化された段階だ。
だからこそ、面白い。
これは完成した未来の話ではない。
「こんな方法で、半導体を作ることができるのか?」
という、巨大な技術実験でもある。
そして私は、この発想を見て一つの実験を作ってみた。
電子のエネルギーを変える。
アンジュレータの周期を変える。
磁場を変える。
すると、
発生する光の波長が変わる。
赤。
緑。
青。
紫外線。
そして――
EUV。
13.5 nm。
普段、私たちがスマートフォンやAIコンピューターの中で当たり前のように使っている半導体。
その極小の回路を生み出すために、実はその背後では、
電子を加速し、磁場で揺さぶり、光を作る。
そんな壮大な物理が働いている。
今回は、その「光が生まれる瞬間」を、ブラウザの中で実験できるシミュレーターにしてみた。
電子を走らせてみよう。
磁場を変えてみよう。
アンジュレータを調整してみよう。
そして、自分の手で――
「光を作ってみよう」。 ⚡️🔬
半導体の未来を考えるとき、見るべきものは巨大な工場だけではない。
その工場の中心で走る、
一筋の電子と、一筋の光
なのかもしれない。
ついに完成!アンジュレータ放射シミュレーターで「光を作る感動」を体験しよう!
🎉 物理の美しさを、あなたの指先で!
ついに、ついに完成しました!

自由電子レーザー(FEL)の基本原理を、あなたの指先で直感的に体験できる画期的なWebシミュレーターが誕生しました!
「加速器」と聞くと、なんだか難しそう…と思うかもしれません。でも、このシミュレーターを使えば、電子を加速して、磁場で曲げて、光を作るという壮大な物理現象を、まるでゲームのように楽しめるんです!
💡 なぜこのアプリを作ったのか?
私は常々思っていました。
「アンジュレータ放射の原理は、実はすごく美しいのに、数式だけじゃ伝わらない!」
電子を光速近くまで加速する
周期磁場で電子を蛇行させる
すると、驚くべきことに電磁波が放射される
電子エネルギーや磁場を調整することで、狙った波長の光を作れる
このプロセス、本当はすごくドラマチックなんです!
でも、従来の教材では数式が並ぶだけで、「実際にどうなるのか」がイメージしにくい。
だったら、作ってしまおう!
そう思って開発したのが、このシミュレーターです。
🚀 このアプリの最大のアピールポイント!
1. 物理計算をサボらない!

多くのシミュレーターは「それっぽいアニメーション」だけで終わってしまいます。でも、このアプリは本気で物理計算しています!
ローレンツ因子 γ
相対論的速度 β
アンジュレータパラメータ K
放射波長 λ_r
光子エネルギー E_γ
すべて実際の物理式で計算し、リアルタイムで表示します。
「適当な数値を表示しているだけ」なんてことではありません!
2. スライダーを動かすと、即座に結果が変化!

電子エネルギーを変えると…
「あ、波長が短くなった!」
アンジュレータ周期を変えると…
「おっ、Kが変わった!波長も変わった!」
アンジュレータ磁場を変えると…

「13.5 nmになった!EUVだ!」
こんな発見の喜びを、あなたも体験できます!
3. 13.5 nm EUVを「作る」体験!
半導体リソグラフィで使われる13.5 nmの極端紫外線(EUV)。
このアプリなら、「13.5 nm EUVを狙う」ボタンを押すだけで、その条件を自動設定!
実際にどうすれば13.5 nmの光が作れるのか、実験的に探求できるんです!
4. 可視光からX線まで、光のスペクトルを体感!
パラメータを調整すると、発生する光が変化します。
赤外線
可視光(赤・橙・黄・緑・青・紫)
紫外線
EUV
X線
「電子を速くしたら、波長が短くなって、紫外線になった!」
こんな体験、他ではできません!
5. 計算過程もすべて見える!
「計算を見る」ボタンを押せば、どの数値からどの結果が導かれたのかを詳細に表示。
「γ = 1 + 1000 / 0.511 ≈ 1957」
といった具合に、物理の因果関係を明確に追えます!
📖 シミュレーターの使い方:完全ガイド
ステップ1:アプリを起動
HTMLファイルを開くだけ!インターネット接続不要で、すぐに使えます。
ステップ2:パラメータを設定
左側のコントロールパネルで、以下のパラメータを調整できます。
電子エネルギー(0.1 ~ 20 GeV)
電子をどれだけのエネルギーで加速するか。
初期値:1.0 GeV
→ 大きくするほど、電子は光速に近づき、放射波長は短くなります!
アンジュレータ周期(5 ~ 100 mm)
磁石の配置周期。
初期値:20 mm
→ 小さくするほど、波長は短くなります!
アンジュレータ磁場(0.1 ~ 2.0 T)
磁石の強さ。
初期値:1.0 T
→ 強くするほど、アンジュレータパラメータ K が大きくなり、波長に影響します!
アンジュレータ周期数(5 ~ 50)
磁石がいくつ並んでいるか。
初期値:20
→ 多いほど、電子は長く蛇行運動を続けます!
ステップ3:計算結果を確認
右側のパネルに、以下の結果がリアルタイム表示されます。
初心者モード:
電子エネルギー
アンジュレータ磁場
アンジュレータ周期
発生波長
光の種類
上級モード:
ローレンツ因子 γ
速度比 β
電子速度 v
アンジュレータパラメータ K
放射波長 λ_r
光子エネルギー E_γ
周波数 f
光の分類
ステップ4:アニメーションで確認
「実験開始」ボタンを押すと、電子がアンジュレータを通過する様子をアニメーションで確認できます!
電子が左から飛んできます
アンジュレータ内部で蛇行運動
電磁波を放射
右側へ出ていきます
「停止」「リセット」ボタンで、いつでもコントロール可能!
ステップ5:軌道グラフで理解
下部のグラフで、電子の軌道 x(z) と磁場 B(z) を同時に表示。
「電子が磁場の周期に合わせて蛇行している」 ことが一目瞭然!
🎯 おすすめの遊び方
遊び方1:13.5 nm EUVを作ろう!
「13.5 nm EUVを狙う」ボタンを押してみましょう。
すると、13.5 nmのEUVを作るためのパラメータが自動設定されます。
「へえ、この条件で13.5 nmになるんだ!」
と、実感できます!
遊び方2:可視光を作ってみよう!
電子エネルギーを下げたり、アンジュレータ周期を大きくしたりして、可視光領域を目指してみましょう。
「450 nmになった!青い光だ!」
と、実際に青い色で表示されます!
遊び方3:逆算モードで実験!
「目標波長から設定を求める」 機能を使えば、「10 nmの光を作るには、どんな条件が必要?」 といった逆算実験も可能!
あなたの**「もしも」を実験**できます!
遊び方4:計算過程を追おう!
「計算を見る」 を開いて、スライダーを動かしてみてください。
「γが変わると、波長がこう変わるんだ!」
と、物理の因果関係が手に取るように分かります!

⚠️ 重要な注意点
このシミュレーターは、教育用に簡略化しています。
実際のFELの完全な自己増幅過程は計算していません
「電子が光る」は教育的な比喩です
実際には、加速された電子の運動によって電磁波が放射されます
でも、基本原理を理解するには最適です!
🌟 さあ、あなたも物理学者になろう!
このシミュレーターを使えば、あなたも今日から「光を作る研究者」 です!
電子を加速して
磁場で曲げて
光を作る

この壮大なプロセスを、あなたの指先で体験してください!
「電子を速くしたら、波長が短くなった!」
「13.5 nmにすると、EUVになった!」
こんな発見の喜びを、ぜひ味わってください!
物理は、楽しい。
光を作る感動を、あなたに。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>アンジュレータ放射シミュレーター</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f5f5 ;
color: #333 ;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
font-size: 1.8em;
color: #2c3e50 ;
margin-bottom: 10px;
}
.subtitle {
color: #7f8c8d ;
font-size: 0.95em;
}
.main-content {
display: grid;
grid-template-columns: 1fr 350px;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
}
}
.canvas-container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#simCanvas {
width: 100%;
height: 400px;
border: 2px solid #34495e ;
border-radius: 4px;
background: #1a1a2e ;
}
.controls {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.control-group {
margin-bottom: 20px;
}
.control-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
color: #2c3e50 ;
}
.control-group input[type="range"] {
width: 100%;
margin-bottom: 5px;
}
.control-group .value-display {
text-align: right;
font-size: 0.9em;
color: #7f8c8d ;
}
.results {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.result-item {
margin-bottom: 15px;
padding: 10px;
background: #ecf0f1 ;
border-radius: 4px;
}
.result-item .label {
font-size: 0.85em;
color: #7f8c8d ;
margin-bottom: 5px;
}
.result-item .value {
font-size: 1.4em;
font-weight: bold;
color: #2c3e50 ;
}
.result-item .unit {
font-size: 0.9em;
color: #7f8c8d ;
margin-left: 5px;
}
.wavelength-display {
text-align: center;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
margin-bottom: 20px;
}
.wavelength-display .wavelength-value {
font-size: 2.5em;
font-weight: bold;
margin: 10px 0;
}
.wavelength-display .region {
font-size: 1.2em;
background: rgba(255,255,255,0.2);
padding: 5px 15px;
border-radius: 20px;
display: inline-block;
margin-top: 10px;
}
.buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s;
}
.btn-primary {
background: #3498db ;
color: white;
}
.btn-primary:hover {
background: #2980b9 ;
}
.btn-secondary {
background: #95a5a6 ;
color: white;
}
.btn-secondary:hover {
background: #7f8c8d ;
}
.btn-success {
background: #27ae60 ;
color: white;
}
.btn-success:hover {
background: #229954 ;
}
.mode-toggle {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.mode-toggle button {
flex: 1;
}
.mode-toggle button.active {
background: #2c3e50 ;
color: white;
}
.calculation-details {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.calculation-details summary {
cursor: pointer;
font-weight: bold;
color: #2c3e50 ;
padding: 10px;
background: #ecf0f1 ;
border-radius: 4px;
}
.calculation-content {
margin-top: 15px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
line-height: 1.8;
}
.calculation-content .formula {
background: #f8f9fa ;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
border-left: 3px solid #3498db ;
}
.explanation {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.explanation h3 {
color: #2c3e50 ;
margin-bottom: 15px;
}
.explanation .step {
padding: 10px;
margin: 10px 0;
background: #e8f4f8 ;
border-radius: 4px;
border-left: 3px solid #3498db ;
}
.warning {
background: #fff3cd ;
border-left: 4px solid #ffc107 ;
padding: 15px;
margin: 15px 0;
border-radius: 4px;
}
.info {
background: #d1ecf1 ;
border-left: 4px solid #17a2b8 ;
padding: 15px;
margin: 15px 0;
border-radius: 4px;
}
.hidden {
display: none;
}
.preset-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.inverse-calc {
background: #f8f9fa ;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.inverse-calc h4 {
margin-bottom: 10px;
color: #2c3e50 ;
}
.inverse-calc input {
width: 100%;
padding: 8px;
border: 1px solid #ddd ;
border-radius: 4px;
margin-bottom: 10px;
}
#trajectoryCanvas {
width: 100%;
height: 200px;
border: 1px solid #ddd ;
border-radius: 4px;
background: white;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🔬 アンジュレータで光を作ってみよう</h1>
<p class="subtitle">高速電子を周期磁場に通すと、電子は蛇行運動をしながら電磁波を放射します。</p>
</header>
<div class="mode-toggle">
<button id="beginnerMode" class="active">初心者モード</button>
<button id="advancedMode">上級モード</button>
</div>
<div class="main-content">
<div class="canvas-container">
<canvas id="simCanvas"></canvas>
<canvas id="trajectoryCanvas"></canvas>
</div>
<div class="controls">
<h3 style="margin-bottom: 15px; color: #2c3e50 ;">パラメータ設定</h3>
<div class="control-group">
<label>電子エネルギー</label>
<input type="range" id="energySlider" min="0.1" max="20" step="0.1" value="1.0">
<div class="value-display"><span id="energyValue">1.0</span> GeV</div>
</div>
<div class="control-group">
<label>アンジュレータ周期 (λ<sub>u</sub>)</label>
<input type="range" id="periodSlider" min="5" max="100" step="1" value="20">
<div class="value-display"><span id="periodValue">20</span> mm</div>
</div>
<div class="control-group">
<label>アンジュレータ磁場 (B)</label>
<input type="range" id="fieldSlider" min="0.1" max="2.0" step="0.01" value="1.0">
<div class="value-display"><span id="fieldValue">1.00</span> T</div>
</div>
<div class="control-group">
<label>アンジュレータ周期数 (N)</label>
<input type="range" id="numPeriodsSlider" min="5" max="50" step="1" value="20">
<div class="value-display"><span id="numPeriodsValue">20</span></div>
</div>
<div class="control-group">
<label>アニメーション速度</label>
<input type="range" id="speedSlider" min="0.5" max="5" step="0.5" value="1">
<div class="value-display"><span id="speedValue">1.0</span>x</div>
</div>
<div class="buttons">
<button id="startBtn" class="btn-primary">▶ 実験開始</button>
<button id="pauseBtn" class="btn-secondary">⏸ 停止</button>
<button id="resetBtn" class="btn-secondary">↺ リセット</button>
</div>
<div class="preset-buttons">
<button id="preset13_5" class="btn-success">🎯 13.5 nm EUVを狙う</button>
</div>
<div class="inverse-calc">
<h4>目標波長から設定を求める</h4>
<input type="number" id="targetWavelength" placeholder="目標波長 (nm)" step="0.1">
<button id="inverseCalcBtn" class="btn-primary" style="width: 100%;">逆算実行</button>
</div>
</div>
</div>
<div class="wavelength-display" id="wavelengthDisplay">
<div style="font-size: 0.9em;">発生した光</div>
<div class="wavelength-value" id="wavelengthValue">--</div>
<div id="photonEnergy">光子エネルギー: -- eV</div>
<div class="region" id="wavelengthRegion">--</div>
</div>
<div class="results" id="resultsPanel">
<h3 style="margin-bottom: 15px; color: #2c3e50 ;">計算結果</h3>
<div id="beginnerResults">
<div class="result-item">
<div class="label">電子エネルギー</div>
<div class="value"><span id="resultEnergy">1.0</span><span class="unit">GeV</span></div>
</div>
<div class="result-item">
<div class="label">アンジュレータ磁場</div>
<div class="value"><span id="resultField">1.00</span><span class="unit">T</span></div>
</div>
<div class="result-item">
<div class="label">アンジュレータ周期</div>
<div class="value"><span id="resultPeriod">20</span><span class="unit">mm</span></div>
</div>
<div class="result-item">
<div class="label">発生波長</div>
<div class="value"><span id="resultWavelength">--</span><span class="unit" id="wavelengthUnit">nm</span></div>
</div>
<div class="result-item">
<div class="label">光の種類</div>
<div class="value"><span id="resultRegion">--</span></div>
</div>
</div>
<div id="advancedResults" class="hidden">
<div class="result-item">
<div class="label">ローレンツ因子 (γ)</div>
<div class="value"><span id="resultGamma">--</span></div>
</div>
<div class="result-item">
<div class="label">速度比 (β = v/c)</div>
<div class="value"><span id="resultBeta">--</span></div>
</div>
<div class="result-item">
<div class="label">電子速度 (v)</div>
<div class="value"><span id="resultVelocity">--</span><span class="unit">m/s</span></div>
</div>
<div class="result-item">
<div class="label">アンジュレータパラメータ (K)</div>
<div class="value"><span id="resultK">--</span></div>
</div>
<div class="result-item">
<div class="label">放射波長 (λ<sub>r</sub>)</div>
<div class="value"><span id="resultWavelengthAdv">--</span><span class="unit" id="wavelengthUnitAdv">nm</span></div>
</div>
<div class="result-item">
<div class="label">光子エネルギー (E<sub>γ</sub>)</div>
<div class="value"><span id="resultPhotonEnergy">--</span><span class="unit">eV</span></div>
</div>
<div class="result-item">
<div class="label">周波数 (f)</div>
<div class="value"><span id="resultFrequency">--</span><span class="unit" id="frequencyUnit">Hz</span></div>
</div>
<div class="result-item">
<div class="label">光の分類</div>
<div class="value"><span id="resultRegionAdv">--</span></div>
</div>
</div>
</div>
<details class="calculation-details">
<summary>📊 計算を見る</summary>
<div class="calculation-content" id="calculationContent">
<!-- 計算過程がここに表示される -->
</div>
</details>
<div class="explanation">
<h3>💡 なぜ光が出るの?</h3>
<div class="step">
<strong>1. 電子は磁場によって進行方向を曲げられます。</strong><br>
アンジュレータのN極とS極が交互に配置された磁場中を電子が通過すると、ローレンツ力によって電子の進行方向が左右に曲げられます。
</div>
<div class="step">
<strong>2. 方向が変化する電子は電磁波を放射します。</strong><br>
加速度運動する荷電粒子は電磁波を放射します(加速度放射)。電子が曲げられることは、つまり加速度運動をしていることを意味します。
</div>
<div class="step">
<strong>3. アンジュレータでは、この曲げを周期的に繰り返します。</strong><br>
N-S-N-S...と磁石を並べることで、電子は蛇行運動を繰り返しながら、各周期で電磁波を放射します。
</div>
<div class="step">
<strong>4. 電子から出た電磁波が条件を満たすと、強く揃った光になります。</strong><br>
特定の条件(共鳴条件)を満たすと、各周期で放射された電磁波が強め合い、波長の揃った強い光が発生します。
</div>
<div class="step">
<strong>5. これが自由電子レーザー(FEL)につながります。</strong><br>
実際のFELでは、多数の電子が電磁場と相互作用し、コヒーレントな放射が自己増幅されます。
</div>
<div class="info">
<strong>注意:</strong> この簡易シミュレーションはFELの完全な自己増幅過程を計算しているわけではありません。教育用に、電子の相対論的運動とアンジュレータ磁場による共鳴波長の計算を可視化しています。
</div>
<div class="warning">
<strong>教育的注意:</strong> 「電子が光る」という表現は比喩です。実際には、加速された電子の運動によって電磁波が放射されます。また、実際のFELでは多数の電子と電磁場の相互作用により光が増幅されます。
</div>
</div>
<div class="info">
<strong>表示倍率について:</strong> 実際の放射波長(例:13.5 nm)を物理スケールそのままに描画すると肉眼で見えないため、教育用に拡大表示しています。波面の間隔は実際の波長に対応していますが、画面上のスケールは誇張されています。
</div>
</div>
<script>
// ============================================
// 物理定数の定義
// ============================================
const C = 299792458; // 光速 [m/s]
const M_E = 9.1093837e-31; // 電子質量 [kg]
const E_CHARGE = 1.602176634e-19; // 電子電荷 [C]
const M_E_C2 = 0.511; // 電子静止エネルギー [MeV]
const H_PLANCK = 6.62607015e-34; // プランク定数 [J·s]
// ============================================
// グローバル変数
// ============================================
let isRunning = false;
let animationId = null;
let electronPosition = 0;
let time = 0;
let currentMode = 'beginner'; // 'beginner' or 'advanced'
// Canvas要素
const simCanvas = document.getElementById('simCanvas');
const simCtx = simCanvas.getContext('2d');
const trajCanvas = document.getElementById('trajectoryCanvas');
const trajCtx = trajCanvas.getContext('2d');
// Canvasサイズ設定
function resizeCanvases() {
simCanvas.width = simCanvas.offsetWidth;
simCanvas.height = simCanvas.offsetHeight;
trajCanvas.width = trajCanvas.offsetWidth;
trajCanvas.height = trajCanvas.offsetHeight;
}
resizeCanvases();
window.addEventListener('resize', () => {
resizeCanvases();
updateSimulation();
});
// ============================================
// 物理計算関数
// ============================================
// ローレンツ因子γを計算
// 入力:電子エネルギー [GeV]
// γ = 1 + E[MeV] / (m_e c² [MeV])
function calculateGamma(energyGeV) {
const energyMeV = energyGeV * 1000; // GeV → MeV
return 1 + energyMeV / M_E_C2;
}
// 速度比βを計算
// β = sqrt(1 - 1/γ²)
function calculateBeta(gamma) {
return Math.sqrt(1 - 1 / (gamma * gamma));
}
// 電子速度を計算
// v = βc
function calculateVelocity(beta) {
return beta * C;
}
// アンジュレータパラメータKを計算
// K = 0.934 × B[T] × λ_u[cm]
function calculateK(fieldTesla, periodMm) {
const periodCm = periodMm / 10; // mm → cm
return 0.934 * fieldTesla * periodCm;
}
// 放射波長を計算
// λ_r = λ_u / (2γ²) × (1 + K²/2)
// 入力:λ_u [m], γ, K
// 出力:λ_r [m]
function calculateRadiationWavelength(periodM, gamma, K) {
return (periodM / (2 * gamma * gamma)) * (1 + (K * K) / 2);
}
// 光子エネルギーを計算
// E = hc/λ
// 入力:λ [m]
// 出力:E [eV]
function calculatePhotonEnergy(wavelengthM) {
const energyJ = (H_PLANCK * C) / wavelengthM;
return energyJ / E_CHARGE; // J → eV
}
// 周波数を計算
// f = c/λ
function calculateFrequency(wavelengthM) {
return C / wavelengthM;
}
// 波長から光の分類を判定
function classifyWavelength(wavelengthM) {
const wavelengthNm = wavelengthM * 1e9;
if (wavelengthNm >= 750) return { region: '赤外線', visible: false };
if (wavelengthNm >= 620) return { region: '可視光(赤)', visible: true, color: 'red' };
if (wavelengthNm >= 590) return { region: '可視光(オレンジ)', visible: true, color: 'orange' };
if (wavelengthNm >= 570) return { region: '可視光(黄)', visible: true, color: 'yellow' };
if (wavelengthNm >= 495) return { region: '可視光(緑)', visible: true, color: 'green' };
if (wavelengthNm >= 450) return { region: '可視光(青)', visible: true, color: 'blue' };
if (wavelengthNm >= 380) return { region: '可視光(紫)', visible: true, color: 'violet' };
if (wavelengthNm >= 121) return { region: '紫外線', visible: false };
if (wavelengthNm >= 10) return { region: 'EUV(極端紫外線)', visible: false };
if (wavelengthNm >= 0.01) return { region: 'X線', visible: false };
return { region: 'ガンマ線', visible: false };
}
// 波長を適切な単位に変換して表示
function formatWavelength(wavelengthM) {
const wavelengthNm = wavelengthM * 1e9;
const wavelengthPm = wavelengthM * 1e12;
const wavelengthUm = wavelengthM * 1e6;
if (wavelengthPm < 1000) {
return { value: wavelengthPm.toFixed(2), unit: 'pm' };
} else if (wavelengthNm < 1000) {
return { value: wavelengthNm.toFixed(2), unit: 'nm' };
} else {
return { value: wavelengthUm.toFixed(2), unit: 'μm' };
}
}
// 周波数を適切な単位に変換して表示
function formatFrequency(frequencyHz) {
if (frequencyHz >= 1e15) {
return { value: (frequencyHz / 1e15).toFixed(2), unit: 'PHz' };
} else if (frequencyHz >= 1e12) {
return { value: (frequencyHz / 1e12).toFixed(2), unit: 'THz' };
} else if (frequencyHz >= 1e9) {
return { value: (frequencyHz / 1e9).toFixed(2), unit: 'GHz' };
} else {
return { value: frequencyHz.toFixed(2), unit: 'Hz' };
}
}
// 可視光の波長からRGB色を計算
function wavelengthToRGB(wavelengthNm) {
let r, g, b;
if (wavelengthNm >= 380 && wavelengthNm < 440) {
r = -(wavelengthNm - 440) / (440 - 380);
g = 0;
b = 1;
} else if (wavelengthNm >= 440 && wavelengthNm < 490) {
r = 0;
g = (wavelengthNm - 440) / (490 - 440);
b = 1;
} else if (wavelengthNm >= 490 && wavelengthNm < 510) {
r = 0;
g = 1;
b = -(wavelengthNm - 510) / (510 - 490);
} else if (wavelengthNm >= 510 && wavelengthNm < 580) {
r = (wavelengthNm - 510) / (580 - 510);
g = 1;
b = 0;
} else if (wavelengthNm >= 580 && wavelengthNm < 645) {
r = 1;
g = -(wavelengthNm - 645) / (645 - 580);
b = 0;
} else if (wavelengthNm >= 645 && wavelengthNm <= 750) {
r = 1;
g = 0;
b = 0;
} else {
r = 0;
g = 0;
b = 0;
}
// 輝度補正
let factor;
if (wavelengthNm >= 380 && wavelengthNm < 420) {
factor = 0.3 + 0.7 * (wavelengthNm - 380) / (420 - 380);
} else if (wavelengthNm >= 420 && wavelengthNm <= 700) {
factor = 1.0;
} else if (wavelengthNm > 700 && wavelengthNm <= 750) {
factor = 0.3 + 0.7 * (750 - wavelengthNm) / (750 - 700);
} else {
factor = 0;
}
return {
r: Math.round(255 * Math.pow(r * factor, 0.8)),
g: Math.round(255 * Math.pow(g * factor, 0.8)),
b: Math.round(255 * Math.pow(b * factor, 0.8))
};
}
// ============================================
// UI更新関数
// ============================================
function updateSimulation() {
// スライダーから値を取得
const energyGeV = parseFloat(document.getElementById('energySlider').value);
const periodMm = parseFloat(document.getElementById('periodSlider').value);
const fieldTesla = parseFloat(document.getElementById('fieldSlider').value);
const numPeriods = parseInt(document.getElementById('numPeriodsSlider').value);
// 物理計算
const gamma = calculateGamma(energyGeV);
const beta = calculateBeta(gamma);
const velocity = calculateVelocity(beta);
const K = calculateK(fieldTesla, periodMm);
const periodM = periodMm / 1000; // mm → m
const wavelengthM = calculateRadiationWavelength(periodM, gamma, K);
const photonEnergyEv = calculatePhotonEnergy(wavelengthM);
const frequencyHz = calculateFrequency(wavelengthM);
const classification = classifyWavelength(wavelengthM);
const wavelengthFormatted = formatWavelength(wavelengthM);
const frequencyFormatted = formatFrequency(frequencyHz);
// 表示更新
document.getElementById('energyValue').textContent = energyGeV.toFixed(1);
document.getElementById('periodValue').textContent = periodMm.toFixed(0);
document.getElementById('fieldValue').textContent = fieldTesla.toFixed(2);
document.getElementById('numPeriodsValue').textContent = numPeriods;
document.getElementById('speedValue').textContent = document.getElementById('speedSlider').value;
// 結果表示
document.getElementById('resultEnergy').textContent = energyGeV.toFixed(1);
document.getElementById('resultField').textContent = fieldTesla.toFixed(2);
document.getElementById('resultPeriod').textContent = periodMm.toFixed(0);
document.getElementById('resultWavelength').textContent = wavelengthFormatted.value;
document.getElementById('wavelengthUnit').textContent = wavelengthFormatted.unit;
document.getElementById('resultRegion').textContent = classification.region;
// 上級モード表示
document.getElementById('resultGamma').textContent = gamma.toFixed(2);
document.getElementById('resultBeta').textContent = beta.toFixed(6);
document.getElementById('resultVelocity').textContent = velocity.toExponential(3);
document.getElementById('resultK').textContent = K.toFixed(3);
document.getElementById('resultWavelengthAdv').textContent = wavelengthFormatted.value;
document.getElementById('wavelengthUnitAdv').textContent = wavelengthFormatted.unit;
document.getElementById('resultPhotonEnergy').textContent = photonEnergyEv.toFixed(2);
document.getElementById('resultFrequency').textContent = frequencyFormatted.value;
document.getElementById('frequencyUnit').textContent = frequencyFormatted.unit;
document.getElementById('resultRegionAdv').textContent = classification.region;
// 波長表示
document.getElementById('wavelengthValue').textContent =
wavelengthFormatted.value + ' ' + wavelengthFormatted.unit;
document.getElementById('photonEnergy').textContent =
'光子エネルギー: ' + photonEnergyEv.toFixed(2) + ' eV';
document.getElementById('wavelengthRegion').textContent = classification.region;
// 波長表示の背景色
if (classification.visible && classification.color) {
const rgb = wavelengthToRGB(wavelengthM * 1e9);
document.getElementById('wavelengthDisplay').style.background =
`linear-gradient(135deg, rgb(${rgb.r},${rgb.g},${rgb.b}) 0%, rgb(${Math.max(0,rgb.r-50)},${Math.max(0,rgb.g-50)},${Math.max(0,rgb.b-50)}) 100%)`;
} else {
document.getElementById('wavelengthDisplay').style.background =
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
}
// 計算詳細を更新
updateCalculationDetails(energyGeV, gamma, beta, velocity, K, wavelengthM, photonEnergyEv, frequencyHz);
// Canvas描画
drawSimulation(energyGeV, periodMm, fieldTesla, numPeriods, gamma, beta, K, wavelengthM);
drawTrajectory(periodMm, numPeriods, K);
// 物理的に非現実的な設定の警告
checkPhysicalValidity(energyGeV, fieldTesla, periodMm);
}
function updateCalculationDetails(energyGeV, gamma, beta, velocity, K, wavelengthM, photonEnergyEv, frequencyHz) {
const energyMeV = energyGeV * 1000;
const wavelengthFormatted = formatWavelength(wavelengthM);
const frequencyFormatted = formatFrequency(frequencyHz);
const html = `
<div class="formula">
<strong>ローレンツ因子 γ:</strong><br>
γ = 1 + E / (m<sub>e</sub>c²)<br>
γ = 1 + ${energyMeV.toFixed(1)} MeV / 0.511 MeV<br>
γ ≈ ${gamma.toFixed(4)}
</div>
<div class="formula">
<strong>速度比 β:</strong><br>
β = √(1 - 1/γ²)<br>
β = √(1 - 1/${gamma.toFixed(4)}²)<br>
β ≈ ${beta.toFixed(8)}<br>
v = βc = ${velocity.toExponential(4)} m/s<br>
v/c = ${beta.toFixed(8)}
</div>
<div class="formula">
<strong>アンジュレータパラメータ K:</strong><br>
K = 0.934 × B[T] × λ<sub>u</sub>[cm]<br>
K = 0.934 × ${document.getElementById('fieldSlider').value} × ${(parseFloat(document.getElementById('periodSlider').value) / 10).toFixed(2)}<br>
K ≈ ${K.toFixed(4)}
</div>
<div class="formula">
<strong>放射波長 λ<sub>r</sub>:</strong><br>
λ<sub>r</sub> = λ<sub>u</sub> / (2γ²) × (1 + K²/2)<br>
λ<sub>r</sub> = ${(parseFloat(document.getElementById('periodSlider').value) / 1000).toExponential(4)} / (2 × ${gamma.toFixed(4)}²) × (1 + ${K.toFixed(4)}²/2)<br>
λ<sub>r</sub> = ${wavelengthM.toExponential(4)} m<br>
λ<sub>r</sub> ≈ ${wavelengthFormatted.value} ${wavelengthFormatted.unit}
</div>
<div class="formula">
<strong>光子エネルギー E<sub>γ</sub>:</strong><br>
E<sub>γ</sub> = hc / λ<sub>r</sub><br>
E<sub>γ</sub> = (6.626×10⁻³⁴ × 2.998×10⁸) / ${wavelengthM.toExponential(4)}<br>
E<sub>γ</sub> ≈ ${photonEnergyEv.toFixed(4)} eV
</div>
<div class="formula">
<strong>周波数 f:</strong><br>
f = c / λ<sub>r</sub><br>
f = 2.998×10⁸ / ${wavelengthM.toExponential(4)}<br>
f ≈ ${frequencyFormatted.value} ${frequencyFormatted.unit}
</div>
`;
document.getElementById('calculationContent').innerHTML = html;
}
function checkPhysicalValidity(energyGeV, fieldTesla, periodMm) {
// 実際のFEL装置として現実的な範囲をチェック
const warnings = [];
if (energyGeV > 10) {
warnings.push('電子エネルギーが10 GeVを超えています。これは大型加速器施設レベルのエネルギーです。');
}
if (fieldTesla > 1.5) {
warnings.push('磁場が1.5 Tを超えています。通常の永久磁石アンジュレータでは実現が困難です。');
}
if (periodMm < 10) {
warnings.push('アンジュレータ周期が10 mm未満です。技術的に製作が困難な場合があります。');
}
// 警告表示は既存の警告要素を更新
// (簡易化のため、ここではコンソールに出力するのみ)
if (warnings.length > 0) {
console.log('物理的警告:', warnings);
}
}
// ============================================
// Canvas描画関数
// ============================================
function drawSimulation(energyGeV, periodMm, fieldTesla, numPeriods, gamma, beta, K, wavelengthM) {
const ctx = simCtx;
const width = simCanvas.width;
const height = simCanvas.height;
// 背景
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, width, height);
// アンジュレータ領域
const undulatorStart = width * 0.2;
const undulatorEnd = width * 0.8;
const undulatorWidth = undulatorEnd - undulatorStart;
const centerY = height / 2;
// 磁石の描画
const magnetWidth = undulatorWidth / (numPeriods * 2);
const magnetHeight = 60;
for (let i = 0; i < numPeriods * 2; i++) {
const x = undulatorStart + i * magnetWidth;
const isNorth = i % 2 === 0;
// 磁石の色
ctx.fillStyle = isNorth ? '#e74c3c' : '#3498db';
ctx.fillRect(x, centerY - magnetHeight - 20, magnetWidth - 2, magnetHeight);
ctx.fillRect(x, centerY + 20, magnetWidth - 2, magnetHeight);
// 磁極ラベル
ctx.fillStyle = 'white';
ctx.font = 'bold 14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(isNorth ? 'N' : 'S', x + magnetWidth / 2, centerY - magnetHeight / 2 - 10);
ctx.fillText(isNorth ? 'S' : 'N', x + magnetWidth / 2, centerY + magnetHeight / 2 + 30);
// 磁場ベクトル(矢印)
ctx.strokeStyle = isNorth ? '#e74c3c' : '#3498db';
ctx.lineWidth = 2;
const arrowX = x + magnetWidth / 2;
const arrowStartY = centerY - magnetHeight - 30;
const arrowEndY = isNorth ? arrowStartY - 15 : arrowStartY + 15;
ctx.beginPath();
ctx.moveTo(arrowX, arrowStartY);
ctx.lineTo(arrowX, arrowEndY);
ctx.stroke();
// 矢じり
ctx.beginPath();
ctx.moveTo(arrowX, arrowEndY);
ctx.lineTo(arrowX - 4, arrowEndY + (isNorth ? -6 : 6));
ctx.lineTo(arrowX + 4, arrowEndY + (isNorth ? -6 : 6));
ctx.closePath();
ctx.fill();
}
// 電子の軌道
if (isRunning || electronPosition > 0) {
const amplitude = 30 * K; // Kに比例して振幅変化
const electronX = undulatorStart + (electronPosition / 100) * undulatorWidth;
// 軌跡を描画
ctx.strokeStyle = '#00ff88';
ctx.lineWidth = 3;
ctx.shadowBlur = 10;
ctx.shadowColor = '#00ff88';
ctx.beginPath();
for (let i = 0; i <= electronPosition; i += 0.5) {
const x = undulatorStart + (i / 100) * undulatorWidth;
const z = (i / 100) * numPeriods * periodMm;
const y = centerY + amplitude * Math.sin(2 * Math.PI * z / periodMm);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
ctx.shadowBlur = 0;
// 電子本体
const electronY = centerY + amplitude * Math.sin(2 * Math.PI * electronPosition / 100 * numPeriods);
ctx.fillStyle = '#00ff88';
ctx.shadowBlur = 15;
ctx.shadowColor = '#00ff88';
ctx.beginPath();
ctx.arc(electronX, electronY, 8, 0, 2 * Math.PI);
ctx.fill();
ctx.shadowBlur = 0;
// 電子ラベル
ctx.fillStyle = 'white';
ctx.font = 'bold 12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('e⁻', electronX, electronY - 15);
// 放射波面(教育用の拡大表示)
if (electronPosition > 10) {
const wavelengthDisplay = wavelengthM * 1e9; // nm
const waveSpacing = Math.max(20, Math.min(80, wavelengthDisplay / 2)); // 表示用の波長間隔
ctx.strokeStyle = 'rgba(255, 255, 100, 0.3)';
ctx.lineWidth = 2;
for (let w = 0; w < 5; w++) {
const waveRadius = 30 + w * waveSpacing;
if (waveRadius < 200) {
ctx.beginPath();
ctx.arc(electronX, electronY, waveRadius, 0, 2 * Math.PI);
ctx.stroke();
}
}
}
}
// ラベル
ctx.fillStyle = 'white';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('電子入射', undulatorStart - 40, centerY);
ctx.fillText('光取出し', undulatorEnd + 40, centerY);
// アンジュレータ領域の枠
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 2;
ctx.strokeRect(undulatorStart, centerY - magnetHeight - 40, undulatorWidth, magnetHeight * 2 + 80);
}
function drawTrajectory(periodMm, numPeriods, K) {
const ctx = trajCtx;
const width = trajCanvas.width;
const height = trajCanvas.height;
// 背景
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
const margin = 40;
const plotWidth = width - 2 * margin;
const plotHeight = height - 2 * margin;
const centerY = margin + plotHeight / 2;
// 軸
ctx.strokeStyle = '#333';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(margin, centerY);
ctx.lineTo(margin + plotWidth, centerY);
ctx.moveTo(margin, margin);
ctx.lineTo(margin, margin + plotHeight);
ctx.stroke();
// 軸ラベル
ctx.fillStyle = '#333';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('アンジュレータ位置 z', width / 2, height - 5);
ctx.textAlign = 'right';
ctx.fillText('x', margin - 10, centerY + 5);
// 電子軌道
const amplitude = plotHeight / 3 * (K / 2); // Kに比例
ctx.strokeStyle = '#00ff88';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i <= plotWidth; i++) {
const z = (i / plotWidth) * numPeriods * periodMm;
const x = amplitude * Math.sin(2 * Math.PI * z / periodMm);
const canvasX = margin + i;
const canvasY = centerY - x;
if (i === 0) {
ctx.moveTo(canvasX, canvasY);
} else {
ctx.lineTo(canvasX, canvasY);
}
}
ctx.stroke();
// 磁場(背景)
ctx.strokeStyle = 'rgba(231, 76, 60, 0.3)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i <= plotWidth; i++) {
const z = (i / plotWidth) * numPeriods * periodMm;
const B = Math.sin(2 * Math.PI * z / periodMm);
const canvasX = margin + i;
const canvasY = centerY - B * plotHeight / 4;
if (i === 0) {
ctx.moveTo(canvasX, canvasY);
} else {
ctx.lineTo(canvasX, canvasY);
}
}
ctx.stroke();
// 凡例
ctx.fillStyle = '#00ff88';
ctx.fillRect(margin + 10, margin + 10, 20, 3);
ctx.fillStyle = '#333';
ctx.textAlign = 'left';
ctx.fillText('電子軌道 x(z)', margin + 35, margin + 15);
ctx.fillStyle = 'rgba(231, 76, 60, 0.5)';
ctx.fillRect(margin + 10, margin + 25, 20, 3);
ctx.fillStyle = '#333';
ctx.fillText('磁場 B(z)', margin + 35, margin + 30);
}
// ============================================
// アニメーション制御
// ============================================
function animate() {
if (!isRunning) return;
const speed = parseFloat(document.getElementById('speedSlider').value);
electronPosition += speed * 0.5;
if (electronPosition >= 100) {
electronPosition = 100;
isRunning = false;
}
updateSimulation();
animationId = requestAnimationFrame(animate);
}
function startAnimation() {
if (electronPosition >= 100) {
electronPosition = 0;
}
isRunning = true;
animate();
}
function pauseAnimation() {
isRunning = false;
if (animationId) {
cancelAnimationFrame(animationId);
}
}
function resetAnimation() {
pauseAnimation();
electronPosition = 0;
updateSimulation();
}
// ============================================
// プリセットと逆算
// ============================================
function setPreset13_5nm() {
// 13.5 nm EUVを目指す設定
// λ_r = λ_u / (2γ²) × (1 + K²/2)
// 目標:λ_r = 13.5 nm = 13.5e-9 m
const targetWavelength = 13.5e-9; // m
const energyGeV = 1.0; // GeV
const fieldTesla = 1.0; // T
// γを計算
const gamma = calculateGamma(energyGeV);
// Kを計算(仮の周期で)
let periodMm = 20;
let K = calculateK(fieldTesla, periodMm);
// λ_uを逆算
// λ_r = λ_u / (2γ²) × (1 + K²/2)
// λ_u = λ_r × 2γ² / (1 + K²/2)
const periodM = targetWavelength * 2 * gamma * gamma / (1 + (K * K) / 2);
periodMm = periodM * 1000; // m → mm
// スライダーを更新
document.getElementById('energySlider').value = energyGeV;
document.getElementById('fieldSlider').value = fieldTesla;
document.getElementById('periodSlider').value = Math.max(5, Math.min(100, periodMm));
updateSimulation();
}
function inverseCalculation() {
const targetWavelengthNm = parseFloat(document.getElementById('targetWavelength').value);
if (isNaN(targetWavelengthNm) || targetWavelengthNm <= 0) {
alert('有効な波長を入力してください');
return;
}
const targetWavelengthM = targetWavelengthNm * 1e-9;
const energyGeV = parseFloat(document.getElementById('energySlider').value);
const fieldTesla = parseFloat(document.getElementById('fieldSlider').value);
// γを計算
const gamma = calculateGamma(energyGeV);
// Kを計算(現在の周期で)
const periodMm = parseFloat(document.getElementById('periodSlider').value);
let K = calculateK(fieldTesla, periodMm);
// λ_uを逆算
// λ_u = λ_r × 2γ² / (1 + K²/2)
const periodM = targetWavelengthM * 2 * gamma * gamma / (1 + (K * K) / 2);
const newPeriodMm = periodM * 1000;
// 範囲チェック
if (newPeriodMm < 5 || newPeriodMm > 100) {
alert(`計算されたアンジュレータ周期 ${newPeriodMm.toFixed(2)} mm は設定範囲外です。\n電子エネルギーまたは磁場を変更してください。`);
return;
}
// スライダーを更新
document.getElementById('periodSlider').value = newPeriodMm;
updateSimulation();
alert(`目標波長 ${targetWavelengthNm} nm に基づき、アンジュレータ周期を ${newPeriodMm.toFixed(2)} mm に設定しました。`);
}
// ============================================
// モード切替
// ============================================
function setMode(mode) {
currentMode = mode;
if (mode === 'beginner') {
document.getElementById('beginnerMode').classList.add('active');
document.getElementById('advancedMode').classList.remove('active');
document.getElementById('beginnerResults').classList.remove('hidden');
document.getElementById('advancedResults').classList.add('hidden');
} else {
document.getElementById('advancedMode').classList.add('active');
document.getElementById('beginnerMode').classList.remove('active');
document.getElementById('advancedResults').classList.remove('hidden');
document.getElementById('beginnerResults').classList.add('hidden');
}
}
// ============================================
// イベントリスナー
// ============================================
// スライダー
document.getElementById('energySlider').addEventListener('input', updateSimulation);
document.getElementById('periodSlider').addEventListener('input', updateSimulation);
document.getElementById('fieldSlider').addEventListener('input', updateSimulation);
document.getElementById('numPeriodsSlider').addEventListener('input', updateSimulation);
document.getElementById('speedSlider').addEventListener('input', updateSimulation);
// ボタン
document.getElementById('startBtn').addEventListener('click', startAnimation);
document.getElementById('pauseBtn').addEventListener('click', pauseAnimation);
document.getElementById('resetBtn').addEventListener('click', resetAnimation);
document.getElementById('preset13_5').addEventListener('click', setPreset13_5nm);
document.getElementById('inverseCalcBtn').addEventListener('click', inverseCalculation);
// モード切替
document.getElementById('beginnerMode').addEventListener('click', () => setMode('beginner'));
document.getElementById('advancedMode').addEventListener('click', () => setMode('advanced'));
// ============================================
// 初期化
// ============================================
updateSimulation();
// バリデーション:既知のケースで計算を確認
console.log('=== 計算バリデーション ===');
// ケース1: 1 GeV, 20 mm, 1.0 T
const testGamma = calculateGamma(1.0);
const testBeta = calculateBeta(testGamma);
const testK = calculateK(1.0, 20);
const testWavelength = calculateRadiationWavelength(0.02, testGamma, testK);
console.log('ケース1 (1 GeV, 20 mm, 1.0 T):');
console.log(' γ =', testGamma.toFixed(2));
console.log(' β =', testBeta.toFixed(6));
console.log(' K =', testK.toFixed(3));
console.log(' λ_r =', (testWavelength * 1e9).toFixed(2), 'nm');
// ケース2: 13.5 nm EUV設定
setPreset13_5nm();
const presetWavelength = calculateRadiationWavelength(
parseFloat(document.getElementById('periodSlider').value) / 1000,
calculateGamma(parseFloat(document.getElementById('energySlider').value)),
calculateK(
parseFloat(document.getElementById('fieldSlider').value),
parseFloat(document.getElementById('periodSlider').value)
)
);
console.log('13.5 nmプリセット結果:');
console.log(' λ_r =', (presetWavelength * 1e9).toFixed(2), 'nm');
console.log('========================');
</script>
</body>
</html>
