@kernel.chat/kbot 3.41.0 → 3.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/agent-teams.d.ts +1 -1
- package/dist/agent-teams.d.ts.map +1 -1
- package/dist/agent-teams.js +36 -3
- package/dist/agent-teams.js.map +1 -1
- package/dist/agents/specialists.d.ts.map +1 -1
- package/dist/agents/specialists.js +20 -0
- package/dist/agents/specialists.js.map +1 -1
- package/dist/auth.d.ts +5 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/channels/kbot-channel.js +8 -31
- package/dist/channels/kbot-channel.js.map +1 -1
- package/dist/cli.js +44 -11
- package/dist/cli.js.map +1 -1
- package/dist/completions.d.ts.map +1 -1
- package/dist/completions.js +7 -0
- package/dist/completions.js.map +1 -1
- package/dist/digest.js +1 -1
- package/dist/digest.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +132 -92
- package/dist/doctor.js.map +1 -1
- package/dist/doctor.test.d.ts +2 -0
- package/dist/doctor.test.d.ts.map +1 -0
- package/dist/doctor.test.js +432 -0
- package/dist/doctor.test.js.map +1 -0
- package/dist/email-service.d.ts.map +1 -1
- package/dist/email-service.js +1 -2
- package/dist/email-service.js.map +1 -1
- package/dist/episodic-memory.d.ts.map +1 -1
- package/dist/episodic-memory.js +14 -0
- package/dist/episodic-memory.js.map +1 -1
- package/dist/learned-router.d.ts.map +1 -1
- package/dist/learned-router.js +29 -0
- package/dist/learned-router.js.map +1 -1
- package/dist/tools/email.d.ts.map +1 -1
- package/dist/tools/email.js +2 -3
- package/dist/tools/email.js.map +1 -1
- package/dist/tools/hypothesis-engine.d.ts +2 -0
- package/dist/tools/hypothesis-engine.d.ts.map +1 -0
- package/dist/tools/hypothesis-engine.js +2276 -0
- package/dist/tools/hypothesis-engine.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +11 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/lab-bio.d.ts +2 -0
- package/dist/tools/lab-bio.d.ts.map +1 -0
- package/dist/tools/lab-bio.js +1392 -0
- package/dist/tools/lab-bio.js.map +1 -0
- package/dist/tools/lab-chem.d.ts +2 -0
- package/dist/tools/lab-chem.d.ts.map +1 -0
- package/dist/tools/lab-chem.js +1257 -0
- package/dist/tools/lab-chem.js.map +1 -0
- package/dist/tools/lab-core.d.ts +2 -0
- package/dist/tools/lab-core.d.ts.map +1 -0
- package/dist/tools/lab-core.js +2452 -0
- package/dist/tools/lab-core.js.map +1 -0
- package/dist/tools/lab-data.d.ts +2 -0
- package/dist/tools/lab-data.d.ts.map +1 -0
- package/dist/tools/lab-data.js +2464 -0
- package/dist/tools/lab-data.js.map +1 -0
- package/dist/tools/lab-earth.d.ts +2 -0
- package/dist/tools/lab-earth.d.ts.map +1 -0
- package/dist/tools/lab-earth.js +1124 -0
- package/dist/tools/lab-earth.js.map +1 -0
- package/dist/tools/lab-math.d.ts +2 -0
- package/dist/tools/lab-math.d.ts.map +1 -0
- package/dist/tools/lab-math.js +3021 -0
- package/dist/tools/lab-math.js.map +1 -0
- package/dist/tools/lab-physics.d.ts +2 -0
- package/dist/tools/lab-physics.d.ts.map +1 -0
- package/dist/tools/lab-physics.js +2423 -0
- package/dist/tools/lab-physics.js.map +1 -0
- package/dist/tools/research-notebook.d.ts +2 -0
- package/dist/tools/research-notebook.d.ts.map +1 -0
- package/dist/tools/research-notebook.js +1165 -0
- package/dist/tools/research-notebook.js.map +1 -0
- package/dist/tools/research-pipeline.d.ts +2 -0
- package/dist/tools/research-pipeline.d.ts.map +1 -0
- package/dist/tools/research-pipeline.js +1094 -0
- package/dist/tools/research-pipeline.js.map +1 -0
- package/dist/tools/science-graph.d.ts +2 -0
- package/dist/tools/science-graph.d.ts.map +1 -0
- package/dist/tools/science-graph.js +995 -0
- package/dist/tools/science-graph.js.map +1 -0
- package/package.json +2 -3
|
@@ -0,0 +1,2423 @@
|
|
|
1
|
+
// kbot Physics & Engineering Tools — Lab-grade calculations
|
|
2
|
+
// Pure TypeScript implementations, zero external dependencies.
|
|
3
|
+
// Covers orbital mechanics, circuits, signal processing, particle physics,
|
|
4
|
+
// relativity, quantum computing, beam analysis, fluid dynamics, EM, and astronomy.
|
|
5
|
+
import { registerTool } from './index.js';
|
|
6
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
7
|
+
const G = 6.67430e-11; // Gravitational constant (m^3 kg^-1 s^-2)
|
|
8
|
+
const c = 299_792_458; // Speed of light (m/s)
|
|
9
|
+
const h = 6.62607015e-34; // Planck constant (J*s)
|
|
10
|
+
const hbar = h / (2 * Math.PI);
|
|
11
|
+
const k_B = 1.380649e-23; // Boltzmann constant (J/K)
|
|
12
|
+
const e_charge = 1.602176634e-19; // Elementary charge (C)
|
|
13
|
+
const mu_0 = 1.25663706212e-6; // Vacuum permeability (H/m)
|
|
14
|
+
const epsilon_0 = 8.8541878128e-12; // Vacuum permittivity (F/m)
|
|
15
|
+
const eV = 1.602176634e-19; // 1 eV in joules
|
|
16
|
+
const MeV = eV * 1e6;
|
|
17
|
+
const GeV = eV * 1e9;
|
|
18
|
+
const AU = 1.496e11; // Astronomical unit (m)
|
|
19
|
+
const SOLAR_MASS = 1.989e30; // kg
|
|
20
|
+
const USER_AGENT = 'KBot/3.0 (Lab Tools)';
|
|
21
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
22
|
+
function fmt(n, digits = 6) {
|
|
23
|
+
if (n === 0)
|
|
24
|
+
return '0';
|
|
25
|
+
const abs = Math.abs(n);
|
|
26
|
+
if (abs >= 1e6 || abs < 1e-3)
|
|
27
|
+
return n.toExponential(digits);
|
|
28
|
+
return n.toPrecision(digits);
|
|
29
|
+
}
|
|
30
|
+
function fmtUnit(n, unit, digits = 4) {
|
|
31
|
+
return `${fmt(n, digits)} ${unit}`;
|
|
32
|
+
}
|
|
33
|
+
const BODIES = {
|
|
34
|
+
sun: { mass: 1.989e30, radius: 6.9634e8 },
|
|
35
|
+
mercury: { mass: 3.3011e23, radius: 2.4397e6, semiMajorAxis: 5.791e10, orbitalPeriod: 7.6005e6, parent: 'sun' },
|
|
36
|
+
venus: { mass: 4.8675e24, radius: 6.0518e6, semiMajorAxis: 1.0821e11, orbitalPeriod: 1.9414e7, parent: 'sun' },
|
|
37
|
+
earth: { mass: 5.9722e24, radius: 6.371e6, semiMajorAxis: 1.496e11, orbitalPeriod: 3.1557e7, parent: 'sun' },
|
|
38
|
+
moon: { mass: 7.342e22, radius: 1.7374e6, semiMajorAxis: 3.844e8, orbitalPeriod: 2.3606e6, parent: 'earth' },
|
|
39
|
+
mars: { mass: 6.4171e23, radius: 3.3895e6, semiMajorAxis: 2.2794e11, orbitalPeriod: 5.9355e7, parent: 'sun' },
|
|
40
|
+
jupiter: { mass: 1.8982e27, radius: 6.9911e7, semiMajorAxis: 7.7857e11, orbitalPeriod: 3.7435e8, parent: 'sun' },
|
|
41
|
+
saturn: { mass: 5.6834e26, radius: 5.8232e7, semiMajorAxis: 1.4335e12, orbitalPeriod: 9.2956e8, parent: 'sun' },
|
|
42
|
+
uranus: { mass: 8.6810e25, radius: 2.5362e7, semiMajorAxis: 2.8725e12, orbitalPeriod: 2.6512e9, parent: 'sun' },
|
|
43
|
+
neptune: { mass: 1.0241e26, radius: 2.4622e7, semiMajorAxis: 4.4951e12, orbitalPeriod: 5.2003e9, parent: 'sun' },
|
|
44
|
+
pluto: { mass: 1.303e22, radius: 1.1883e6, semiMajorAxis: 5.9064e12, orbitalPeriod: 7.8241e9, parent: 'sun' },
|
|
45
|
+
};
|
|
46
|
+
const PARTICLES = {
|
|
47
|
+
// ── Leptons ──
|
|
48
|
+
electron: { name: 'Electron', symbol: 'e\u207b', category: 'Lepton', mass_MeV: 0.51100, charge: -1, spin: '1/2', lifetime_s: Infinity },
|
|
49
|
+
positron: { name: 'Positron', symbol: 'e\u207a', category: 'Lepton', mass_MeV: 0.51100, charge: 1, spin: '1/2', lifetime_s: Infinity, isAntiparticle: true },
|
|
50
|
+
muon: { name: 'Muon', symbol: '\u03bc\u207b', category: 'Lepton', mass_MeV: 105.658, charge: -1, spin: '1/2', lifetime_s: 2.197e-6, decay_modes: ['e\u207b + \u03bd\u0305_e + \u03bd_\u03bc (100%)'] },
|
|
51
|
+
antimuon: { name: 'Antimuon', symbol: '\u03bc\u207a', category: 'Lepton', mass_MeV: 105.658, charge: 1, spin: '1/2', lifetime_s: 2.197e-6, isAntiparticle: true },
|
|
52
|
+
tau: { name: 'Tau', symbol: '\u03c4\u207b', category: 'Lepton', mass_MeV: 1776.86, charge: -1, spin: '1/2', lifetime_s: 2.903e-13, decay_modes: ['Hadrons (~65%)', 'e\u207b + \u03bd\u0305_e + \u03bd_\u03c4 (~18%)', '\u03bc\u207b + \u03bd\u0305_\u03bc + \u03bd_\u03c4 (~17%)'] },
|
|
53
|
+
electron_neutrino: { name: 'Electron Neutrino', symbol: '\u03bd_e', category: 'Lepton', mass_MeV: 0, charge: 0, spin: '1/2', lifetime_s: Infinity },
|
|
54
|
+
muon_neutrino: { name: 'Muon Neutrino', symbol: '\u03bd_\u03bc', category: 'Lepton', mass_MeV: 0, charge: 0, spin: '1/2', lifetime_s: Infinity },
|
|
55
|
+
tau_neutrino: { name: 'Tau Neutrino', symbol: '\u03bd_\u03c4', category: 'Lepton', mass_MeV: 0, charge: 0, spin: '1/2', lifetime_s: Infinity },
|
|
56
|
+
// ── Quarks ──
|
|
57
|
+
up: { name: 'Up Quark', symbol: 'u', category: 'Quark', mass_MeV: 2.16, charge: 2 / 3, spin: '1/2', lifetime_s: Infinity, color_charge: 'r/g/b' },
|
|
58
|
+
down: { name: 'Down Quark', symbol: 'd', category: 'Quark', mass_MeV: 4.67, charge: -1 / 3, spin: '1/2', lifetime_s: Infinity, color_charge: 'r/g/b' },
|
|
59
|
+
charm: { name: 'Charm Quark', symbol: 'c', category: 'Quark', mass_MeV: 1270, charge: 2 / 3, spin: '1/2', lifetime_s: Infinity, color_charge: 'r/g/b' },
|
|
60
|
+
strange: { name: 'Strange Quark', symbol: 's', category: 'Quark', mass_MeV: 93.4, charge: -1 / 3, spin: '1/2', lifetime_s: Infinity, color_charge: 'r/g/b' },
|
|
61
|
+
top: { name: 'Top Quark', symbol: 't', category: 'Quark', mass_MeV: 172_760, charge: 2 / 3, spin: '1/2', lifetime_s: 5e-25, color_charge: 'r/g/b', decay_modes: ['W\u207a + b (~100%)'] },
|
|
62
|
+
bottom: { name: 'Bottom Quark', symbol: 'b', category: 'Quark', mass_MeV: 4180, charge: -1 / 3, spin: '1/2', lifetime_s: Infinity, color_charge: 'r/g/b' },
|
|
63
|
+
// ── Gauge Bosons ──
|
|
64
|
+
photon: { name: 'Photon', symbol: '\u03b3', category: 'Gauge Boson', mass_MeV: null, charge: 0, spin: '1', lifetime_s: Infinity },
|
|
65
|
+
gluon: { name: 'Gluon', symbol: 'g', category: 'Gauge Boson', mass_MeV: null, charge: 0, spin: '1', lifetime_s: Infinity, color_charge: 'octet' },
|
|
66
|
+
w_plus: { name: 'W\u207a Boson', symbol: 'W\u207a', category: 'Gauge Boson', mass_MeV: 80_377, charge: 1, spin: '1', lifetime_s: 3.07e-25, decay_modes: ['q + q\u0305\' (~67%)', 'l + \u03bd_l (~33%)'] },
|
|
67
|
+
w_minus: { name: 'W\u207b Boson', symbol: 'W\u207b', category: 'Gauge Boson', mass_MeV: 80_377, charge: -1, spin: '1', lifetime_s: 3.07e-25, decay_modes: ['q + q\u0305\' (~67%)', 'l + \u03bd_l (~33%)'] },
|
|
68
|
+
z_boson: { name: 'Z Boson', symbol: 'Z\u2070', category: 'Gauge Boson', mass_MeV: 91_188, charge: 0, spin: '1', lifetime_s: 2.64e-25, decay_modes: ['q + q\u0305 (~70%)', 'l\u207a + l\u207b (~10%)', '\u03bd + \u03bd\u0305 (~20%)'] },
|
|
69
|
+
// ── Scalar Boson ──
|
|
70
|
+
higgs: { name: 'Higgs Boson', symbol: 'H\u2070', category: 'Scalar Boson', mass_MeV: 125_250, charge: 0, spin: '0', lifetime_s: 1.56e-22, decay_modes: ['b + b\u0305 (~58%)', 'W\u207a + W\u207b (~21%)', 'gg (~8.2%)', '\u03c4\u207a + \u03c4\u207b (~6.3%)', 'Z + Z (~2.6%)', '\u03b3\u03b3 (~0.23%)'] },
|
|
71
|
+
// ── Mesons ──
|
|
72
|
+
pion_plus: { name: 'Pion', symbol: '\u03c0\u207a', category: 'Meson', mass_MeV: 139.570, charge: 1, spin: '0', lifetime_s: 2.603e-8, quark_content: 'u + d\u0305', decay_modes: ['\u03bc\u207a + \u03bd_\u03bc (~99.99%)'] },
|
|
73
|
+
pion_minus: { name: 'Pion', symbol: '\u03c0\u207b', category: 'Meson', mass_MeV: 139.570, charge: -1, spin: '0', lifetime_s: 2.603e-8, quark_content: 'd + u\u0305', decay_modes: ['\u03bc\u207b + \u03bd\u0305_\u03bc (~99.99%)'] },
|
|
74
|
+
pion_zero: { name: 'Neutral Pion', symbol: '\u03c0\u2070', category: 'Meson', mass_MeV: 134.977, charge: 0, spin: '0', lifetime_s: 8.43e-17, quark_content: '(u\u016b \u2212 d\u0064\u0305)/\u221a2', decay_modes: ['\u03b3 + \u03b3 (~98.8%)'] },
|
|
75
|
+
kaon_plus: { name: 'Kaon', symbol: 'K\u207a', category: 'Meson', mass_MeV: 493.677, charge: 1, spin: '0', lifetime_s: 1.238e-8, quark_content: 'u + s\u0305', decay_modes: ['\u03bc\u207a + \u03bd_\u03bc (~63.6%)', '\u03c0\u207a + \u03c0\u2070 (~20.7%)'] },
|
|
76
|
+
kaon_minus: { name: 'Kaon', symbol: 'K\u207b', category: 'Meson', mass_MeV: 493.677, charge: -1, spin: '0', lifetime_s: 1.238e-8, quark_content: 's + u\u0305' },
|
|
77
|
+
kaon_zero: { name: 'Neutral Kaon', symbol: 'K\u2070', category: 'Meson', mass_MeV: 497.611, charge: 0, spin: '0', lifetime_s: null, quark_content: 'd + s\u0305', decay_modes: ['Mixes to K_S / K_L'] },
|
|
78
|
+
kaon_short: { name: 'K-short', symbol: 'K_S', category: 'Meson', mass_MeV: 497.611, charge: 0, spin: '0', lifetime_s: 8.954e-11, decay_modes: ['\u03c0\u207a + \u03c0\u207b (~69%)', '\u03c0\u2070 + \u03c0\u2070 (~31%)'] },
|
|
79
|
+
kaon_long: { name: 'K-long', symbol: 'K_L', category: 'Meson', mass_MeV: 497.611, charge: 0, spin: '0', lifetime_s: 5.116e-8, decay_modes: ['\u03c0\u00b1 + e\u2213 + \u03bd (~41%)', '\u03c0\u00b1 + \u03bc\u2213 + \u03bd (~27%)', '3\u03c0\u2070 (~20%)'] },
|
|
80
|
+
eta: { name: 'Eta', symbol: '\u03b7', category: 'Meson', mass_MeV: 547.862, charge: 0, spin: '0', lifetime_s: 5.02e-19, quark_content: '(u\u016b + d\u0064\u0305 \u2212 2s\u0073\u0305)/\u221a6', decay_modes: ['\u03b3 + \u03b3 (~39%)', '3\u03c0\u2070 (~33%)', '\u03c0\u207a + \u03c0\u207b + \u03c0\u2070 (~23%)'] },
|
|
81
|
+
eta_prime: { name: 'Eta Prime', symbol: "\u03b7'(958)", category: 'Meson', mass_MeV: 957.78, charge: 0, spin: '0', lifetime_s: 3.32e-21, decay_modes: ['\u03c0\u207a + \u03c0\u207b + \u03b7 (~43%)', '\u03c1\u2070 + \u03b3 (~29%)'] },
|
|
82
|
+
rho_plus: { name: 'Rho', symbol: '\u03c1\u207a', category: 'Meson', mass_MeV: 775.11, charge: 1, spin: '1', lifetime_s: 4.41e-24, quark_content: 'u + d\u0305', decay_modes: ['\u03c0\u207a + \u03c0\u2070 (~100%)'] },
|
|
83
|
+
rho_zero: { name: 'Rho', symbol: '\u03c1\u2070', category: 'Meson', mass_MeV: 775.26, charge: 0, spin: '1', lifetime_s: 4.41e-24, quark_content: '(u\u016b \u2212 d\u0064\u0305)/\u221a2', decay_modes: ['\u03c0\u207a + \u03c0\u207b (~100%)'] },
|
|
84
|
+
omega_meson: { name: 'Omega Meson', symbol: '\u03c9(782)', category: 'Meson', mass_MeV: 782.66, charge: 0, spin: '1', lifetime_s: 7.75e-23, decay_modes: ['\u03c0\u207a + \u03c0\u207b + \u03c0\u2070 (~89%)'] },
|
|
85
|
+
phi_meson: { name: 'Phi Meson', symbol: '\u03c6(1020)', category: 'Meson', mass_MeV: 1019.461, charge: 0, spin: '1', lifetime_s: 1.55e-22, quark_content: 's + s\u0305', decay_modes: ['K\u207a + K\u207b (~49%)', 'K_L + K_S (~34%)'] },
|
|
86
|
+
d_plus: { name: 'D Meson', symbol: 'D\u207a', category: 'Meson', mass_MeV: 1869.66, charge: 1, spin: '0', lifetime_s: 1.040e-12, quark_content: 'c + d\u0305', decay_modes: ['K\u0305\u2070 + \u03c0\u207a (~3.9%)', 'K\u207b + \u03c0\u207a + \u03c0\u207a (~9.4%)'] },
|
|
87
|
+
d_zero: { name: 'D Meson', symbol: 'D\u2070', category: 'Meson', mass_MeV: 1864.84, charge: 0, spin: '0', lifetime_s: 4.101e-13, quark_content: 'c + u\u0305', decay_modes: ['K\u207b + \u03c0\u207a (~3.9%)'] },
|
|
88
|
+
d_s_plus: { name: 'D_s Meson', symbol: 'D_s\u207a', category: 'Meson', mass_MeV: 1968.35, charge: 1, spin: '0', lifetime_s: 5.04e-13, quark_content: 'c + s\u0305' },
|
|
89
|
+
b_plus: { name: 'B Meson', symbol: 'B\u207a', category: 'Meson', mass_MeV: 5279.34, charge: 1, spin: '0', lifetime_s: 1.638e-12, quark_content: 'u + b\u0305' },
|
|
90
|
+
b_zero: { name: 'B Meson', symbol: 'B\u2070', category: 'Meson', mass_MeV: 5279.66, charge: 0, spin: '0', lifetime_s: 1.519e-12, quark_content: 'd + b\u0305' },
|
|
91
|
+
b_s: { name: 'B_s Meson', symbol: 'B_s\u2070', category: 'Meson', mass_MeV: 5366.92, charge: 0, spin: '0', lifetime_s: 1.515e-12, quark_content: 's + b\u0305' },
|
|
92
|
+
b_c_plus: { name: 'B_c Meson', symbol: 'B_c\u207a', category: 'Meson', mass_MeV: 6274.47, charge: 1, spin: '0', lifetime_s: 5.10e-13, quark_content: 'c + b\u0305' },
|
|
93
|
+
j_psi: { name: 'J/Psi', symbol: 'J/\u03c8', category: 'Meson', mass_MeV: 3096.900, charge: 0, spin: '1', lifetime_s: 7.09e-21, quark_content: 'c + c\u0305', decay_modes: ['Hadrons (~88%)', 'e\u207a + e\u207b (~6%)', '\u03bc\u207a + \u03bc\u207b (~6%)'] },
|
|
94
|
+
psi_2s: { name: 'Psi(2S)', symbol: '\u03c8(2S)', category: 'Meson', mass_MeV: 3686.10, charge: 0, spin: '1', lifetime_s: 2.12e-21, quark_content: 'c + c\u0305' },
|
|
95
|
+
upsilon_1s: { name: 'Upsilon(1S)', symbol: '\u03a5(1S)', category: 'Meson', mass_MeV: 9460.30, charge: 0, spin: '1', lifetime_s: 1.22e-20, quark_content: 'b + b\u0305', decay_modes: ['Hadrons (~82%)', 'e\u207a + e\u207b (~2.4%)', '\u03bc\u207a + \u03bc\u207b (~2.5%)', '\u03c4\u207a + \u03c4\u207b (~2.6%)'] },
|
|
96
|
+
upsilon_2s: { name: 'Upsilon(2S)', symbol: '\u03a5(2S)', category: 'Meson', mass_MeV: 10023.26, charge: 0, spin: '1', lifetime_s: 3.25e-20, quark_content: 'b + b\u0305' },
|
|
97
|
+
upsilon_3s: { name: 'Upsilon(3S)', symbol: '\u03a5(3S)', category: 'Meson', mass_MeV: 10355.20, charge: 0, spin: '1', lifetime_s: 4.25e-20, quark_content: 'b + b\u0305' },
|
|
98
|
+
// ── Baryons ──
|
|
99
|
+
proton: { name: 'Proton', symbol: 'p', category: 'Baryon', mass_MeV: 938.272, charge: 1, spin: '1/2', lifetime_s: Infinity, quark_content: 'uud' },
|
|
100
|
+
neutron: { name: 'Neutron', symbol: 'n', category: 'Baryon', mass_MeV: 939.565, charge: 0, spin: '1/2', lifetime_s: 878.4, quark_content: 'udd', decay_modes: ['p + e\u207b + \u03bd\u0305_e (100%)'] },
|
|
101
|
+
antiproton: { name: 'Antiproton', symbol: 'p\u0305', category: 'Baryon', mass_MeV: 938.272, charge: -1, spin: '1/2', lifetime_s: Infinity, quark_content: 'u\u0305u\u0305d\u0305', isAntiparticle: true },
|
|
102
|
+
lambda: { name: 'Lambda', symbol: '\u039b\u2070', category: 'Baryon', mass_MeV: 1115.683, charge: 0, spin: '1/2', lifetime_s: 2.632e-10, quark_content: 'uds', decay_modes: ['p + \u03c0\u207b (~64%)', 'n + \u03c0\u2070 (~36%)'] },
|
|
103
|
+
sigma_plus: { name: 'Sigma+', symbol: '\u03a3\u207a', category: 'Baryon', mass_MeV: 1189.37, charge: 1, spin: '1/2', lifetime_s: 8.018e-11, quark_content: 'uus', decay_modes: ['p + \u03c0\u2070 (~52%)', 'n + \u03c0\u207a (~48%)'] },
|
|
104
|
+
sigma_zero: { name: 'Sigma0', symbol: '\u03a3\u2070', category: 'Baryon', mass_MeV: 1192.642, charge: 0, spin: '1/2', lifetime_s: 7.4e-20, quark_content: 'uds', decay_modes: ['\u039b\u2070 + \u03b3 (100%)'] },
|
|
105
|
+
sigma_minus: { name: 'Sigma-', symbol: '\u03a3\u207b', category: 'Baryon', mass_MeV: 1197.449, charge: -1, spin: '1/2', lifetime_s: 1.479e-10, quark_content: 'dds', decay_modes: ['n + \u03c0\u207b (100%)'] },
|
|
106
|
+
xi_zero: { name: 'Xi0', symbol: '\u039e\u2070', category: 'Baryon', mass_MeV: 1314.86, charge: 0, spin: '1/2', lifetime_s: 2.90e-10, quark_content: 'uss', decay_modes: ['\u039b\u2070 + \u03c0\u2070 (100%)'] },
|
|
107
|
+
xi_minus: { name: 'Xi-', symbol: '\u039e\u207b', category: 'Baryon', mass_MeV: 1321.71, charge: -1, spin: '1/2', lifetime_s: 1.639e-10, quark_content: 'dss', decay_modes: ['\u039b\u2070 + \u03c0\u207b (100%)'] },
|
|
108
|
+
omega_minus: { name: 'Omega-', symbol: '\u03a9\u207b', category: 'Baryon', mass_MeV: 1672.45, charge: -1, spin: '3/2', lifetime_s: 8.21e-11, quark_content: 'sss', decay_modes: ['\u039b\u2070 + K\u207b (~68%)', '\u039e\u2070 + \u03c0\u207b (~24%)'] },
|
|
109
|
+
delta_pp: { name: 'Delta++', symbol: '\u0394\u207a\u207a', category: 'Baryon', mass_MeV: 1232, charge: 2, spin: '3/2', lifetime_s: 5.58e-24, quark_content: 'uuu', decay_modes: ['p + \u03c0\u207a (100%)'] },
|
|
110
|
+
delta_plus: { name: 'Delta+', symbol: '\u0394\u207a', category: 'Baryon', mass_MeV: 1232, charge: 1, spin: '3/2', lifetime_s: 5.58e-24, quark_content: 'uud', decay_modes: ['p + \u03c0\u2070 (~66%)', 'n + \u03c0\u207a (~33%)'] },
|
|
111
|
+
delta_zero: { name: 'Delta0', symbol: '\u0394\u2070', category: 'Baryon', mass_MeV: 1232, charge: 0, spin: '3/2', lifetime_s: 5.58e-24, quark_content: 'udd', decay_modes: ['n + \u03c0\u2070 (~66%)', 'p + \u03c0\u207b (~33%)'] },
|
|
112
|
+
delta_minus: { name: 'Delta-', symbol: '\u0394\u207b', category: 'Baryon', mass_MeV: 1232, charge: -1, spin: '3/2', lifetime_s: 5.58e-24, quark_content: 'ddd', decay_modes: ['n + \u03c0\u207b (100%)'] },
|
|
113
|
+
lambda_c_plus: { name: 'Lambda_c+', symbol: '\u039b_c\u207a', category: 'Baryon', mass_MeV: 2286.46, charge: 1, spin: '1/2', lifetime_s: 2.024e-13, quark_content: 'udc' },
|
|
114
|
+
sigma_c_pp: { name: 'Sigma_c++', symbol: '\u03a3_c\u207a\u207a', category: 'Baryon', mass_MeV: 2453.97, charge: 2, spin: '1/2', lifetime_s: 2.91e-22, quark_content: 'uuc' },
|
|
115
|
+
xi_c_plus: { name: 'Xi_c+', symbol: '\u039e_c\u207a', category: 'Baryon', mass_MeV: 2467.71, charge: 1, spin: '1/2', lifetime_s: 4.56e-13, quark_content: 'usc' },
|
|
116
|
+
xi_c_zero: { name: 'Xi_c0', symbol: '\u039e_c\u2070', category: 'Baryon', mass_MeV: 2470.44, charge: 0, spin: '1/2', lifetime_s: 1.53e-13, quark_content: 'dsc' },
|
|
117
|
+
omega_c: { name: 'Omega_c0', symbol: '\u03a9_c\u2070', category: 'Baryon', mass_MeV: 2695.2, charge: 0, spin: '1/2', lifetime_s: 2.68e-13, quark_content: 'ssc' },
|
|
118
|
+
lambda_b: { name: 'Lambda_b0', symbol: '\u039b_b\u2070', category: 'Baryon', mass_MeV: 5619.60, charge: 0, spin: '1/2', lifetime_s: 1.471e-12, quark_content: 'udb' },
|
|
119
|
+
xi_b_zero: { name: 'Xi_b0', symbol: '\u039e_b\u2070', category: 'Baryon', mass_MeV: 5791.9, charge: 0, spin: '1/2', lifetime_s: 1.480e-12, quark_content: 'usb' },
|
|
120
|
+
xi_b_minus: { name: 'Xi_b-', symbol: '\u039e_b\u207b', category: 'Baryon', mass_MeV: 5797.0, charge: -1, spin: '1/2', lifetime_s: 1.572e-12, quark_content: 'dsb' },
|
|
121
|
+
omega_b: { name: 'Omega_b-', symbol: '\u03a9_b\u207b', category: 'Baryon', mass_MeV: 6046.1, charge: -1, spin: '1/2', lifetime_s: 1.64e-12, quark_content: 'ssb' },
|
|
122
|
+
sigma_b_plus: { name: 'Sigma_b+', symbol: '\u03a3_b\u207a', category: 'Baryon', mass_MeV: 5811.3, charge: 1, spin: '1/2', lifetime_s: 5.7e-23, quark_content: 'uub' },
|
|
123
|
+
sigma_b_minus: { name: 'Sigma_b-', symbol: '\u03a3_b\u207b', category: 'Baryon', mass_MeV: 5815.5, charge: -1, spin: '1/2', lifetime_s: 6.4e-23, quark_content: 'ddb' },
|
|
124
|
+
};
|
|
125
|
+
// Build alias map for flexible lookup
|
|
126
|
+
const PARTICLE_ALIASES = {};
|
|
127
|
+
for (const [key, p] of Object.entries(PARTICLES)) {
|
|
128
|
+
PARTICLE_ALIASES[key] = key;
|
|
129
|
+
PARTICLE_ALIASES[p.name.toLowerCase()] = key;
|
|
130
|
+
PARTICLE_ALIASES[p.symbol.toLowerCase()] = key;
|
|
131
|
+
// Common aliases
|
|
132
|
+
if (key === 'j_psi') {
|
|
133
|
+
PARTICLE_ALIASES['jpsi'] = key;
|
|
134
|
+
PARTICLE_ALIASES['j/psi'] = key;
|
|
135
|
+
PARTICLE_ALIASES['j/\u03c8'] = key;
|
|
136
|
+
}
|
|
137
|
+
if (key === 'w_plus') {
|
|
138
|
+
PARTICLE_ALIASES['w+'] = key;
|
|
139
|
+
PARTICLE_ALIASES['w boson'] = key;
|
|
140
|
+
PARTICLE_ALIASES['w+boson'] = key;
|
|
141
|
+
}
|
|
142
|
+
if (key === 'w_minus') {
|
|
143
|
+
PARTICLE_ALIASES['w-'] = key;
|
|
144
|
+
PARTICLE_ALIASES['w-boson'] = key;
|
|
145
|
+
}
|
|
146
|
+
if (key === 'z_boson') {
|
|
147
|
+
PARTICLE_ALIASES['z'] = key;
|
|
148
|
+
PARTICLE_ALIASES['z0'] = key;
|
|
149
|
+
PARTICLE_ALIASES['z boson'] = key;
|
|
150
|
+
}
|
|
151
|
+
if (key === 'higgs') {
|
|
152
|
+
PARTICLE_ALIASES['h0'] = key;
|
|
153
|
+
PARTICLE_ALIASES['higgs boson'] = key;
|
|
154
|
+
}
|
|
155
|
+
if (key === 'pion_plus') {
|
|
156
|
+
PARTICLE_ALIASES['pi+'] = key;
|
|
157
|
+
PARTICLE_ALIASES['pion+'] = key;
|
|
158
|
+
}
|
|
159
|
+
if (key === 'pion_minus') {
|
|
160
|
+
PARTICLE_ALIASES['pi-'] = key;
|
|
161
|
+
PARTICLE_ALIASES['pion-'] = key;
|
|
162
|
+
}
|
|
163
|
+
if (key === 'pion_zero') {
|
|
164
|
+
PARTICLE_ALIASES['pi0'] = key;
|
|
165
|
+
PARTICLE_ALIASES['pion0'] = key;
|
|
166
|
+
}
|
|
167
|
+
if (key === 'kaon_plus') {
|
|
168
|
+
PARTICLE_ALIASES['k+'] = key;
|
|
169
|
+
}
|
|
170
|
+
if (key === 'kaon_minus') {
|
|
171
|
+
PARTICLE_ALIASES['k-'] = key;
|
|
172
|
+
}
|
|
173
|
+
if (key === 'kaon_zero') {
|
|
174
|
+
PARTICLE_ALIASES['k0'] = key;
|
|
175
|
+
}
|
|
176
|
+
if (key === 'upsilon_1s') {
|
|
177
|
+
PARTICLE_ALIASES['upsilon'] = key;
|
|
178
|
+
PARTICLE_ALIASES['y(1s)'] = key;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function lookupParticle(name) {
|
|
182
|
+
const key = PARTICLE_ALIASES[name.toLowerCase().trim()];
|
|
183
|
+
return key ? PARTICLES[key] : undefined;
|
|
184
|
+
}
|
|
185
|
+
function cMul(a, b) {
|
|
186
|
+
return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re };
|
|
187
|
+
}
|
|
188
|
+
function cAdd(a, b) {
|
|
189
|
+
return { re: a.re + b.re, im: a.im + b.im };
|
|
190
|
+
}
|
|
191
|
+
function cSub(a, b) {
|
|
192
|
+
return { re: a.re - b.re, im: a.im - b.im };
|
|
193
|
+
}
|
|
194
|
+
function cAbs2(a) {
|
|
195
|
+
return a.re * a.re + a.im * a.im;
|
|
196
|
+
}
|
|
197
|
+
function cScale(a, s) {
|
|
198
|
+
return { re: a.re * s, im: a.im * s };
|
|
199
|
+
}
|
|
200
|
+
function cExp(theta) {
|
|
201
|
+
return { re: Math.cos(theta), im: Math.sin(theta) };
|
|
202
|
+
}
|
|
203
|
+
function cFmt(z, digits = 4) {
|
|
204
|
+
const r = z.re, i = z.im;
|
|
205
|
+
if (Math.abs(r) < 1e-10 && Math.abs(i) < 1e-10)
|
|
206
|
+
return '0';
|
|
207
|
+
if (Math.abs(i) < 1e-10)
|
|
208
|
+
return r.toFixed(digits);
|
|
209
|
+
if (Math.abs(r) < 1e-10)
|
|
210
|
+
return `${i.toFixed(digits)}i`;
|
|
211
|
+
const sign = i >= 0 ? '+' : '-';
|
|
212
|
+
return `${r.toFixed(digits)}${sign}${Math.abs(i).toFixed(digits)}i`;
|
|
213
|
+
}
|
|
214
|
+
const GATE_H = [
|
|
215
|
+
[{ re: 1 / Math.SQRT2, im: 0 }, { re: 1 / Math.SQRT2, im: 0 }],
|
|
216
|
+
[{ re: 1 / Math.SQRT2, im: 0 }, { re: -1 / Math.SQRT2, im: 0 }],
|
|
217
|
+
];
|
|
218
|
+
const GATE_X = [
|
|
219
|
+
[{ re: 0, im: 0 }, { re: 1, im: 0 }],
|
|
220
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
221
|
+
];
|
|
222
|
+
const GATE_Y = [
|
|
223
|
+
[{ re: 0, im: 0 }, { re: 0, im: -1 }],
|
|
224
|
+
[{ re: 0, im: 1 }, { re: 0, im: 0 }],
|
|
225
|
+
];
|
|
226
|
+
const GATE_Z = [
|
|
227
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
228
|
+
[{ re: 0, im: 0 }, { re: -1, im: 0 }],
|
|
229
|
+
];
|
|
230
|
+
const GATE_T = [
|
|
231
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
232
|
+
[{ re: 0, im: 0 }, cExp(Math.PI / 4)],
|
|
233
|
+
];
|
|
234
|
+
const GATE_S = [
|
|
235
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
236
|
+
[{ re: 0, im: 0 }, { re: 0, im: 1 }],
|
|
237
|
+
];
|
|
238
|
+
const GATE_I = [
|
|
239
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
240
|
+
[{ re: 0, im: 0 }, { re: 1, im: 0 }],
|
|
241
|
+
];
|
|
242
|
+
const GATE_CNOT = [
|
|
243
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
244
|
+
[{ re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
245
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }],
|
|
246
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
247
|
+
];
|
|
248
|
+
const GATE_SWAP = [
|
|
249
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
250
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
251
|
+
[{ re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
252
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }],
|
|
253
|
+
];
|
|
254
|
+
// Toffoli = 8x8 identity with bottom-right 2x2 as X
|
|
255
|
+
function makeToffoli() {
|
|
256
|
+
const n = 8;
|
|
257
|
+
const m = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => ({ re: i === j ? 1 : 0, im: 0 })));
|
|
258
|
+
// Swap |110> and |111>
|
|
259
|
+
m[6][6] = { re: 0, im: 0 };
|
|
260
|
+
m[6][7] = { re: 1, im: 0 };
|
|
261
|
+
m[7][6] = { re: 1, im: 0 };
|
|
262
|
+
m[7][7] = { re: 0, im: 0 };
|
|
263
|
+
return m;
|
|
264
|
+
}
|
|
265
|
+
const GATE_TOFFOLI = makeToffoli();
|
|
266
|
+
/** Tensor product of two matrices */
|
|
267
|
+
function tensorProduct(a, b) {
|
|
268
|
+
const ra = a.length, ca = a[0].length;
|
|
269
|
+
const rb = b.length, cb = b[0].length;
|
|
270
|
+
const result = Array.from({ length: ra * rb }, () => Array.from({ length: ca * cb }, () => ({ re: 0, im: 0 })));
|
|
271
|
+
for (let i = 0; i < ra; i++) {
|
|
272
|
+
for (let j = 0; j < ca; j++) {
|
|
273
|
+
for (let k = 0; k < rb; k++) {
|
|
274
|
+
for (let l = 0; l < cb; l++) {
|
|
275
|
+
result[i * rb + k][j * cb + l] = cMul(a[i][j], b[k][l]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
/** Apply gate matrix to state vector */
|
|
283
|
+
function applyMatrix(mat, state) {
|
|
284
|
+
const n = state.length;
|
|
285
|
+
const result = Array.from({ length: n }, () => ({ re: 0, im: 0 }));
|
|
286
|
+
for (let i = 0; i < n; i++) {
|
|
287
|
+
for (let j = 0; j < n; j++) {
|
|
288
|
+
result[i] = cAdd(result[i], cMul(mat[i][j], state[j]));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
/** Build full n-qubit gate from a single-qubit gate acting on qubit q */
|
|
294
|
+
function buildSingleQubitGate(gate, qubit, nQubits) {
|
|
295
|
+
let result = qubit === 0 ? gate : GATE_I;
|
|
296
|
+
for (let i = 1; i < nQubits; i++) {
|
|
297
|
+
result = tensorProduct(result, i === qubit ? gate : GATE_I);
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
/** Build full n-qubit gate from a two-qubit gate acting on qubits q1, q2 (adjacent) */
|
|
302
|
+
function buildTwoQubitGate(gate, q1, q2, nQubits) {
|
|
303
|
+
// For CNOT/SWAP: q1=control, q2=target. We need them adjacent via SWAP routing.
|
|
304
|
+
// Simple case: handle adjacent qubits directly
|
|
305
|
+
const minQ = Math.min(q1, q2);
|
|
306
|
+
// Build: I^minQ tensor gate tensor I^(nQubits-minQ-2)
|
|
307
|
+
let result = null;
|
|
308
|
+
for (let i = 0; i < nQubits; i++) {
|
|
309
|
+
if (i === minQ) {
|
|
310
|
+
// If q1 < q2, gate acts normally. If q2 < q1, we need to reverse.
|
|
311
|
+
let g = gate;
|
|
312
|
+
if (q2 < q1 && gate === GATE_CNOT) {
|
|
313
|
+
// Reverse CNOT: swap target/control via conjugation with SWAP
|
|
314
|
+
// |00> -> |00>, |01> -> |11>, |10> -> |10>, |11> -> |01>
|
|
315
|
+
g = [
|
|
316
|
+
[{ re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
317
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }],
|
|
318
|
+
[{ re: 0, im: 0 }, { re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }],
|
|
319
|
+
[{ re: 0, im: 0 }, { re: 1, im: 0 }, { re: 0, im: 0 }, { re: 0, im: 0 }],
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
result = result ? tensorProduct(result, g) : g;
|
|
323
|
+
i++; // skip next qubit (already part of 2-qubit gate)
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
result = result ? tensorProduct(result, GATE_I) : GATE_I;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
/** Build a three-qubit Toffoli gate for specified control/target qubits */
|
|
332
|
+
function buildThreeQubitGate(gate, q0, q1, q2, nQubits) {
|
|
333
|
+
// Simple case: contiguous qubits
|
|
334
|
+
let result = null;
|
|
335
|
+
const minQ = Math.min(q0, q1, q2);
|
|
336
|
+
for (let i = 0; i < nQubits; i++) {
|
|
337
|
+
if (i === minQ) {
|
|
338
|
+
result = result ? tensorProduct(result, gate) : gate;
|
|
339
|
+
i += 2; // skip next 2 qubits
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
result = result ? tensorProduct(result, GATE_I) : GATE_I;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
// ─── Signal Processing Helpers ───────────────────────────────────────────────
|
|
348
|
+
/** Cooley-Tukey FFT (radix-2, input length must be power of 2) */
|
|
349
|
+
function fft(input) {
|
|
350
|
+
const n = input.length;
|
|
351
|
+
if (n === 1)
|
|
352
|
+
return [input[0]];
|
|
353
|
+
// Pad to next power of 2 if needed (handled by caller)
|
|
354
|
+
const even = fft(input.filter((_, i) => i % 2 === 0));
|
|
355
|
+
const odd = fft(input.filter((_, i) => i % 2 === 1));
|
|
356
|
+
const result = new Array(n);
|
|
357
|
+
for (let k = 0; k < n / 2; k++) {
|
|
358
|
+
const t = cMul(cExp(-2 * Math.PI * k / n), odd[k]);
|
|
359
|
+
result[k] = cAdd(even[k], t);
|
|
360
|
+
result[k + n / 2] = cSub(even[k], t);
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
function nextPow2(n) {
|
|
365
|
+
let p = 1;
|
|
366
|
+
while (p < n)
|
|
367
|
+
p <<= 1;
|
|
368
|
+
return p;
|
|
369
|
+
}
|
|
370
|
+
function padToPow2(signal) {
|
|
371
|
+
const n = nextPow2(signal.length);
|
|
372
|
+
const padded = signal.map(v => ({ re: v, im: 0 }));
|
|
373
|
+
while (padded.length < n)
|
|
374
|
+
padded.push({ re: 0, im: 0 });
|
|
375
|
+
return padded;
|
|
376
|
+
}
|
|
377
|
+
/** Inverse FFT */
|
|
378
|
+
function ifft(input) {
|
|
379
|
+
const n = input.length;
|
|
380
|
+
// Conjugate, FFT, conjugate, scale
|
|
381
|
+
const conj = input.map(z => ({ re: z.re, im: -z.im }));
|
|
382
|
+
const transformed = fft(conj);
|
|
383
|
+
return transformed.map(z => ({ re: z.re / n, im: -z.im / n }));
|
|
384
|
+
}
|
|
385
|
+
// Window functions
|
|
386
|
+
function hammingWindow(n) {
|
|
387
|
+
return Array.from({ length: n }, (_, i) => 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (n - 1)));
|
|
388
|
+
}
|
|
389
|
+
function hanningWindow(n) {
|
|
390
|
+
return Array.from({ length: n }, (_, i) => 0.5 * (1 - Math.cos(2 * Math.PI * i / (n - 1))));
|
|
391
|
+
}
|
|
392
|
+
function blackmanWindow(n) {
|
|
393
|
+
return Array.from({ length: n }, (_, i) => 0.42 - 0.5 * Math.cos(2 * Math.PI * i / (n - 1)) + 0.08 * Math.cos(4 * Math.PI * i / (n - 1)));
|
|
394
|
+
}
|
|
395
|
+
const DEFAULT_MATERIALS = {
|
|
396
|
+
steel: { E: 200e9, I: 8.333e-6 }, // ~100x100mm square section
|
|
397
|
+
aluminum: { E: 69e9, I: 8.333e-6 },
|
|
398
|
+
timber: { E: 12e9, I: 8.333e-6 },
|
|
399
|
+
concrete: { E: 30e9, I: 8.333e-6 },
|
|
400
|
+
};
|
|
401
|
+
// ─── Tool Registration ───────────────────────────────────────────────────────
|
|
402
|
+
export function registerLabPhysicsTools() {
|
|
403
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
404
|
+
// 1. ORBIT CALCULATOR
|
|
405
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
406
|
+
registerTool({
|
|
407
|
+
name: 'orbit_calculator',
|
|
408
|
+
description: 'Orbital mechanics: period, velocity, energy, escape velocity, Hohmann transfers, Lagrange points. Embedded solar system data (Sun through Pluto + Moon).',
|
|
409
|
+
parameters: {
|
|
410
|
+
body: { type: 'string', description: 'Central body name (sun, earth, mars, etc.)', required: true },
|
|
411
|
+
altitude_km: { type: 'number', description: 'Orbital altitude above surface in km', required: true },
|
|
412
|
+
calculation: { type: 'string', description: 'Calculation type: orbit, hohmann, escape, lagrange', required: true },
|
|
413
|
+
target_body: { type: 'string', description: 'Target body for Hohmann transfer (e.g. mars)', required: false },
|
|
414
|
+
},
|
|
415
|
+
tier: 'free',
|
|
416
|
+
async execute(args) {
|
|
417
|
+
const bodyName = String(args.body).toLowerCase();
|
|
418
|
+
const altitude_km = Number(args.altitude_km);
|
|
419
|
+
const calc = String(args.calculation).toLowerCase();
|
|
420
|
+
const targetName = args.target_body ? String(args.target_body).toLowerCase() : undefined;
|
|
421
|
+
const body = BODIES[bodyName];
|
|
422
|
+
if (!body)
|
|
423
|
+
return `**Error**: Unknown body "${bodyName}". Available: ${Object.keys(BODIES).join(', ')}`;
|
|
424
|
+
const altitude_m = altitude_km * 1000;
|
|
425
|
+
const r = body.radius + altitude_m;
|
|
426
|
+
const mu = G * body.mass; // Standard gravitational parameter
|
|
427
|
+
const lines = [`## Orbital Mechanics \u2014 ${bodyName.charAt(0).toUpperCase() + bodyName.slice(1)}`];
|
|
428
|
+
lines.push('');
|
|
429
|
+
lines.push(`| Parameter | Value |`);
|
|
430
|
+
lines.push(`|---|---|`);
|
|
431
|
+
lines.push(`| Central body mass | ${fmtUnit(body.mass, 'kg')} |`);
|
|
432
|
+
lines.push(`| Body radius | ${fmtUnit(body.radius / 1000, 'km')} |`);
|
|
433
|
+
lines.push(`| Altitude | ${fmtUnit(altitude_km, 'km')} |`);
|
|
434
|
+
lines.push(`| Orbital radius (r) | ${fmtUnit(r / 1000, 'km')} |`);
|
|
435
|
+
lines.push(`| \u03bc (GM) | ${fmtUnit(mu, 'm\u00b3/s\u00b2')} |`);
|
|
436
|
+
if (calc === 'orbit' || calc === 'all') {
|
|
437
|
+
// Circular orbit
|
|
438
|
+
const v_circ = Math.sqrt(mu / r);
|
|
439
|
+
const T = 2 * Math.PI * Math.sqrt(r ** 3 / mu);
|
|
440
|
+
const E_specific = -mu / (2 * r); // specific orbital energy
|
|
441
|
+
const v_escape = Math.sqrt(2 * mu / r);
|
|
442
|
+
lines.push('');
|
|
443
|
+
lines.push('### Circular Orbit');
|
|
444
|
+
lines.push('');
|
|
445
|
+
lines.push(`| Quantity | Value |`);
|
|
446
|
+
lines.push(`|---|---|`);
|
|
447
|
+
lines.push(`| Orbital velocity | ${fmtUnit(v_circ, 'm/s')} (${fmtUnit(v_circ / 1000, 'km/s')}) |`);
|
|
448
|
+
lines.push(`| Orbital period | ${fmtUnit(T, 's')} (${fmtUnit(T / 3600, 'hours')}) |`);
|
|
449
|
+
lines.push(`| Specific energy | ${fmtUnit(E_specific, 'J/kg')} |`);
|
|
450
|
+
lines.push(`| Escape velocity | ${fmtUnit(v_escape, 'm/s')} (${fmtUnit(v_escape / 1000, 'km/s')}) |`);
|
|
451
|
+
// Vis-viva for elliptical orbits — show for reference
|
|
452
|
+
lines.push('');
|
|
453
|
+
lines.push('**Vis-viva equation**: v\u00b2 = \u03bc(2/r - 1/a)');
|
|
454
|
+
}
|
|
455
|
+
if (calc === 'escape') {
|
|
456
|
+
const v_escape = Math.sqrt(2 * mu / r);
|
|
457
|
+
const v_circ = Math.sqrt(mu / r);
|
|
458
|
+
const delta_v = v_escape - v_circ;
|
|
459
|
+
lines.push('');
|
|
460
|
+
lines.push('### Escape Analysis');
|
|
461
|
+
lines.push('');
|
|
462
|
+
lines.push(`| Quantity | Value |`);
|
|
463
|
+
lines.push(`|---|---|`);
|
|
464
|
+
lines.push(`| Escape velocity | ${fmtUnit(v_escape, 'm/s')} (${fmtUnit(v_escape / 1000, 'km/s')}) |`);
|
|
465
|
+
lines.push(`| Circular velocity | ${fmtUnit(v_circ, 'm/s')} |`);
|
|
466
|
+
lines.push(`| \u0394v (circular \u2192 escape) | ${fmtUnit(delta_v, 'm/s')} |`);
|
|
467
|
+
lines.push(`| Escape energy (1kg) | ${fmtUnit(0.5 * v_escape ** 2, 'J')} |`);
|
|
468
|
+
}
|
|
469
|
+
if (calc === 'hohmann' && targetName) {
|
|
470
|
+
const target = BODIES[targetName];
|
|
471
|
+
if (!target)
|
|
472
|
+
return `**Error**: Unknown target body "${targetName}".`;
|
|
473
|
+
if (!body.semiMajorAxis && !target.semiMajorAxis)
|
|
474
|
+
return '**Error**: Need orbital data for Hohmann transfer.';
|
|
475
|
+
// Both bodies orbit the same parent (e.g., Sun)
|
|
476
|
+
const parentName = body.parent || target.parent || 'sun';
|
|
477
|
+
const parent = BODIES[parentName];
|
|
478
|
+
if (!parent)
|
|
479
|
+
return '**Error**: Cannot determine parent body for transfer.';
|
|
480
|
+
const mu_parent = G * parent.mass;
|
|
481
|
+
const r1 = BODIES[bodyName]?.semiMajorAxis || r;
|
|
482
|
+
const r2 = BODIES[targetName]?.semiMajorAxis;
|
|
483
|
+
if (!r2)
|
|
484
|
+
return `**Error**: No orbital data for ${targetName}.`;
|
|
485
|
+
// Hohmann transfer
|
|
486
|
+
const a_transfer = (r1 + r2) / 2;
|
|
487
|
+
const T_transfer = Math.PI * Math.sqrt(a_transfer ** 3 / mu_parent);
|
|
488
|
+
// Delta-v at departure (vis-viva)
|
|
489
|
+
const v_circ1 = Math.sqrt(mu_parent / r1);
|
|
490
|
+
const v_transfer1 = Math.sqrt(mu_parent * (2 / r1 - 1 / a_transfer));
|
|
491
|
+
const dv1 = Math.abs(v_transfer1 - v_circ1);
|
|
492
|
+
// Delta-v at arrival
|
|
493
|
+
const v_circ2 = Math.sqrt(mu_parent / r2);
|
|
494
|
+
const v_transfer2 = Math.sqrt(mu_parent * (2 / r2 - 1 / a_transfer));
|
|
495
|
+
const dv2 = Math.abs(v_circ2 - v_transfer2);
|
|
496
|
+
const dv_total = dv1 + dv2;
|
|
497
|
+
lines.push('');
|
|
498
|
+
lines.push(`### Hohmann Transfer: ${bodyName} \u2192 ${targetName}`);
|
|
499
|
+
lines.push('');
|
|
500
|
+
lines.push(`| Quantity | Value |`);
|
|
501
|
+
lines.push(`|---|---|`);
|
|
502
|
+
lines.push(`| Departure orbit (r\u2081) | ${fmtUnit(r1 / AU, 'AU')} |`);
|
|
503
|
+
lines.push(`| Arrival orbit (r\u2082) | ${fmtUnit(r2 / AU, 'AU')} |`);
|
|
504
|
+
lines.push(`| Transfer semi-major axis | ${fmtUnit(a_transfer / AU, 'AU')} |`);
|
|
505
|
+
lines.push(`| Transfer time | ${fmtUnit(T_transfer, 's')} (${fmtUnit(T_transfer / 86400, 'days')}) |`);
|
|
506
|
+
lines.push(`| \u0394v\u2081 (departure burn) | ${fmtUnit(dv1, 'm/s')} (${fmtUnit(dv1 / 1000, 'km/s')}) |`);
|
|
507
|
+
lines.push(`| \u0394v\u2082 (arrival burn) | ${fmtUnit(dv2, 'm/s')} (${fmtUnit(dv2 / 1000, 'km/s')}) |`);
|
|
508
|
+
lines.push(`| Total \u0394v | ${fmtUnit(dv_total, 'm/s')} (${fmtUnit(dv_total / 1000, 'km/s')}) |`);
|
|
509
|
+
}
|
|
510
|
+
if (calc === 'lagrange' && targetName) {
|
|
511
|
+
const target = BODIES[targetName];
|
|
512
|
+
if (!target)
|
|
513
|
+
return `**Error**: Unknown body "${targetName}".`;
|
|
514
|
+
// L1-L5 points for two-body system
|
|
515
|
+
// Mass ratio
|
|
516
|
+
const M = body.mass;
|
|
517
|
+
const m = target.mass;
|
|
518
|
+
const R = target.semiMajorAxis;
|
|
519
|
+
if (!R)
|
|
520
|
+
return `**Error**: No orbital distance data for ${targetName}.`;
|
|
521
|
+
const q = m / (M + m); // mass ratio
|
|
522
|
+
// L1: approximately R * (1 - (q/3)^(1/3))
|
|
523
|
+
const rL1 = R * (1 - Math.pow(q / 3, 1 / 3));
|
|
524
|
+
// L2: approximately R * (1 + (q/3)^(1/3))
|
|
525
|
+
const rL2 = R * (1 + Math.pow(q / 3, 1 / 3));
|
|
526
|
+
// L3: approximately -R * (1 + 5*q/12)
|
|
527
|
+
const rL3 = R * (1 + 5 * q / 12);
|
|
528
|
+
lines.push('');
|
|
529
|
+
lines.push(`### Lagrange Points: ${bodyName}\u2013${targetName} System`);
|
|
530
|
+
lines.push('');
|
|
531
|
+
lines.push(`| Point | Distance from ${bodyName} | Notes |`);
|
|
532
|
+
lines.push(`|---|---|---|`);
|
|
533
|
+
lines.push(`| L1 | ${fmtUnit(rL1 / 1000, 'km')} (${fmtUnit(rL1 / AU, 'AU')}) | Between bodies, unstable |`);
|
|
534
|
+
lines.push(`| L2 | ${fmtUnit(rL2 / 1000, 'km')} (${fmtUnit(rL2 / AU, 'AU')}) | Beyond ${targetName}, unstable |`);
|
|
535
|
+
lines.push(`| L3 | ${fmtUnit(rL3 / 1000, 'km')} (${fmtUnit(rL3 / AU, 'AU')}) | Opposite side, unstable |`);
|
|
536
|
+
lines.push(`| L4 | R at 60\u00b0 ahead | Stable (equilateral) |`);
|
|
537
|
+
lines.push(`| L5 | R at 60\u00b0 behind | Stable (equilateral) |`);
|
|
538
|
+
lines.push('');
|
|
539
|
+
lines.push(`Mass ratio q = m/(M+m) = ${fmt(q)}`);
|
|
540
|
+
}
|
|
541
|
+
return lines.join('\n');
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
545
|
+
// 2. CIRCUIT ANALYZER
|
|
546
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
547
|
+
registerTool({
|
|
548
|
+
name: 'circuit_analyze',
|
|
549
|
+
description: 'DC/AC circuit analysis: Ohm\'s law, series/parallel resistors, voltage dividers, Wheatstone bridge, RC/RL/RLC transient and frequency response, impedance, resonance.',
|
|
550
|
+
parameters: {
|
|
551
|
+
circuit_type: { type: 'string', description: 'Circuit type: series, parallel, rlc, voltage_divider, wheatstone', required: true },
|
|
552
|
+
components: { type: 'string', description: 'JSON object with R (ohms), L (henries), C (farads), V (volts) arrays/values', required: true },
|
|
553
|
+
frequency: { type: 'number', description: 'Frequency in Hz for AC analysis', required: false },
|
|
554
|
+
},
|
|
555
|
+
tier: 'free',
|
|
556
|
+
async execute(args) {
|
|
557
|
+
const circuitType = String(args.circuit_type).toLowerCase();
|
|
558
|
+
let comp;
|
|
559
|
+
try {
|
|
560
|
+
comp = JSON.parse(String(args.components));
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return '**Error**: Invalid JSON in components parameter.';
|
|
564
|
+
}
|
|
565
|
+
const freq = args.frequency ? Number(args.frequency) : undefined;
|
|
566
|
+
const omega = freq ? 2 * Math.PI * freq : undefined;
|
|
567
|
+
const lines = ['## Circuit Analysis'];
|
|
568
|
+
lines.push('');
|
|
569
|
+
if (circuitType === 'series') {
|
|
570
|
+
const resistors = comp.R || [];
|
|
571
|
+
const V = comp.V || 0;
|
|
572
|
+
if (resistors.length === 0)
|
|
573
|
+
return '**Error**: Provide R array of resistor values in ohms.';
|
|
574
|
+
const R_total = resistors.reduce((a, b) => a + b, 0);
|
|
575
|
+
const I = V ? V / R_total : 0;
|
|
576
|
+
lines.push('### Series Resistors');
|
|
577
|
+
lines.push('');
|
|
578
|
+
lines.push(`| Component | Value |`);
|
|
579
|
+
lines.push(`|---|---|`);
|
|
580
|
+
resistors.forEach((r, i) => lines.push(`| R${i + 1} | ${fmtUnit(r, '\u03a9')} |`));
|
|
581
|
+
lines.push(`| **R_total** | **${fmtUnit(R_total, '\u03a9')}** |`);
|
|
582
|
+
if (V) {
|
|
583
|
+
lines.push(`| Supply voltage | ${fmtUnit(V, 'V')} |`);
|
|
584
|
+
lines.push(`| Current | ${fmtUnit(I, 'A')} |`);
|
|
585
|
+
lines.push(`| Power | ${fmtUnit(V * I, 'W')} |`);
|
|
586
|
+
lines.push('');
|
|
587
|
+
lines.push('#### Voltage drops');
|
|
588
|
+
lines.push('');
|
|
589
|
+
resistors.forEach((r, i) => lines.push(`- V_R${i + 1} = ${fmtUnit(I * r, 'V')}`));
|
|
590
|
+
}
|
|
591
|
+
// AC analysis if frequency provided
|
|
592
|
+
if (omega && comp.L) {
|
|
593
|
+
const L = comp.L;
|
|
594
|
+
const X_L = omega * L;
|
|
595
|
+
const Z = Math.sqrt(R_total ** 2 + X_L ** 2);
|
|
596
|
+
const phi = Math.atan2(X_L, R_total) * 180 / Math.PI;
|
|
597
|
+
lines.push('');
|
|
598
|
+
lines.push(`### AC Analysis @ ${freq} Hz`);
|
|
599
|
+
lines.push(`- Inductive reactance X_L = ${fmtUnit(X_L, '\u03a9')}`);
|
|
600
|
+
lines.push(`- Impedance |Z| = ${fmtUnit(Z, '\u03a9')}`);
|
|
601
|
+
lines.push(`- Phase angle = ${fmtUnit(phi, '\u00b0')}`);
|
|
602
|
+
}
|
|
603
|
+
if (omega && comp.C) {
|
|
604
|
+
const C = comp.C;
|
|
605
|
+
const X_C = 1 / (omega * C);
|
|
606
|
+
const Z = Math.sqrt(R_total ** 2 + X_C ** 2);
|
|
607
|
+
const phi = Math.atan2(-X_C, R_total) * 180 / Math.PI;
|
|
608
|
+
lines.push('');
|
|
609
|
+
lines.push(`### AC Analysis @ ${freq} Hz`);
|
|
610
|
+
lines.push(`- Capacitive reactance X_C = ${fmtUnit(X_C, '\u03a9')}`);
|
|
611
|
+
lines.push(`- Impedance |Z| = ${fmtUnit(Z, '\u03a9')}`);
|
|
612
|
+
lines.push(`- Phase angle = ${fmtUnit(phi, '\u00b0')}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else if (circuitType === 'parallel') {
|
|
616
|
+
const resistors = comp.R || [];
|
|
617
|
+
const V = comp.V || 0;
|
|
618
|
+
if (resistors.length === 0)
|
|
619
|
+
return '**Error**: Provide R array of resistor values in ohms.';
|
|
620
|
+
const G_total = resistors.reduce((a, r) => a + 1 / r, 0); // total conductance
|
|
621
|
+
const R_total = 1 / G_total;
|
|
622
|
+
const I_total = V ? V / R_total : 0;
|
|
623
|
+
lines.push('### Parallel Resistors');
|
|
624
|
+
lines.push('');
|
|
625
|
+
lines.push(`| Component | Value |`);
|
|
626
|
+
lines.push(`|---|---|`);
|
|
627
|
+
resistors.forEach((r, i) => lines.push(`| R${i + 1} | ${fmtUnit(r, '\u03a9')} |`));
|
|
628
|
+
lines.push(`| **R_total** | **${fmtUnit(R_total, '\u03a9')}** |`);
|
|
629
|
+
if (V) {
|
|
630
|
+
lines.push(`| Supply voltage | ${fmtUnit(V, 'V')} |`);
|
|
631
|
+
lines.push(`| Total current | ${fmtUnit(I_total, 'A')} |`);
|
|
632
|
+
lines.push(`| Total power | ${fmtUnit(V * I_total, 'W')} |`);
|
|
633
|
+
lines.push('');
|
|
634
|
+
lines.push('#### Branch currents');
|
|
635
|
+
lines.push('');
|
|
636
|
+
resistors.forEach((r, i) => lines.push(`- I_R${i + 1} = ${fmtUnit(V / r, 'A')}`));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
else if (circuitType === 'voltage_divider') {
|
|
640
|
+
const R1 = comp.R1 || comp.R?.[0];
|
|
641
|
+
const R2 = comp.R2 || comp.R?.[1];
|
|
642
|
+
const V_in = comp.V || comp.Vin || 0;
|
|
643
|
+
if (!R1 || !R2)
|
|
644
|
+
return '**Error**: Provide R1 and R2 (or R: [R1, R2]) for voltage divider.';
|
|
645
|
+
const V_out = V_in * R2 / (R1 + R2);
|
|
646
|
+
const I = V_in / (R1 + R2);
|
|
647
|
+
lines.push('### Voltage Divider');
|
|
648
|
+
lines.push('');
|
|
649
|
+
lines.push(`| Parameter | Value |`);
|
|
650
|
+
lines.push(`|---|---|`);
|
|
651
|
+
lines.push(`| R1 (top) | ${fmtUnit(R1, '\u03a9')} |`);
|
|
652
|
+
lines.push(`| R2 (bottom) | ${fmtUnit(R2, '\u03a9')} |`);
|
|
653
|
+
lines.push(`| V_in | ${fmtUnit(V_in, 'V')} |`);
|
|
654
|
+
lines.push(`| **V_out** | **${fmtUnit(V_out, 'V')}** |`);
|
|
655
|
+
lines.push(`| Ratio | ${fmt(R2 / (R1 + R2))} |`);
|
|
656
|
+
lines.push(`| Current | ${fmtUnit(I, 'A')} |`);
|
|
657
|
+
lines.push('');
|
|
658
|
+
lines.push(`**Formula**: V_out = V_in \u00d7 R2 / (R1 + R2)`);
|
|
659
|
+
}
|
|
660
|
+
else if (circuitType === 'wheatstone') {
|
|
661
|
+
const R1 = comp.R1;
|
|
662
|
+
const R2 = comp.R2;
|
|
663
|
+
const R3 = comp.R3;
|
|
664
|
+
const R4 = comp.R4;
|
|
665
|
+
const V = comp.V || 0;
|
|
666
|
+
if (!R1 || !R2 || !R3 || !R4)
|
|
667
|
+
return '**Error**: Provide R1, R2, R3, R4 for Wheatstone bridge.';
|
|
668
|
+
// Bridge voltage
|
|
669
|
+
const V_bridge = V * (R3 / (R3 + R1) - R4 / (R4 + R2));
|
|
670
|
+
const balanced = Math.abs(R1 * R4 - R2 * R3) < 1e-10;
|
|
671
|
+
lines.push('### Wheatstone Bridge');
|
|
672
|
+
lines.push('');
|
|
673
|
+
lines.push('```');
|
|
674
|
+
lines.push(` +--R1(${R1}\u03a9)--+--R2(${R2}\u03a9)--+`);
|
|
675
|
+
lines.push(` | | |`);
|
|
676
|
+
lines.push(` [V=${V}V] [V_bd] |`);
|
|
677
|
+
lines.push(` | | |`);
|
|
678
|
+
lines.push(` +--R3(${R3}\u03a9)--+--R4(${R4}\u03a9)--+`);
|
|
679
|
+
lines.push('```');
|
|
680
|
+
lines.push('');
|
|
681
|
+
lines.push(`| Parameter | Value |`);
|
|
682
|
+
lines.push(`|---|---|`);
|
|
683
|
+
lines.push(`| Bridge voltage | ${fmtUnit(V_bridge, 'V')} |`);
|
|
684
|
+
lines.push(`| Balanced? | ${balanced ? 'Yes (R1\u00b7R4 = R2\u00b7R3)' : 'No'} |`);
|
|
685
|
+
lines.push(`| R1\u00b7R4 | ${fmt(R1 * R4)} |`);
|
|
686
|
+
lines.push(`| R2\u00b7R3 | ${fmt(R2 * R3)} |`);
|
|
687
|
+
if (balanced) {
|
|
688
|
+
lines.push(`| Unknown R_x (if R4 unknown) | ${fmtUnit(R2 * R3 / R1, '\u03a9')} |`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else if (circuitType === 'rlc') {
|
|
692
|
+
const R = comp.R || 0;
|
|
693
|
+
const L = comp.L || 0;
|
|
694
|
+
const C = comp.C || 0;
|
|
695
|
+
const V = comp.V || 0;
|
|
696
|
+
if (!R && !L && !C)
|
|
697
|
+
return '**Error**: Provide at least one of R, L, C for RLC analysis.';
|
|
698
|
+
lines.push('### RLC Circuit Analysis');
|
|
699
|
+
lines.push('');
|
|
700
|
+
lines.push(`| Component | Value |`);
|
|
701
|
+
lines.push(`|---|---|`);
|
|
702
|
+
if (R)
|
|
703
|
+
lines.push(`| R | ${fmtUnit(R, '\u03a9')} |`);
|
|
704
|
+
if (L)
|
|
705
|
+
lines.push(`| L | ${fmtUnit(L, 'H')} |`);
|
|
706
|
+
if (C)
|
|
707
|
+
lines.push(`| C | ${fmtUnit(C, 'F')} |`);
|
|
708
|
+
if (V)
|
|
709
|
+
lines.push(`| V | ${fmtUnit(V, 'V')} |`);
|
|
710
|
+
// Resonance frequency
|
|
711
|
+
if (L && C) {
|
|
712
|
+
const f_res = 1 / (2 * Math.PI * Math.sqrt(L * C));
|
|
713
|
+
const omega_res = 2 * Math.PI * f_res;
|
|
714
|
+
const Q = R > 0 ? (1 / R) * Math.sqrt(L / C) : Infinity;
|
|
715
|
+
const bandwidth = R > 0 ? f_res / Q : 0;
|
|
716
|
+
lines.push('');
|
|
717
|
+
lines.push('### Resonance');
|
|
718
|
+
lines.push('');
|
|
719
|
+
lines.push(`| Parameter | Value |`);
|
|
720
|
+
lines.push(`|---|---|`);
|
|
721
|
+
lines.push(`| Resonant frequency | ${fmtUnit(f_res, 'Hz')} |`);
|
|
722
|
+
lines.push(`| Angular frequency \u03c9\u2080 | ${fmtUnit(omega_res, 'rad/s')} |`);
|
|
723
|
+
lines.push(`| Quality factor Q | ${fmt(Q)} |`);
|
|
724
|
+
lines.push(`| Bandwidth | ${fmtUnit(bandwidth, 'Hz')} |`);
|
|
725
|
+
lines.push(`| Characteristic impedance | ${fmtUnit(Math.sqrt(L / C), '\u03a9')} |`);
|
|
726
|
+
}
|
|
727
|
+
// Time constants
|
|
728
|
+
if (R && C && !L) {
|
|
729
|
+
const tau = R * C;
|
|
730
|
+
lines.push('');
|
|
731
|
+
lines.push(`### RC Time Constant`);
|
|
732
|
+
lines.push(`- \u03c4 = RC = ${fmtUnit(tau, 's')}`);
|
|
733
|
+
lines.push(`- Cutoff frequency f_c = ${fmtUnit(1 / (2 * Math.PI * tau), 'Hz')}`);
|
|
734
|
+
}
|
|
735
|
+
if (R && L && !C) {
|
|
736
|
+
const tau = L / R;
|
|
737
|
+
lines.push('');
|
|
738
|
+
lines.push(`### RL Time Constant`);
|
|
739
|
+
lines.push(`- \u03c4 = L/R = ${fmtUnit(tau, 's')}`);
|
|
740
|
+
lines.push(`- Cutoff frequency f_c = ${fmtUnit(R / (2 * Math.PI * L), 'Hz')}`);
|
|
741
|
+
}
|
|
742
|
+
// Damping analysis for RLC
|
|
743
|
+
if (R && L && C) {
|
|
744
|
+
const alpha = R / (2 * L); // damping coefficient
|
|
745
|
+
const omega0 = 1 / Math.sqrt(L * C); // natural frequency
|
|
746
|
+
const zeta = alpha / omega0; // damping ratio
|
|
747
|
+
lines.push('');
|
|
748
|
+
lines.push('### Damping Analysis');
|
|
749
|
+
lines.push('');
|
|
750
|
+
lines.push(`| Parameter | Value |`);
|
|
751
|
+
lines.push(`|---|---|`);
|
|
752
|
+
lines.push(`| Damping coefficient \u03b1 | ${fmtUnit(alpha, 's\u207b\u00b9')} |`);
|
|
753
|
+
lines.push(`| Natural frequency \u03c9\u2080 | ${fmtUnit(omega0, 'rad/s')} |`);
|
|
754
|
+
lines.push(`| Damping ratio \u03b6 | ${fmt(zeta)} |`);
|
|
755
|
+
lines.push(`| Response type | ${zeta > 1 ? 'Overdamped' : zeta === 1 ? 'Critically damped' : 'Underdamped'} |`);
|
|
756
|
+
if (zeta < 1) {
|
|
757
|
+
const omega_d = omega0 * Math.sqrt(1 - zeta ** 2);
|
|
758
|
+
lines.push(`| Damped frequency \u03c9_d | ${fmtUnit(omega_d, 'rad/s')} |`);
|
|
759
|
+
lines.push(`| Damped freq (Hz) | ${fmtUnit(omega_d / (2 * Math.PI), 'Hz')} |`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// AC impedance at given frequency
|
|
763
|
+
if (freq && omega) {
|
|
764
|
+
const X_L = L ? omega * L : 0;
|
|
765
|
+
const X_C = C ? 1 / (omega * C) : 0;
|
|
766
|
+
const X_net = X_L - X_C;
|
|
767
|
+
const Z_mag = Math.sqrt(R ** 2 + X_net ** 2);
|
|
768
|
+
const phi = Math.atan2(X_net, R) * 180 / Math.PI;
|
|
769
|
+
const I_mag = V ? V / Z_mag : 0;
|
|
770
|
+
lines.push('');
|
|
771
|
+
lines.push(`### AC Response @ ${freq} Hz`);
|
|
772
|
+
lines.push('');
|
|
773
|
+
lines.push(`| Parameter | Value |`);
|
|
774
|
+
lines.push(`|---|---|`);
|
|
775
|
+
lines.push(`| Inductive reactance X_L | ${fmtUnit(X_L, '\u03a9')} |`);
|
|
776
|
+
lines.push(`| Capacitive reactance X_C | ${fmtUnit(X_C, '\u03a9')} |`);
|
|
777
|
+
lines.push(`| Net reactance X | ${fmtUnit(X_net, '\u03a9')} |`);
|
|
778
|
+
lines.push(`| Impedance |Z| | ${fmtUnit(Z_mag, '\u03a9')} |`);
|
|
779
|
+
lines.push(`| Phase angle | ${fmtUnit(phi, '\u00b0')} |`);
|
|
780
|
+
if (V) {
|
|
781
|
+
lines.push(`| Current amplitude | ${fmtUnit(I_mag, 'A')} |`);
|
|
782
|
+
lines.push(`| Real power | ${fmtUnit(I_mag ** 2 * R, 'W')} |`);
|
|
783
|
+
lines.push(`| Reactive power | ${fmtUnit(I_mag ** 2 * Math.abs(X_net), 'VAR')} |`);
|
|
784
|
+
lines.push(`| Apparent power | ${fmtUnit(V * I_mag, 'VA')} |`);
|
|
785
|
+
lines.push(`| Power factor | ${fmt(Math.cos(phi * Math.PI / 180))} |`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
return `**Error**: Unknown circuit type "${circuitType}". Options: series, parallel, rlc, voltage_divider, wheatstone`;
|
|
791
|
+
}
|
|
792
|
+
return lines.join('\n');
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
796
|
+
// 3. SIGNAL PROCESSOR
|
|
797
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
798
|
+
registerTool({
|
|
799
|
+
name: 'signal_process',
|
|
800
|
+
description: 'Digital signal processing: FFT, lowpass/highpass/bandpass filters (FIR), convolution, autocorrelation, windowing (Hamming, Hanning, Blackman).',
|
|
801
|
+
parameters: {
|
|
802
|
+
signal: { type: 'string', description: 'Comma-separated signal values (e.g. "1,0,-1,0,1,0,-1,0")', required: true },
|
|
803
|
+
operation: { type: 'string', description: 'Operation: fft, lowpass, highpass, bandpass, convolve, autocorrelation, window', required: true },
|
|
804
|
+
params: { type: 'string', description: 'JSON params: {cutoff_freq, window_type, kernel, low_freq, high_freq, etc.}', required: false },
|
|
805
|
+
sample_rate: { type: 'number', description: 'Sample rate in Hz', required: true },
|
|
806
|
+
},
|
|
807
|
+
tier: 'free',
|
|
808
|
+
async execute(args) {
|
|
809
|
+
const signalStr = String(args.signal);
|
|
810
|
+
const operation = String(args.operation).toLowerCase();
|
|
811
|
+
const sampleRate = Number(args.sample_rate);
|
|
812
|
+
let params = {};
|
|
813
|
+
if (args.params) {
|
|
814
|
+
try {
|
|
815
|
+
params = JSON.parse(String(args.params));
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return '**Error**: Invalid JSON in params.';
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const signal = signalStr.split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n));
|
|
822
|
+
if (signal.length === 0)
|
|
823
|
+
return '**Error**: Empty or invalid signal data.';
|
|
824
|
+
const lines = ['## Signal Processing'];
|
|
825
|
+
lines.push('');
|
|
826
|
+
lines.push(`- Samples: ${signal.length}`);
|
|
827
|
+
lines.push(`- Sample rate: ${sampleRate} Hz`);
|
|
828
|
+
lines.push(`- Duration: ${fmtUnit(signal.length / sampleRate, 's')}`);
|
|
829
|
+
lines.push('');
|
|
830
|
+
if (operation === 'fft') {
|
|
831
|
+
const padded = padToPow2(signal);
|
|
832
|
+
const spectrum = fft(padded);
|
|
833
|
+
const N = padded.length;
|
|
834
|
+
const freqResolution = sampleRate / N;
|
|
835
|
+
lines.push('### FFT Spectrum');
|
|
836
|
+
lines.push('');
|
|
837
|
+
lines.push(`| Bin | Frequency (Hz) | Magnitude | Phase (\u00b0) |`);
|
|
838
|
+
lines.push(`|---|---|---|---|`);
|
|
839
|
+
const halfN = Math.floor(N / 2);
|
|
840
|
+
// Show up to 32 bins
|
|
841
|
+
const showBins = Math.min(halfN, 32);
|
|
842
|
+
for (let k = 0; k < showBins; k++) {
|
|
843
|
+
const freq_k = k * freqResolution;
|
|
844
|
+
const mag = Math.sqrt(cAbs2(spectrum[k])) / N;
|
|
845
|
+
const phase = Math.atan2(spectrum[k].im, spectrum[k].re) * 180 / Math.PI;
|
|
846
|
+
if (mag > 1e-10) {
|
|
847
|
+
lines.push(`| ${k} | ${fmtUnit(freq_k, 'Hz')} | ${fmt(mag)} | ${fmt(phase)} |`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (halfN > 32)
|
|
851
|
+
lines.push(`| ... | (${halfN - 32} more bins) | | |`);
|
|
852
|
+
// Dominant frequency
|
|
853
|
+
let maxMag = 0, maxBin = 0;
|
|
854
|
+
for (let k = 1; k < halfN; k++) {
|
|
855
|
+
const mag = cAbs2(spectrum[k]);
|
|
856
|
+
if (mag > maxMag) {
|
|
857
|
+
maxMag = mag;
|
|
858
|
+
maxBin = k;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
lines.push('');
|
|
862
|
+
lines.push(`**Dominant frequency**: ${fmtUnit(maxBin * freqResolution, 'Hz')} (bin ${maxBin}, magnitude ${fmt(Math.sqrt(maxMag) / N)})`);
|
|
863
|
+
lines.push(`**Frequency resolution**: ${fmtUnit(freqResolution, 'Hz')}`);
|
|
864
|
+
}
|
|
865
|
+
else if (operation === 'lowpass' || operation === 'highpass' || operation === 'bandpass') {
|
|
866
|
+
const cutoff = params.cutoff_freq || sampleRate / 4;
|
|
867
|
+
const lowFreq = params.low_freq || cutoff * 0.8;
|
|
868
|
+
const highFreq = params.high_freq || cutoff * 1.2;
|
|
869
|
+
const order = params.order || 21; // FIR filter order (must be odd)
|
|
870
|
+
// Design FIR filter via windowed sinc method
|
|
871
|
+
const M = order % 2 === 0 ? order + 1 : order;
|
|
872
|
+
const halfM = Math.floor(M / 2);
|
|
873
|
+
const window = hammingWindow(M);
|
|
874
|
+
let kernel;
|
|
875
|
+
if (operation === 'lowpass') {
|
|
876
|
+
const fc = cutoff / sampleRate; // normalized cutoff
|
|
877
|
+
kernel = Array.from({ length: M }, (_, n) => {
|
|
878
|
+
if (n === halfM)
|
|
879
|
+
return 2 * fc;
|
|
880
|
+
const x = n - halfM;
|
|
881
|
+
return Math.sin(2 * Math.PI * fc * x) / (Math.PI * x);
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
else if (operation === 'highpass') {
|
|
885
|
+
const fc = cutoff / sampleRate;
|
|
886
|
+
kernel = Array.from({ length: M }, (_, n) => {
|
|
887
|
+
if (n === halfM)
|
|
888
|
+
return 1 - 2 * fc;
|
|
889
|
+
const x = n - halfM;
|
|
890
|
+
return -Math.sin(2 * Math.PI * fc * x) / (Math.PI * x);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
// Bandpass = lowpass(high) - lowpass(low)
|
|
895
|
+
const fcLow = lowFreq / sampleRate;
|
|
896
|
+
const fcHigh = highFreq / sampleRate;
|
|
897
|
+
kernel = Array.from({ length: M }, (_, n) => {
|
|
898
|
+
if (n === halfM)
|
|
899
|
+
return 2 * (fcHigh - fcLow);
|
|
900
|
+
const x = n - halfM;
|
|
901
|
+
return (Math.sin(2 * Math.PI * fcHigh * x) - Math.sin(2 * Math.PI * fcLow * x)) / (Math.PI * x);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
// Apply window
|
|
905
|
+
kernel = kernel.map((v, i) => v * window[i]);
|
|
906
|
+
// Normalize
|
|
907
|
+
const sum = kernel.reduce((a, b) => a + b, 0);
|
|
908
|
+
if (operation !== 'highpass' && Math.abs(sum) > 1e-10) {
|
|
909
|
+
kernel = kernel.map(v => v / sum);
|
|
910
|
+
}
|
|
911
|
+
// Convolve signal with kernel
|
|
912
|
+
const output = [];
|
|
913
|
+
for (let i = 0; i < signal.length; i++) {
|
|
914
|
+
let val = 0;
|
|
915
|
+
for (let j = 0; j < kernel.length; j++) {
|
|
916
|
+
const idx = i - halfM + j;
|
|
917
|
+
if (idx >= 0 && idx < signal.length) {
|
|
918
|
+
val += signal[idx] * kernel[j];
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
output.push(val);
|
|
922
|
+
}
|
|
923
|
+
lines.push(`### ${operation.charAt(0).toUpperCase() + operation.slice(1)} Filter`);
|
|
924
|
+
lines.push('');
|
|
925
|
+
if (operation === 'bandpass') {
|
|
926
|
+
lines.push(`- Pass band: ${fmtUnit(lowFreq, 'Hz')} \u2013 ${fmtUnit(highFreq, 'Hz')}`);
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
lines.push(`- Cutoff frequency: ${fmtUnit(cutoff, 'Hz')}`);
|
|
930
|
+
}
|
|
931
|
+
lines.push(`- Filter order: ${M}`);
|
|
932
|
+
lines.push(`- Window: Hamming`);
|
|
933
|
+
lines.push('');
|
|
934
|
+
// Show first 50 output samples
|
|
935
|
+
const showN = Math.min(output.length, 50);
|
|
936
|
+
lines.push('### Filtered Output (first ' + showN + ' samples)');
|
|
937
|
+
lines.push('');
|
|
938
|
+
lines.push('```');
|
|
939
|
+
lines.push(output.slice(0, showN).map(v => v.toFixed(6)).join(', '));
|
|
940
|
+
lines.push('```');
|
|
941
|
+
// Statistics
|
|
942
|
+
const maxVal = Math.max(...output.map(Math.abs));
|
|
943
|
+
const rms = Math.sqrt(output.reduce((a, v) => a + v * v, 0) / output.length);
|
|
944
|
+
lines.push('');
|
|
945
|
+
lines.push(`- Peak amplitude: ${fmt(maxVal)}`);
|
|
946
|
+
lines.push(`- RMS: ${fmt(rms)}`);
|
|
947
|
+
}
|
|
948
|
+
else if (operation === 'convolve') {
|
|
949
|
+
const kernelStr = params.kernel || '';
|
|
950
|
+
const kernel = kernelStr.split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n));
|
|
951
|
+
if (kernel.length === 0)
|
|
952
|
+
return '**Error**: Provide params.kernel as comma-separated values.';
|
|
953
|
+
// Full convolution
|
|
954
|
+
const outLen = signal.length + kernel.length - 1;
|
|
955
|
+
const output = new Array(outLen).fill(0);
|
|
956
|
+
for (let i = 0; i < signal.length; i++) {
|
|
957
|
+
for (let j = 0; j < kernel.length; j++) {
|
|
958
|
+
output[i + j] += signal[i] * kernel[j];
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
lines.push('### Convolution');
|
|
962
|
+
lines.push('');
|
|
963
|
+
lines.push(`- Signal length: ${signal.length}`);
|
|
964
|
+
lines.push(`- Kernel length: ${kernel.length}`);
|
|
965
|
+
lines.push(`- Output length: ${outLen}`);
|
|
966
|
+
lines.push('');
|
|
967
|
+
const showN = Math.min(output.length, 60);
|
|
968
|
+
lines.push('```');
|
|
969
|
+
lines.push(output.slice(0, showN).map(v => v.toFixed(6)).join(', '));
|
|
970
|
+
if (outLen > showN)
|
|
971
|
+
lines.push(`... (${outLen - showN} more values)`);
|
|
972
|
+
lines.push('```');
|
|
973
|
+
}
|
|
974
|
+
else if (operation === 'autocorrelation') {
|
|
975
|
+
// Normalized autocorrelation via FFT
|
|
976
|
+
const N = nextPow2(signal.length * 2);
|
|
977
|
+
const padded = signal.map(v => ({ re: v, im: 0 }));
|
|
978
|
+
while (padded.length < N)
|
|
979
|
+
padded.push({ re: 0, im: 0 });
|
|
980
|
+
const spectrum = fft(padded);
|
|
981
|
+
// Power spectrum = |X(f)|^2
|
|
982
|
+
const power = spectrum.map(z => ({ re: cAbs2(z), im: 0 }));
|
|
983
|
+
// IFFT of power spectrum
|
|
984
|
+
const acf = ifft(power);
|
|
985
|
+
// Normalize by acf[0]
|
|
986
|
+
const acf0 = acf[0].re;
|
|
987
|
+
const normalizedAcf = acf.slice(0, signal.length).map(z => z.re / acf0);
|
|
988
|
+
lines.push('### Autocorrelation');
|
|
989
|
+
lines.push('');
|
|
990
|
+
const showN = Math.min(normalizedAcf.length, 40);
|
|
991
|
+
lines.push(`| Lag | Time (s) | R(lag) |`);
|
|
992
|
+
lines.push(`|---|---|---|`);
|
|
993
|
+
for (let i = 0; i < showN; i++) {
|
|
994
|
+
lines.push(`| ${i} | ${fmtUnit(i / sampleRate, 's')} | ${fmt(normalizedAcf[i])} |`);
|
|
995
|
+
}
|
|
996
|
+
// Find first zero crossing for periodicity estimate
|
|
997
|
+
let firstZero = -1;
|
|
998
|
+
for (let i = 1; i < normalizedAcf.length - 1; i++) {
|
|
999
|
+
if (normalizedAcf[i] * normalizedAcf[i + 1] < 0) {
|
|
1000
|
+
firstZero = i;
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (firstZero > 0) {
|
|
1005
|
+
lines.push('');
|
|
1006
|
+
lines.push(`**First zero crossing**: lag ${firstZero} (${fmtUnit(firstZero / sampleRate, 's')})`);
|
|
1007
|
+
lines.push(`**Estimated period**: ~${fmtUnit(2 * firstZero / sampleRate, 's')} (${fmtUnit(sampleRate / (2 * firstZero), 'Hz')})`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
else if (operation === 'window') {
|
|
1011
|
+
const windowType = (params.window_type || 'hamming').toLowerCase();
|
|
1012
|
+
const N = signal.length;
|
|
1013
|
+
let window;
|
|
1014
|
+
switch (windowType) {
|
|
1015
|
+
case 'hamming':
|
|
1016
|
+
window = hammingWindow(N);
|
|
1017
|
+
break;
|
|
1018
|
+
case 'hanning':
|
|
1019
|
+
case 'hann':
|
|
1020
|
+
window = hanningWindow(N);
|
|
1021
|
+
break;
|
|
1022
|
+
case 'blackman':
|
|
1023
|
+
window = blackmanWindow(N);
|
|
1024
|
+
break;
|
|
1025
|
+
default: return `**Error**: Unknown window type "${windowType}". Options: hamming, hanning, blackman`;
|
|
1026
|
+
}
|
|
1027
|
+
const windowed = signal.map((v, i) => v * window[i]);
|
|
1028
|
+
lines.push(`### ${windowType.charAt(0).toUpperCase() + windowType.slice(1)} Window`);
|
|
1029
|
+
lines.push('');
|
|
1030
|
+
const showN = Math.min(N, 50);
|
|
1031
|
+
lines.push('#### Window coefficients (first ' + showN + ')');
|
|
1032
|
+
lines.push('```');
|
|
1033
|
+
lines.push(window.slice(0, showN).map(v => v.toFixed(6)).join(', '));
|
|
1034
|
+
lines.push('```');
|
|
1035
|
+
lines.push('');
|
|
1036
|
+
lines.push('#### Windowed signal (first ' + showN + ')');
|
|
1037
|
+
lines.push('```');
|
|
1038
|
+
lines.push(windowed.slice(0, showN).map(v => v.toFixed(6)).join(', '));
|
|
1039
|
+
lines.push('```');
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
return `**Error**: Unknown operation "${operation}". Options: fft, lowpass, highpass, bandpass, convolve, autocorrelation, window`;
|
|
1043
|
+
}
|
|
1044
|
+
return lines.join('\n');
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1048
|
+
// 4. PARTICLE PHYSICS DATA
|
|
1049
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1050
|
+
registerTool({
|
|
1051
|
+
name: 'particle_physics_data',
|
|
1052
|
+
description: 'Look up particle properties from embedded PDG data. ~70 particles: leptons, quarks, gauge bosons, Higgs, mesons (pions, kaons, D, B, J/psi, Upsilon), baryons (proton, neutron, Lambda, Sigma, Xi, Omega, charmed/bottom baryons).',
|
|
1053
|
+
parameters: {
|
|
1054
|
+
particle: { type: 'string', description: 'Particle name, symbol, or key (e.g. "electron", "pi+", "J/psi", "higgs", "omega_minus")', required: true },
|
|
1055
|
+
},
|
|
1056
|
+
tier: 'free',
|
|
1057
|
+
async execute(args) {
|
|
1058
|
+
const query = String(args.particle).trim();
|
|
1059
|
+
// Try exact lookup first
|
|
1060
|
+
let p = lookupParticle(query);
|
|
1061
|
+
// Fuzzy search if not found
|
|
1062
|
+
if (!p) {
|
|
1063
|
+
const lowerQuery = query.toLowerCase();
|
|
1064
|
+
const candidates = Object.entries(PARTICLES).filter(([key, part]) => key.includes(lowerQuery) ||
|
|
1065
|
+
part.name.toLowerCase().includes(lowerQuery) ||
|
|
1066
|
+
part.symbol.toLowerCase().includes(lowerQuery) ||
|
|
1067
|
+
(part.quark_content && part.quark_content.toLowerCase().includes(lowerQuery)));
|
|
1068
|
+
if (candidates.length === 1) {
|
|
1069
|
+
p = candidates[0][1];
|
|
1070
|
+
}
|
|
1071
|
+
else if (candidates.length > 1) {
|
|
1072
|
+
const lines = [`**Multiple matches for "${query}"**:`, ''];
|
|
1073
|
+
for (const [key, part] of candidates) {
|
|
1074
|
+
lines.push(`- **${part.symbol}** (${part.name}) \u2014 key: \`${key}\``);
|
|
1075
|
+
}
|
|
1076
|
+
lines.push('');
|
|
1077
|
+
lines.push('Please specify one of the above keys.');
|
|
1078
|
+
return lines.join('\n');
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (!p) {
|
|
1082
|
+
// List all categories
|
|
1083
|
+
const categories = new Map();
|
|
1084
|
+
for (const [key, part] of Object.entries(PARTICLES)) {
|
|
1085
|
+
if (!categories.has(part.category))
|
|
1086
|
+
categories.set(part.category, []);
|
|
1087
|
+
categories.get(part.category).push(`${part.symbol} (\`${key}\`)`);
|
|
1088
|
+
}
|
|
1089
|
+
const lines = [`**Particle "${query}" not found.** Available particles:`, ''];
|
|
1090
|
+
for (const [cat, parts] of categories) {
|
|
1091
|
+
lines.push(`### ${cat}s`);
|
|
1092
|
+
lines.push(parts.join(', '));
|
|
1093
|
+
lines.push('');
|
|
1094
|
+
}
|
|
1095
|
+
return lines.join('\n');
|
|
1096
|
+
}
|
|
1097
|
+
const lines = [`## ${p.symbol} \u2014 ${p.name}`];
|
|
1098
|
+
lines.push('');
|
|
1099
|
+
lines.push(`| Property | Value |`);
|
|
1100
|
+
lines.push(`|---|---|`);
|
|
1101
|
+
lines.push(`| Category | ${p.category} |`);
|
|
1102
|
+
lines.push(`| Symbol | ${p.symbol} |`);
|
|
1103
|
+
if (p.mass_MeV !== null) {
|
|
1104
|
+
lines.push(`| Mass | ${fmtUnit(p.mass_MeV, 'MeV/c\u00b2')} |`);
|
|
1105
|
+
if (p.mass_MeV > 1000) {
|
|
1106
|
+
lines.push(`| Mass | ${fmtUnit(p.mass_MeV / 1000, 'GeV/c\u00b2')} |`);
|
|
1107
|
+
}
|
|
1108
|
+
lines.push(`| Mass (kg) | ${fmtUnit(p.mass_MeV * MeV / (c * c), 'kg')} |`);
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
lines.push(`| Mass | 0 (massless) |`);
|
|
1112
|
+
}
|
|
1113
|
+
lines.push(`| Charge | ${p.charge > 0 ? '+' : ''}${p.charge === Math.floor(p.charge) ? p.charge.toString() : (p.charge > 0 ? '+' : '') + (Math.abs(p.charge) === 1 / 3 ? (p.charge > 0 ? '1/3' : '-1/3') : (p.charge > 0 ? '2/3' : '-2/3'))} e |`);
|
|
1114
|
+
lines.push(`| Spin | ${p.spin} |`);
|
|
1115
|
+
if (p.lifetime_s === Infinity) {
|
|
1116
|
+
lines.push(`| Lifetime | Stable |`);
|
|
1117
|
+
}
|
|
1118
|
+
else if (p.lifetime_s !== null) {
|
|
1119
|
+
lines.push(`| Lifetime | ${fmtUnit(p.lifetime_s, 's')} |`);
|
|
1120
|
+
if (p.lifetime_s > 0) {
|
|
1121
|
+
lines.push(`| c\u03c4 | ${fmtUnit(p.lifetime_s * c, 'm')} |`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
lines.push(`| Lifetime | Mixes (see decay modes) |`);
|
|
1126
|
+
}
|
|
1127
|
+
if (p.quark_content)
|
|
1128
|
+
lines.push(`| Quark content | ${p.quark_content} |`);
|
|
1129
|
+
if (p.color_charge)
|
|
1130
|
+
lines.push(`| Color charge | ${p.color_charge} |`);
|
|
1131
|
+
if (p.isAntiparticle)
|
|
1132
|
+
lines.push(`| Antiparticle | Yes |`);
|
|
1133
|
+
if (p.decay_modes && p.decay_modes.length > 0) {
|
|
1134
|
+
lines.push('');
|
|
1135
|
+
lines.push('### Decay Modes');
|
|
1136
|
+
lines.push('');
|
|
1137
|
+
for (const mode of p.decay_modes) {
|
|
1138
|
+
lines.push(`- ${mode}`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return lines.join('\n');
|
|
1142
|
+
},
|
|
1143
|
+
});
|
|
1144
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1145
|
+
// 5. RELATIVITY CALCULATOR
|
|
1146
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1147
|
+
registerTool({
|
|
1148
|
+
name: 'relativity_calc',
|
|
1149
|
+
description: 'Special & general relativity: time dilation, length contraction, relativistic energy/momentum, Schwarzschild radius, gravitational time dilation, relativistic Doppler effect.',
|
|
1150
|
+
parameters: {
|
|
1151
|
+
calculation: { type: 'string', description: 'Calculation: time_dilation, length_contraction, energy, momentum, schwarzschild, grav_time_dilation, doppler', required: true },
|
|
1152
|
+
velocity: { type: 'number', description: 'Velocity as fraction of c (0-1) or m/s if > 1', required: true },
|
|
1153
|
+
mass: { type: 'number', description: 'Mass in kg (for energy/schwarzschild calculations)', required: false },
|
|
1154
|
+
params: { type: 'string', description: 'Additional JSON params: {proper_time, proper_length, distance, mass_body}', required: false },
|
|
1155
|
+
},
|
|
1156
|
+
tier: 'free',
|
|
1157
|
+
async execute(args) {
|
|
1158
|
+
const calc = String(args.calculation).toLowerCase();
|
|
1159
|
+
let v = Number(args.velocity);
|
|
1160
|
+
const mass = args.mass ? Number(args.mass) : undefined;
|
|
1161
|
+
let params = {};
|
|
1162
|
+
if (args.params) {
|
|
1163
|
+
try {
|
|
1164
|
+
params = JSON.parse(String(args.params));
|
|
1165
|
+
}
|
|
1166
|
+
catch { /* ignore */ }
|
|
1167
|
+
}
|
|
1168
|
+
// Interpret velocity: if > 1, assume m/s; otherwise fraction of c
|
|
1169
|
+
let beta;
|
|
1170
|
+
if (v > 1) {
|
|
1171
|
+
beta = v / c;
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
beta = v;
|
|
1175
|
+
v = beta * c;
|
|
1176
|
+
}
|
|
1177
|
+
if (beta >= 1)
|
|
1178
|
+
return '**Error**: Velocity must be less than c (speed of light). Massive objects cannot reach c.';
|
|
1179
|
+
const gamma = 1 / Math.sqrt(1 - beta * beta);
|
|
1180
|
+
const lines = ['## Relativistic Calculations'];
|
|
1181
|
+
lines.push('');
|
|
1182
|
+
lines.push(`| Parameter | Value |`);
|
|
1183
|
+
lines.push(`|---|---|`);
|
|
1184
|
+
lines.push(`| Velocity | ${fmtUnit(v, 'm/s')} |`);
|
|
1185
|
+
lines.push(`| \u03b2 (v/c) | ${fmt(beta)} |`);
|
|
1186
|
+
lines.push(`| \u03b3 (Lorentz factor) | ${fmt(gamma)} |`);
|
|
1187
|
+
if (calc === 'time_dilation') {
|
|
1188
|
+
const properTime = params.proper_time || 1; // default 1 second
|
|
1189
|
+
const dilatedTime = properTime * gamma;
|
|
1190
|
+
lines.push('');
|
|
1191
|
+
lines.push('### Time Dilation');
|
|
1192
|
+
lines.push('');
|
|
1193
|
+
lines.push(`| Quantity | Value |`);
|
|
1194
|
+
lines.push(`|---|---|`);
|
|
1195
|
+
lines.push(`| Proper time (\u0394\u03c4) | ${fmtUnit(properTime, 's')} |`);
|
|
1196
|
+
lines.push(`| Dilated time (\u0394t) | ${fmtUnit(dilatedTime, 's')} |`);
|
|
1197
|
+
lines.push(`| Ratio \u0394t/\u0394\u03c4 | ${fmt(gamma)} |`);
|
|
1198
|
+
lines.push('');
|
|
1199
|
+
lines.push(`**Formula**: \u0394t = \u03b3 \u00d7 \u0394\u03c4`);
|
|
1200
|
+
lines.push('');
|
|
1201
|
+
lines.push(`A clock moving at ${fmt(beta)}c runs ${fmt(gamma)} times slower as observed from the rest frame.`);
|
|
1202
|
+
}
|
|
1203
|
+
else if (calc === 'length_contraction') {
|
|
1204
|
+
const properLength = params.proper_length || 1; // default 1 meter
|
|
1205
|
+
const contractedLength = properLength / gamma;
|
|
1206
|
+
lines.push('');
|
|
1207
|
+
lines.push('### Length Contraction');
|
|
1208
|
+
lines.push('');
|
|
1209
|
+
lines.push(`| Quantity | Value |`);
|
|
1210
|
+
lines.push(`|---|---|`);
|
|
1211
|
+
lines.push(`| Proper length (L\u2080) | ${fmtUnit(properLength, 'm')} |`);
|
|
1212
|
+
lines.push(`| Contracted length (L) | ${fmtUnit(contractedLength, 'm')} |`);
|
|
1213
|
+
lines.push(`| Ratio L/L\u2080 | ${fmt(1 / gamma)} |`);
|
|
1214
|
+
lines.push('');
|
|
1215
|
+
lines.push(`**Formula**: L = L\u2080 / \u03b3`);
|
|
1216
|
+
}
|
|
1217
|
+
else if (calc === 'energy') {
|
|
1218
|
+
if (!mass)
|
|
1219
|
+
return '**Error**: Mass (kg) required for energy calculation.';
|
|
1220
|
+
const E_rest = mass * c * c;
|
|
1221
|
+
const E_total = gamma * E_rest;
|
|
1222
|
+
const E_kinetic = E_total - E_rest;
|
|
1223
|
+
lines.push('');
|
|
1224
|
+
lines.push('### Relativistic Energy');
|
|
1225
|
+
lines.push('');
|
|
1226
|
+
lines.push(`| Quantity | Value |`);
|
|
1227
|
+
lines.push(`|---|---|`);
|
|
1228
|
+
lines.push(`| Mass | ${fmtUnit(mass, 'kg')} |`);
|
|
1229
|
+
lines.push(`| Rest energy (E\u2080 = mc\u00b2) | ${fmtUnit(E_rest, 'J')} (${fmtUnit(E_rest / eV, 'eV')}) |`);
|
|
1230
|
+
lines.push(`| Total energy (E = \u03b3mc\u00b2) | ${fmtUnit(E_total, 'J')} (${fmtUnit(E_total / eV, 'eV')}) |`);
|
|
1231
|
+
lines.push(`| Kinetic energy (E_k) | ${fmtUnit(E_kinetic, 'J')} (${fmtUnit(E_kinetic / eV, 'eV')}) |`);
|
|
1232
|
+
lines.push(`| Classical K.E. (\u00bdmv\u00b2) | ${fmtUnit(0.5 * mass * v * v, 'J')} |`);
|
|
1233
|
+
lines.push(`| Relativistic correction | ${fmt(E_kinetic / (0.5 * mass * v * v))} \u00d7 classical |`);
|
|
1234
|
+
}
|
|
1235
|
+
else if (calc === 'momentum') {
|
|
1236
|
+
if (!mass)
|
|
1237
|
+
return '**Error**: Mass (kg) required for momentum calculation.';
|
|
1238
|
+
const p_rel = gamma * mass * v;
|
|
1239
|
+
const p_class = mass * v;
|
|
1240
|
+
const E_total = gamma * mass * c * c;
|
|
1241
|
+
lines.push('');
|
|
1242
|
+
lines.push('### Relativistic Momentum');
|
|
1243
|
+
lines.push('');
|
|
1244
|
+
lines.push(`| Quantity | Value |`);
|
|
1245
|
+
lines.push(`|---|---|`);
|
|
1246
|
+
lines.push(`| Relativistic p = \u03b3mv | ${fmtUnit(p_rel, 'kg\u00b7m/s')} |`);
|
|
1247
|
+
lines.push(`| Classical p = mv | ${fmtUnit(p_class, 'kg\u00b7m/s')} |`);
|
|
1248
|
+
lines.push(`| Ratio | ${fmt(gamma)} |`);
|
|
1249
|
+
lines.push(`| E\u00b2 = (pc)\u00b2 + (mc\u00b2)\u00b2 check | ${fmt(E_total)} = ${fmt(Math.sqrt((p_rel * c) ** 2 + (mass * c * c) ** 2))} |`);
|
|
1250
|
+
}
|
|
1251
|
+
else if (calc === 'schwarzschild') {
|
|
1252
|
+
const m = mass || params.mass_body || SOLAR_MASS;
|
|
1253
|
+
const r_s = 2 * G * m / (c * c);
|
|
1254
|
+
lines.push('');
|
|
1255
|
+
lines.push('### Schwarzschild Radius');
|
|
1256
|
+
lines.push('');
|
|
1257
|
+
lines.push(`| Quantity | Value |`);
|
|
1258
|
+
lines.push(`|---|---|`);
|
|
1259
|
+
lines.push(`| Mass | ${fmtUnit(m, 'kg')} (${fmtUnit(m / SOLAR_MASS, 'M\u2609')}) |`);
|
|
1260
|
+
lines.push(`| Schwarzschild radius | ${fmtUnit(r_s, 'm')} |`);
|
|
1261
|
+
lines.push(`| r_s | ${fmtUnit(r_s / 1000, 'km')} |`);
|
|
1262
|
+
lines.push(`| Surface gravity | ${fmtUnit(c ** 4 / (4 * G * m), 'm/s\u00b2')} |`);
|
|
1263
|
+
lines.push(`| Hawking temperature | ${fmtUnit(hbar * c ** 3 / (8 * Math.PI * G * m * k_B), 'K')} |`);
|
|
1264
|
+
lines.push('');
|
|
1265
|
+
lines.push(`**Formula**: r_s = 2GM/c\u00b2`);
|
|
1266
|
+
// Compare to known objects
|
|
1267
|
+
if (m === SOLAR_MASS) {
|
|
1268
|
+
lines.push('');
|
|
1269
|
+
lines.push('For reference: the Sun\'s Schwarzschild radius is ~3 km (Sun\'s actual radius: 696,340 km)');
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
else if (calc === 'grav_time_dilation') {
|
|
1273
|
+
const M = mass || params.mass_body || BODIES.earth.mass;
|
|
1274
|
+
const r = params.distance || BODIES.earth.radius;
|
|
1275
|
+
// Gravitational time dilation: dt_far / dt_near = 1/sqrt(1 - 2GM/(rc^2))
|
|
1276
|
+
const factor = Math.sqrt(1 - 2 * G * M / (r * c * c));
|
|
1277
|
+
const properTime = params.proper_time || 1;
|
|
1278
|
+
lines.push('');
|
|
1279
|
+
lines.push('### Gravitational Time Dilation');
|
|
1280
|
+
lines.push('');
|
|
1281
|
+
lines.push(`| Quantity | Value |`);
|
|
1282
|
+
lines.push(`|---|---|`);
|
|
1283
|
+
lines.push(`| Mass | ${fmtUnit(M, 'kg')} |`);
|
|
1284
|
+
lines.push(`| Distance from center | ${fmtUnit(r, 'm')} |`);
|
|
1285
|
+
lines.push(`| r_s / r | ${fmt(2 * G * M / (r * c * c))} |`);
|
|
1286
|
+
lines.push(`| Time dilation factor | ${fmt(factor)} |`);
|
|
1287
|
+
lines.push(`| ${fmtUnit(properTime, 's')} at r = | ${fmtUnit(properTime / factor, 's')} at \u221e |`);
|
|
1288
|
+
lines.push('');
|
|
1289
|
+
lines.push('A clock at distance r runs slower by this factor compared to a clock infinitely far away.');
|
|
1290
|
+
}
|
|
1291
|
+
else if (calc === 'doppler') {
|
|
1292
|
+
// Relativistic Doppler effect
|
|
1293
|
+
// Approaching: f_obs = f_source * sqrt((1+beta)/(1-beta))
|
|
1294
|
+
// Receding: f_obs = f_source * sqrt((1-beta)/(1+beta))
|
|
1295
|
+
const approaching = params.approaching !== false; // default true
|
|
1296
|
+
const ratio_approach = Math.sqrt((1 + beta) / (1 - beta));
|
|
1297
|
+
const ratio_recede = Math.sqrt((1 - beta) / (1 + beta));
|
|
1298
|
+
// Transverse Doppler (perpendicular): f_obs = f_source / gamma
|
|
1299
|
+
const ratio_transverse = 1 / gamma;
|
|
1300
|
+
lines.push('');
|
|
1301
|
+
lines.push('### Relativistic Doppler Effect');
|
|
1302
|
+
lines.push('');
|
|
1303
|
+
lines.push(`| Scenario | f_obs / f_source | Wavelength shift |`);
|
|
1304
|
+
lines.push(`|---|---|---|`);
|
|
1305
|
+
lines.push(`| Approaching | ${fmt(ratio_approach)} | Blueshift (${fmt((ratio_approach - 1) * 100)}%) |`);
|
|
1306
|
+
lines.push(`| Receding | ${fmt(ratio_recede)} | Redshift (${fmt((1 - ratio_recede) * 100)}%) |`);
|
|
1307
|
+
lines.push(`| Transverse | ${fmt(ratio_transverse)} | Redshift (${fmt((1 - ratio_transverse) * 100)}%) |`);
|
|
1308
|
+
lines.push('');
|
|
1309
|
+
lines.push(`**Note**: Transverse Doppler is a purely relativistic effect (no classical analogue).`);
|
|
1310
|
+
const sourceFreq = params.source_freq;
|
|
1311
|
+
if (sourceFreq) {
|
|
1312
|
+
lines.push('');
|
|
1313
|
+
lines.push(`For source frequency ${fmtUnit(sourceFreq, 'Hz')}:`);
|
|
1314
|
+
lines.push(`- Approaching: ${fmtUnit(sourceFreq * ratio_approach, 'Hz')}`);
|
|
1315
|
+
lines.push(`- Receding: ${fmtUnit(sourceFreq * ratio_recede, 'Hz')}`);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else {
|
|
1319
|
+
return `**Error**: Unknown calculation "${calc}". Options: time_dilation, length_contraction, energy, momentum, schwarzschild, grav_time_dilation, doppler`;
|
|
1320
|
+
}
|
|
1321
|
+
return lines.join('\n');
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1325
|
+
// 6. QUANTUM STATE SIMULATOR
|
|
1326
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1327
|
+
registerTool({
|
|
1328
|
+
name: 'quantum_state',
|
|
1329
|
+
description: 'Quantum state vector simulator for up to 8 qubits. Initialize states, apply gates (H, X, Y, Z, CNOT, T, S, SWAP, Toffoli), and measure. Full complex amplitude simulation.',
|
|
1330
|
+
parameters: {
|
|
1331
|
+
n_qubits: { type: 'number', description: 'Number of qubits (1-8)', required: true },
|
|
1332
|
+
initial_state: { type: 'string', description: 'Initial state as binary string (e.g. "00", "101") or "0" for all-zero', required: false },
|
|
1333
|
+
gates: { type: 'string', description: 'JSON array of gates: [{gate:"H", qubit:0}, {gate:"CNOT", control:0, target:1}, ...]', required: true },
|
|
1334
|
+
measure: { type: 'string', description: 'Whether to simulate measurement (true/false)', required: false },
|
|
1335
|
+
},
|
|
1336
|
+
tier: 'free',
|
|
1337
|
+
async execute(args) {
|
|
1338
|
+
const nQubits = Math.min(Math.max(Number(args.n_qubits) || 1, 1), 8);
|
|
1339
|
+
const dim = 1 << nQubits; // 2^n
|
|
1340
|
+
const initialStr = String(args.initial_state || '0');
|
|
1341
|
+
const shouldMeasure = String(args.measure || 'false').toLowerCase() === 'true';
|
|
1342
|
+
let gateOps;
|
|
1343
|
+
try {
|
|
1344
|
+
gateOps = JSON.parse(String(args.gates));
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
return '**Error**: Invalid JSON in gates parameter. Expected: [{gate:"H", qubit:0}, ...]';
|
|
1348
|
+
}
|
|
1349
|
+
// Initialize state vector
|
|
1350
|
+
let state = Array.from({ length: dim }, () => ({ re: 0, im: 0 }));
|
|
1351
|
+
// Parse initial state
|
|
1352
|
+
let initIdx = 0;
|
|
1353
|
+
if (initialStr.length === nQubits && /^[01]+$/.test(initialStr)) {
|
|
1354
|
+
initIdx = parseInt(initialStr, 2);
|
|
1355
|
+
}
|
|
1356
|
+
else if (/^\d+$/.test(initialStr)) {
|
|
1357
|
+
initIdx = parseInt(initialStr, 10);
|
|
1358
|
+
}
|
|
1359
|
+
if (initIdx >= dim)
|
|
1360
|
+
initIdx = 0;
|
|
1361
|
+
state[initIdx] = { re: 1, im: 0 };
|
|
1362
|
+
const lines = ['## Quantum State Simulation'];
|
|
1363
|
+
lines.push('');
|
|
1364
|
+
lines.push(`- Qubits: ${nQubits}`);
|
|
1365
|
+
lines.push(`- Hilbert space dimension: ${dim}`);
|
|
1366
|
+
lines.push(`- Initial state: |${initIdx.toString(2).padStart(nQubits, '0')}\u27e9`);
|
|
1367
|
+
lines.push('');
|
|
1368
|
+
// Apply gates
|
|
1369
|
+
lines.push('### Gate Sequence');
|
|
1370
|
+
lines.push('');
|
|
1371
|
+
for (const op of gateOps) {
|
|
1372
|
+
const gateName = String(op.gate || '').toUpperCase();
|
|
1373
|
+
const qubit = op.qubit !== undefined ? Number(op.qubit) : undefined;
|
|
1374
|
+
const control = op.control !== undefined ? Number(op.control) : undefined;
|
|
1375
|
+
const target = op.target !== undefined ? Number(op.target) : undefined;
|
|
1376
|
+
let gateDescription = '';
|
|
1377
|
+
if (['H', 'X', 'Y', 'Z', 'T', 'S'].includes(gateName) && qubit !== undefined) {
|
|
1378
|
+
const gateMap = { H: GATE_H, X: GATE_X, Y: GATE_Y, Z: GATE_Z, T: GATE_T, S: GATE_S };
|
|
1379
|
+
const fullGate = buildSingleQubitGate(gateMap[gateName], qubit, nQubits);
|
|
1380
|
+
state = applyMatrix(fullGate, state);
|
|
1381
|
+
gateDescription = `${gateName} on qubit ${qubit}`;
|
|
1382
|
+
}
|
|
1383
|
+
else if (gateName === 'CNOT' && control !== undefined && target !== undefined) {
|
|
1384
|
+
if (Math.abs(control - target) === 1) {
|
|
1385
|
+
const fullGate = buildTwoQubitGate(GATE_CNOT, control, target, nQubits);
|
|
1386
|
+
state = applyMatrix(fullGate, state);
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
// Non-adjacent: decompose via SWAP routing
|
|
1390
|
+
// For simplicity with small qubit counts, build the matrix directly
|
|
1391
|
+
const fullGate = Array.from({ length: dim }, (_, i) => Array.from({ length: dim }, (_, j) => ({ re: i === j ? 1 : 0, im: 0 })));
|
|
1392
|
+
// CNOT flips target bit when control bit is 1
|
|
1393
|
+
for (let i = 0; i < dim; i++) {
|
|
1394
|
+
const controlBit = (i >> (nQubits - 1 - control)) & 1;
|
|
1395
|
+
if (controlBit === 1) {
|
|
1396
|
+
const flipped = i ^ (1 << (nQubits - 1 - target));
|
|
1397
|
+
fullGate[i][i] = { re: 0, im: 0 };
|
|
1398
|
+
fullGate[i][flipped] = { re: 1, im: 0 };
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
state = applyMatrix(fullGate, state);
|
|
1402
|
+
}
|
|
1403
|
+
gateDescription = `CNOT(control=${control}, target=${target})`;
|
|
1404
|
+
}
|
|
1405
|
+
else if (gateName === 'SWAP' && control !== undefined && target !== undefined) {
|
|
1406
|
+
if (Math.abs(control - target) === 1) {
|
|
1407
|
+
const fullGate = buildTwoQubitGate(GATE_SWAP, control, target, nQubits);
|
|
1408
|
+
state = applyMatrix(fullGate, state);
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
// Direct matrix for non-adjacent SWAP
|
|
1412
|
+
const fullGate = Array.from({ length: dim }, (_, i) => Array.from({ length: dim }, (_, j) => ({ re: i === j ? 1 : 0, im: 0 })));
|
|
1413
|
+
for (let i = 0; i < dim; i++) {
|
|
1414
|
+
const bit1 = (i >> (nQubits - 1 - control)) & 1;
|
|
1415
|
+
const bit2 = (i >> (nQubits - 1 - target)) & 1;
|
|
1416
|
+
if (bit1 !== bit2) {
|
|
1417
|
+
const swapped = i ^ (1 << (nQubits - 1 - control)) ^ (1 << (nQubits - 1 - target));
|
|
1418
|
+
fullGate[i][i] = { re: 0, im: 0 };
|
|
1419
|
+
fullGate[i][swapped] = { re: 1, im: 0 };
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
state = applyMatrix(fullGate, state);
|
|
1423
|
+
}
|
|
1424
|
+
gateDescription = `SWAP(${control}, ${target})`;
|
|
1425
|
+
}
|
|
1426
|
+
else if (gateName === 'TOFFOLI' || gateName === 'CCX') {
|
|
1427
|
+
const c0 = control !== undefined ? control : Number(op.control1 ?? 0);
|
|
1428
|
+
const c1 = target !== undefined ? target : Number(op.control2 ?? 1);
|
|
1429
|
+
const tgt = op.target !== undefined ? Number(op.target) : Number(op.qubit ?? 2);
|
|
1430
|
+
// Build directly for arbitrary qubit positions
|
|
1431
|
+
const fullGate = Array.from({ length: dim }, (_, i) => Array.from({ length: dim }, (_, j) => ({ re: i === j ? 1 : 0, im: 0 })));
|
|
1432
|
+
for (let i = 0; i < dim; i++) {
|
|
1433
|
+
const cb0 = (i >> (nQubits - 1 - c0)) & 1;
|
|
1434
|
+
const cb1 = (i >> (nQubits - 1 - c1)) & 1;
|
|
1435
|
+
if (cb0 === 1 && cb1 === 1) {
|
|
1436
|
+
const flipped = i ^ (1 << (nQubits - 1 - tgt));
|
|
1437
|
+
fullGate[i][i] = { re: 0, im: 0 };
|
|
1438
|
+
fullGate[i][flipped] = { re: 1, im: 0 };
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
state = applyMatrix(fullGate, state);
|
|
1442
|
+
gateDescription = `Toffoli(c0=${c0}, c1=${c1}, target=${tgt})`;
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
lines.push(`- **?** Unknown gate "${gateName}" \u2014 skipped`);
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
lines.push(`- ${gateDescription}`);
|
|
1449
|
+
}
|
|
1450
|
+
// Display final state
|
|
1451
|
+
lines.push('');
|
|
1452
|
+
lines.push('### Final State Vector');
|
|
1453
|
+
lines.push('');
|
|
1454
|
+
lines.push(`| Basis State | Amplitude | Probability |`);
|
|
1455
|
+
lines.push(`|---|---|---|`);
|
|
1456
|
+
const probabilities = [];
|
|
1457
|
+
for (let i = 0; i < dim; i++) {
|
|
1458
|
+
const prob = cAbs2(state[i]);
|
|
1459
|
+
if (prob > 1e-10) {
|
|
1460
|
+
const basisStr = `|${i.toString(2).padStart(nQubits, '0')}\u27e9`;
|
|
1461
|
+
lines.push(`| ${basisStr} | ${cFmt(state[i])} | ${(prob * 100).toFixed(4)}% |`);
|
|
1462
|
+
probabilities.push({ state: i.toString(2).padStart(nQubits, '0'), prob });
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// Verify normalization
|
|
1466
|
+
const totalProb = probabilities.reduce((s, p) => s + p.prob, 0);
|
|
1467
|
+
lines.push('');
|
|
1468
|
+
lines.push(`**Total probability**: ${fmt(totalProb)} ${Math.abs(totalProb - 1) < 1e-6 ? '(normalized)' : '(WARNING: not normalized!)'}`);
|
|
1469
|
+
// Entanglement detection (simple: check if state is separable)
|
|
1470
|
+
if (nQubits === 2) {
|
|
1471
|
+
// For 2 qubits, check if |psi> = |a>|b> via concurrence
|
|
1472
|
+
const a = state[0], b = state[1], cc = state[2], d = state[3];
|
|
1473
|
+
const concurrence = 2 * Math.abs(a.re * d.re + a.im * d.im - b.re * cc.re - b.im * cc.im
|
|
1474
|
+
+ (a.re * d.im - a.im * d.re - b.re * cc.im + b.im * cc.re));
|
|
1475
|
+
lines.push(`**Concurrence**: ${fmt(concurrence)} ${concurrence > 0.01 ? '(entangled)' : '(separable)'}`);
|
|
1476
|
+
}
|
|
1477
|
+
// Simulated measurement
|
|
1478
|
+
if (shouldMeasure) {
|
|
1479
|
+
lines.push('');
|
|
1480
|
+
lines.push('### Measurement');
|
|
1481
|
+
lines.push('');
|
|
1482
|
+
// Weighted random selection
|
|
1483
|
+
const rand = Math.random();
|
|
1484
|
+
let cumulative = 0;
|
|
1485
|
+
let measured = probabilities[0]?.state || '0'.repeat(nQubits);
|
|
1486
|
+
for (const p of probabilities) {
|
|
1487
|
+
cumulative += p.prob;
|
|
1488
|
+
if (rand < cumulative) {
|
|
1489
|
+
measured = p.state;
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
lines.push(`**Measured outcome**: |${measured}\u27e9`);
|
|
1494
|
+
lines.push('');
|
|
1495
|
+
lines.push('Probability distribution:');
|
|
1496
|
+
for (const p of probabilities) {
|
|
1497
|
+
const bar = '\u2588'.repeat(Math.round(p.prob * 40));
|
|
1498
|
+
lines.push(` |${p.state}\u27e9 ${bar} ${(p.prob * 100).toFixed(2)}%`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return lines.join('\n');
|
|
1502
|
+
},
|
|
1503
|
+
});
|
|
1504
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1505
|
+
// 7. BEAM ANALYSIS
|
|
1506
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1507
|
+
registerTool({
|
|
1508
|
+
name: 'beam_analysis',
|
|
1509
|
+
description: 'Structural beam analysis using Euler-Bernoulli theory. Simply supported, cantilever, and fixed-fixed beams. Point loads, distributed loads, moments. Outputs reactions, shear/moment diagrams, max deflection.',
|
|
1510
|
+
parameters: {
|
|
1511
|
+
beam_type: { type: 'string', description: 'Beam type: simply_supported, cantilever, fixed_fixed', required: true },
|
|
1512
|
+
length: { type: 'number', description: 'Beam length in meters', required: true },
|
|
1513
|
+
loads: { type: 'string', description: 'JSON array of loads: [{type:"point", magnitude:1000, position:2.5}, {type:"distributed", magnitude:500, position:0, end_position:5}]', required: true },
|
|
1514
|
+
material: { type: 'string', description: 'Material name (steel, aluminum, timber, concrete) or JSON {E, I}', required: false },
|
|
1515
|
+
},
|
|
1516
|
+
tier: 'free',
|
|
1517
|
+
async execute(args) {
|
|
1518
|
+
const beamType = String(args.beam_type).toLowerCase().replace(/[\s-]/g, '_');
|
|
1519
|
+
const L = Number(args.length);
|
|
1520
|
+
let loads;
|
|
1521
|
+
try {
|
|
1522
|
+
loads = JSON.parse(String(args.loads));
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
return '**Error**: Invalid JSON in loads parameter.';
|
|
1526
|
+
}
|
|
1527
|
+
// Parse material
|
|
1528
|
+
let mat = DEFAULT_MATERIALS.steel;
|
|
1529
|
+
if (args.material) {
|
|
1530
|
+
const matStr = String(args.material).toLowerCase();
|
|
1531
|
+
if (DEFAULT_MATERIALS[matStr]) {
|
|
1532
|
+
mat = DEFAULT_MATERIALS[matStr];
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
try {
|
|
1536
|
+
mat = JSON.parse(String(args.material));
|
|
1537
|
+
}
|
|
1538
|
+
catch { /* use default */ }
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const EI = mat.E * mat.I;
|
|
1542
|
+
const lines = ['## Beam Analysis'];
|
|
1543
|
+
lines.push('');
|
|
1544
|
+
lines.push(`| Parameter | Value |`);
|
|
1545
|
+
lines.push(`|---|---|`);
|
|
1546
|
+
lines.push(`| Beam type | ${beamType.replace(/_/g, ' ')} |`);
|
|
1547
|
+
lines.push(`| Length | ${fmtUnit(L, 'm')} |`);
|
|
1548
|
+
lines.push(`| E (Young\'s modulus) | ${fmtUnit(mat.E, 'Pa')} (${fmtUnit(mat.E / 1e9, 'GPa')}) |`);
|
|
1549
|
+
lines.push(`| I (Second moment) | ${fmtUnit(mat.I, 'm\u2074')} |`);
|
|
1550
|
+
lines.push(`| EI | ${fmtUnit(EI, 'N\u00b7m\u00b2')} |`);
|
|
1551
|
+
// Applied loads
|
|
1552
|
+
lines.push('');
|
|
1553
|
+
lines.push('### Applied Loads');
|
|
1554
|
+
lines.push('');
|
|
1555
|
+
for (let i = 0; i < loads.length; i++) {
|
|
1556
|
+
const load = loads[i];
|
|
1557
|
+
if (load.type === 'point') {
|
|
1558
|
+
lines.push(`- Point load: ${fmtUnit(load.magnitude, 'N')} at x = ${fmtUnit(load.position, 'm')}`);
|
|
1559
|
+
}
|
|
1560
|
+
else if (load.type === 'distributed') {
|
|
1561
|
+
lines.push(`- Distributed load: ${fmtUnit(load.magnitude, 'N/m')} from x = ${fmtUnit(load.position, 'm')} to x = ${fmtUnit(load.end_position || L, 'm')}`);
|
|
1562
|
+
}
|
|
1563
|
+
else if (load.type === 'moment') {
|
|
1564
|
+
lines.push(`- Applied moment: ${fmtUnit(load.magnitude, 'N\u00b7m')} at x = ${fmtUnit(load.position, 'm')}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
// Calculate reactions and internal forces using superposition
|
|
1568
|
+
let R_A = 0, R_B = 0, M_A = 0, M_B = 0;
|
|
1569
|
+
// Shear force and bending moment at N points
|
|
1570
|
+
const N_POINTS = 51;
|
|
1571
|
+
const dx = L / (N_POINTS - 1);
|
|
1572
|
+
const shear = new Array(N_POINTS).fill(0);
|
|
1573
|
+
const moment = new Array(N_POINTS).fill(0);
|
|
1574
|
+
const deflection = new Array(N_POINTS).fill(0);
|
|
1575
|
+
if (beamType === 'simply_supported') {
|
|
1576
|
+
// Sum of forces and moments about A
|
|
1577
|
+
for (const load of loads) {
|
|
1578
|
+
if (load.type === 'point') {
|
|
1579
|
+
const P = load.magnitude;
|
|
1580
|
+
const a = load.position;
|
|
1581
|
+
// R_B*L = P*a => R_B = P*a/L, R_A = P*(L-a)/L
|
|
1582
|
+
R_B += P * a / L;
|
|
1583
|
+
R_A += P * (L - a) / L;
|
|
1584
|
+
// Shear and moment
|
|
1585
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1586
|
+
const x = i * dx;
|
|
1587
|
+
if (x < a) {
|
|
1588
|
+
shear[i] += P * (L - a) / L;
|
|
1589
|
+
moment[i] += P * (L - a) / L * x;
|
|
1590
|
+
}
|
|
1591
|
+
else {
|
|
1592
|
+
shear[i] += -P * a / L;
|
|
1593
|
+
moment[i] += P * a / L * (L - x);
|
|
1594
|
+
}
|
|
1595
|
+
// Deflection: standard formula for point load on SS beam
|
|
1596
|
+
if (x <= a) {
|
|
1597
|
+
deflection[i] += -P * (L - a) * x * (L * L - (L - a) * (L - a) - x * x) / (6 * EI * L);
|
|
1598
|
+
}
|
|
1599
|
+
else {
|
|
1600
|
+
deflection[i] += -P * a * (L - x) * (L * L - a * a - (L - x) * (L - x)) / (6 * EI * L);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
else if (load.type === 'distributed') {
|
|
1605
|
+
const w = load.magnitude;
|
|
1606
|
+
const a = load.position;
|
|
1607
|
+
const b = load.end_position || L;
|
|
1608
|
+
const loadLen = b - a;
|
|
1609
|
+
const W = w * loadLen; // total force
|
|
1610
|
+
const centroid = a + loadLen / 2;
|
|
1611
|
+
R_B += W * centroid / L;
|
|
1612
|
+
R_A += W * (L - centroid) / L;
|
|
1613
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1614
|
+
const x = i * dx;
|
|
1615
|
+
// Reaction contribution
|
|
1616
|
+
const ra = W * (L - centroid) / L;
|
|
1617
|
+
if (x <= a) {
|
|
1618
|
+
shear[i] += ra;
|
|
1619
|
+
moment[i] += ra * x;
|
|
1620
|
+
}
|
|
1621
|
+
else if (x <= b) {
|
|
1622
|
+
const loaded = x - a;
|
|
1623
|
+
shear[i] += ra - w * loaded;
|
|
1624
|
+
moment[i] += ra * x - w * loaded * loaded / 2;
|
|
1625
|
+
}
|
|
1626
|
+
else {
|
|
1627
|
+
shear[i] += ra - W;
|
|
1628
|
+
moment[i] += ra * x - W * (x - centroid);
|
|
1629
|
+
}
|
|
1630
|
+
// Deflection for UDL on full span (simplified for full-span case)
|
|
1631
|
+
if (a === 0 && b === L) {
|
|
1632
|
+
deflection[i] += -w * x * (L * L * L - 2 * L * x * x + x * x * x) / (24 * EI);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
else if (load.type === 'moment') {
|
|
1637
|
+
const M = load.magnitude;
|
|
1638
|
+
const a = load.position;
|
|
1639
|
+
R_A += -M / L;
|
|
1640
|
+
R_B += M / L;
|
|
1641
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1642
|
+
const x = i * dx;
|
|
1643
|
+
if (x < a) {
|
|
1644
|
+
shear[i] += -M / L;
|
|
1645
|
+
moment[i] += -M * x / L;
|
|
1646
|
+
}
|
|
1647
|
+
else {
|
|
1648
|
+
shear[i] += -M / L;
|
|
1649
|
+
moment[i] += M * (1 - x / L);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
else if (beamType === 'cantilever') {
|
|
1656
|
+
// Fixed at A (x=0), free at B (x=L)
|
|
1657
|
+
for (const load of loads) {
|
|
1658
|
+
if (load.type === 'point') {
|
|
1659
|
+
const P = load.magnitude;
|
|
1660
|
+
const a = load.position;
|
|
1661
|
+
R_A += P;
|
|
1662
|
+
M_A += -P * a;
|
|
1663
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1664
|
+
const x = i * dx;
|
|
1665
|
+
if (x < a) {
|
|
1666
|
+
shear[i] += P;
|
|
1667
|
+
moment[i] += -P * (a - x);
|
|
1668
|
+
}
|
|
1669
|
+
// else: shear = 0, moment = 0 beyond load
|
|
1670
|
+
// Deflection
|
|
1671
|
+
if (x <= a) {
|
|
1672
|
+
deflection[i] += -P * x * x * (3 * a - x) / (6 * EI);
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
deflection[i] += -P * a * a * (3 * x - a) / (6 * EI);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
else if (load.type === 'distributed') {
|
|
1680
|
+
const w = load.magnitude;
|
|
1681
|
+
const a = load.position;
|
|
1682
|
+
const b = load.end_position || L;
|
|
1683
|
+
const loadLen = b - a;
|
|
1684
|
+
const W = w * loadLen;
|
|
1685
|
+
R_A += W;
|
|
1686
|
+
M_A += -W * (a + loadLen / 2);
|
|
1687
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1688
|
+
const x = i * dx;
|
|
1689
|
+
if (x <= a) {
|
|
1690
|
+
shear[i] += W;
|
|
1691
|
+
moment[i] += -W * ((a + loadLen / 2) - x);
|
|
1692
|
+
}
|
|
1693
|
+
else if (x <= b) {
|
|
1694
|
+
const remaining = b - x;
|
|
1695
|
+
shear[i] += w * remaining;
|
|
1696
|
+
moment[i] += -w * remaining * remaining / 2;
|
|
1697
|
+
}
|
|
1698
|
+
// Full-span UDL deflection
|
|
1699
|
+
if (a === 0 && b === L) {
|
|
1700
|
+
deflection[i] += -w * x * x * (6 * L * L - 4 * L * x + x * x) / (24 * EI);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
else if (beamType === 'fixed_fixed') {
|
|
1707
|
+
// Both ends fixed
|
|
1708
|
+
for (const load of loads) {
|
|
1709
|
+
if (load.type === 'point') {
|
|
1710
|
+
const P = load.magnitude;
|
|
1711
|
+
const a = load.position;
|
|
1712
|
+
const b = L - a;
|
|
1713
|
+
// Fixed-fixed beam with point load
|
|
1714
|
+
R_A += P * b * b * (3 * a + b) / (L * L * L);
|
|
1715
|
+
R_B += P * a * a * (a + 3 * b) / (L * L * L);
|
|
1716
|
+
M_A += -P * a * b * b / (L * L);
|
|
1717
|
+
M_B += P * a * a * b / (L * L);
|
|
1718
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1719
|
+
const x = i * dx;
|
|
1720
|
+
const ra = P * b * b * (3 * a + b) / (L * L * L);
|
|
1721
|
+
const ma = -P * a * b * b / (L * L);
|
|
1722
|
+
if (x <= a) {
|
|
1723
|
+
shear[i] += ra;
|
|
1724
|
+
moment[i] += ma + ra * x;
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
shear[i] += ra - P;
|
|
1728
|
+
moment[i] += ma + ra * x - P * (x - a);
|
|
1729
|
+
}
|
|
1730
|
+
// Deflection
|
|
1731
|
+
if (x <= a) {
|
|
1732
|
+
deflection[i] += -P * b * b * x * x * (3 * a * L - x * (3 * a + b)) / (6 * EI * L * L * L);
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
deflection[i] += -P * a * a * (L - x) * (L - x) * (3 * b * L - (L - x) * (3 * b + a)) / (6 * EI * L * L * L);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
else if (load.type === 'distributed' && load.position === 0 && (load.end_position || L) === L) {
|
|
1740
|
+
// Full-span UDL on fixed-fixed beam
|
|
1741
|
+
const w = load.magnitude;
|
|
1742
|
+
R_A += w * L / 2;
|
|
1743
|
+
R_B += w * L / 2;
|
|
1744
|
+
M_A += -w * L * L / 12;
|
|
1745
|
+
M_B += w * L * L / 12;
|
|
1746
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1747
|
+
const x = i * dx;
|
|
1748
|
+
shear[i] += w * L / 2 - w * x;
|
|
1749
|
+
moment[i] += -w * L * L / 12 + w * L * x / 2 - w * x * x / 2;
|
|
1750
|
+
deflection[i] += -w * x * x * (L - x) * (L - x) / (24 * EI);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
return `**Error**: Unknown beam type "${beamType}". Options: simply_supported, cantilever, fixed_fixed`;
|
|
1757
|
+
}
|
|
1758
|
+
// Reactions
|
|
1759
|
+
lines.push('');
|
|
1760
|
+
lines.push('### Reactions');
|
|
1761
|
+
lines.push('');
|
|
1762
|
+
lines.push(`| Reaction | Value |`);
|
|
1763
|
+
lines.push(`|---|---|`);
|
|
1764
|
+
lines.push(`| R_A (left) | ${fmtUnit(R_A, 'N')} |`);
|
|
1765
|
+
if (beamType !== 'cantilever')
|
|
1766
|
+
lines.push(`| R_B (right) | ${fmtUnit(R_B, 'N')} |`);
|
|
1767
|
+
if (M_A !== 0)
|
|
1768
|
+
lines.push(`| M_A (left moment) | ${fmtUnit(M_A, 'N\u00b7m')} |`);
|
|
1769
|
+
if (M_B !== 0)
|
|
1770
|
+
lines.push(`| M_B (right moment) | ${fmtUnit(M_B, 'N\u00b7m')} |`);
|
|
1771
|
+
// Find max values
|
|
1772
|
+
let maxShear = 0, maxShearX = 0, maxMoment = 0, maxMomentX = 0, maxDefl = 0, maxDeflX = 0;
|
|
1773
|
+
for (let i = 0; i < N_POINTS; i++) {
|
|
1774
|
+
if (Math.abs(shear[i]) > Math.abs(maxShear)) {
|
|
1775
|
+
maxShear = shear[i];
|
|
1776
|
+
maxShearX = i * dx;
|
|
1777
|
+
}
|
|
1778
|
+
if (Math.abs(moment[i]) > Math.abs(maxMoment)) {
|
|
1779
|
+
maxMoment = moment[i];
|
|
1780
|
+
maxMomentX = i * dx;
|
|
1781
|
+
}
|
|
1782
|
+
if (Math.abs(deflection[i]) > Math.abs(maxDefl)) {
|
|
1783
|
+
maxDefl = deflection[i];
|
|
1784
|
+
maxDeflX = i * dx;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
lines.push('');
|
|
1788
|
+
lines.push('### Critical Values');
|
|
1789
|
+
lines.push('');
|
|
1790
|
+
lines.push(`| Quantity | Maximum | Location |`);
|
|
1791
|
+
lines.push(`|---|---|---|`);
|
|
1792
|
+
lines.push(`| Shear force | ${fmtUnit(maxShear, 'N')} | x = ${fmtUnit(maxShearX, 'm')} |`);
|
|
1793
|
+
lines.push(`| Bending moment | ${fmtUnit(maxMoment, 'N\u00b7m')} | x = ${fmtUnit(maxMomentX, 'm')} |`);
|
|
1794
|
+
lines.push(`| Deflection | ${fmtUnit(maxDefl, 'm')} (${fmtUnit(maxDefl * 1000, 'mm')}) | x = ${fmtUnit(maxDeflX, 'm')} |`);
|
|
1795
|
+
// Shear diagram (ASCII)
|
|
1796
|
+
lines.push('');
|
|
1797
|
+
lines.push('### Shear Force Diagram');
|
|
1798
|
+
lines.push('');
|
|
1799
|
+
lines.push('```');
|
|
1800
|
+
const shearMax = Math.max(...shear.map(Math.abs)) || 1;
|
|
1801
|
+
for (let i = 0; i < N_POINTS; i += 2) {
|
|
1802
|
+
const x = (i * dx).toFixed(2).padStart(5);
|
|
1803
|
+
const val = shear[i];
|
|
1804
|
+
const barLen = Math.round(Math.abs(val) / shearMax * 25);
|
|
1805
|
+
const bar = val >= 0 ? ' '.repeat(25) + '\u2502' + '\u2588'.repeat(barLen) : ' '.repeat(25 - barLen) + '\u2588'.repeat(barLen) + '\u2502';
|
|
1806
|
+
lines.push(`${x}m ${bar} ${val.toFixed(1)}N`);
|
|
1807
|
+
}
|
|
1808
|
+
lines.push('```');
|
|
1809
|
+
// Bending moment diagram
|
|
1810
|
+
lines.push('');
|
|
1811
|
+
lines.push('### Bending Moment Diagram');
|
|
1812
|
+
lines.push('');
|
|
1813
|
+
lines.push('```');
|
|
1814
|
+
const momMax = Math.max(...moment.map(Math.abs)) || 1;
|
|
1815
|
+
for (let i = 0; i < N_POINTS; i += 2) {
|
|
1816
|
+
const x = (i * dx).toFixed(2).padStart(5);
|
|
1817
|
+
const val = moment[i];
|
|
1818
|
+
const barLen = Math.round(Math.abs(val) / momMax * 25);
|
|
1819
|
+
const bar = val >= 0 ? ' '.repeat(25) + '\u2502' + '\u2588'.repeat(barLen) : ' '.repeat(25 - barLen) + '\u2588'.repeat(barLen) + '\u2502';
|
|
1820
|
+
lines.push(`${x}m ${bar} ${val.toFixed(1)}Nm`);
|
|
1821
|
+
}
|
|
1822
|
+
lines.push('```');
|
|
1823
|
+
return lines.join('\n');
|
|
1824
|
+
},
|
|
1825
|
+
});
|
|
1826
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1827
|
+
// 8. FLUID DYNAMICS
|
|
1828
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1829
|
+
registerTool({
|
|
1830
|
+
name: 'fluid_dynamics',
|
|
1831
|
+
description: 'Fluid mechanics calculations: Reynolds number, Bernoulli equation, Darcy-Weisbach pipe flow, drag force, terminal velocity.',
|
|
1832
|
+
parameters: {
|
|
1833
|
+
calculation: { type: 'string', description: 'Calculation: reynolds, bernoulli, pipe_flow, drag, terminal_velocity', required: true },
|
|
1834
|
+
params: { type: 'string', description: 'JSON with: velocity (m/s), density (kg/m^3), viscosity (Pa*s), diameter (m), length (m), roughness (m), area (m^2), Cd, mass (kg), pressure (Pa), height (m)', required: true },
|
|
1835
|
+
},
|
|
1836
|
+
tier: 'free',
|
|
1837
|
+
async execute(args) {
|
|
1838
|
+
const calc = String(args.calculation).toLowerCase();
|
|
1839
|
+
let params;
|
|
1840
|
+
try {
|
|
1841
|
+
params = JSON.parse(String(args.params));
|
|
1842
|
+
}
|
|
1843
|
+
catch {
|
|
1844
|
+
return '**Error**: Invalid JSON in params.';
|
|
1845
|
+
}
|
|
1846
|
+
// Common fluid properties
|
|
1847
|
+
const FLUIDS = {
|
|
1848
|
+
water: { density: 998, viscosity: 1.002e-3 },
|
|
1849
|
+
air: { density: 1.225, viscosity: 1.81e-5 },
|
|
1850
|
+
oil: { density: 900, viscosity: 0.1 },
|
|
1851
|
+
mercury: { density: 13_546, viscosity: 1.526e-3 },
|
|
1852
|
+
};
|
|
1853
|
+
const lines = ['## Fluid Dynamics'];
|
|
1854
|
+
lines.push('');
|
|
1855
|
+
if (calc === 'reynolds') {
|
|
1856
|
+
const rho = params.density || FLUIDS.water.density;
|
|
1857
|
+
const mu = params.viscosity || FLUIDS.water.viscosity;
|
|
1858
|
+
const v = params.velocity || 1;
|
|
1859
|
+
const D = params.diameter || params.length || 0.01;
|
|
1860
|
+
const Re = rho * v * D / mu;
|
|
1861
|
+
lines.push('### Reynolds Number');
|
|
1862
|
+
lines.push('');
|
|
1863
|
+
lines.push(`| Parameter | Value |`);
|
|
1864
|
+
lines.push(`|---|---|`);
|
|
1865
|
+
lines.push(`| Density (\u03c1) | ${fmtUnit(rho, 'kg/m\u00b3')} |`);
|
|
1866
|
+
lines.push(`| Velocity (v) | ${fmtUnit(v, 'm/s')} |`);
|
|
1867
|
+
lines.push(`| Characteristic length (D) | ${fmtUnit(D, 'm')} |`);
|
|
1868
|
+
lines.push(`| Dynamic viscosity (\u03bc) | ${fmtUnit(mu, 'Pa\u00b7s')} |`);
|
|
1869
|
+
lines.push(`| **Reynolds number (Re)** | **${fmt(Re)}** |`);
|
|
1870
|
+
lines.push('');
|
|
1871
|
+
let regime = 'Turbulent';
|
|
1872
|
+
if (Re < 2300)
|
|
1873
|
+
regime = 'Laminar';
|
|
1874
|
+
else if (Re < 4000)
|
|
1875
|
+
regime = 'Transitional';
|
|
1876
|
+
lines.push(`**Flow regime**: ${regime}`);
|
|
1877
|
+
lines.push('');
|
|
1878
|
+
lines.push('| Range | Regime |');
|
|
1879
|
+
lines.push('|---|---|');
|
|
1880
|
+
lines.push('| Re < 2,300 | Laminar |');
|
|
1881
|
+
lines.push('| 2,300 < Re < 4,000 | Transitional |');
|
|
1882
|
+
lines.push('| Re > 4,000 | Turbulent |');
|
|
1883
|
+
}
|
|
1884
|
+
else if (calc === 'bernoulli') {
|
|
1885
|
+
// Bernoulli: P1 + 0.5*rho*v1^2 + rho*g*h1 = P2 + 0.5*rho*v2^2 + rho*g*h2
|
|
1886
|
+
const rho = params.density || FLUIDS.water.density;
|
|
1887
|
+
const g = 9.81;
|
|
1888
|
+
const P1 = params.pressure1 || params.pressure || 101325;
|
|
1889
|
+
const v1 = params.velocity1 || params.velocity || 0;
|
|
1890
|
+
const h1 = params.height1 || params.height || 0;
|
|
1891
|
+
const P2 = params.pressure2;
|
|
1892
|
+
const v2 = params.velocity2;
|
|
1893
|
+
const h2 = params.height2 || 0;
|
|
1894
|
+
const E1 = P1 + 0.5 * rho * v1 * v1 + rho * g * h1;
|
|
1895
|
+
lines.push('### Bernoulli Equation');
|
|
1896
|
+
lines.push('');
|
|
1897
|
+
lines.push('P + \u00bd\u03c1v\u00b2 + \u03c1gh = constant');
|
|
1898
|
+
lines.push('');
|
|
1899
|
+
lines.push(`| Parameter | Point 1 | Point 2 |`);
|
|
1900
|
+
lines.push(`|---|---|---|`);
|
|
1901
|
+
lines.push(`| Pressure (Pa) | ${fmtUnit(P1, 'Pa')} | ${P2 !== undefined ? fmtUnit(P2, 'Pa') : '?'} |`);
|
|
1902
|
+
lines.push(`| Velocity (m/s) | ${fmtUnit(v1, 'm/s')} | ${v2 !== undefined ? fmtUnit(v2, 'm/s') : '?'} |`);
|
|
1903
|
+
lines.push(`| Height (m) | ${fmtUnit(h1, 'm')} | ${fmtUnit(h2, 'm')} |`);
|
|
1904
|
+
lines.push(`| Total head | ${fmtUnit(E1, 'Pa')} | \u2014 |`);
|
|
1905
|
+
// Solve for unknown
|
|
1906
|
+
if (P2 === undefined && v2 !== undefined) {
|
|
1907
|
+
const P2_calc = E1 - 0.5 * rho * v2 * v2 - rho * g * h2;
|
|
1908
|
+
lines.push('');
|
|
1909
|
+
lines.push(`**Solved P\u2082** = ${fmtUnit(P2_calc, 'Pa')} (${fmtUnit(P2_calc / 1000, 'kPa')})`);
|
|
1910
|
+
}
|
|
1911
|
+
else if (v2 === undefined && P2 !== undefined) {
|
|
1912
|
+
const v2_calc = Math.sqrt(2 * (E1 - P2 - rho * g * h2) / rho);
|
|
1913
|
+
lines.push('');
|
|
1914
|
+
lines.push(`**Solved v\u2082** = ${fmtUnit(v2_calc, 'm/s')}`);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
else if (calc === 'pipe_flow') {
|
|
1918
|
+
const rho = params.density || FLUIDS.water.density;
|
|
1919
|
+
const mu = params.viscosity || FLUIDS.water.viscosity;
|
|
1920
|
+
const v = params.velocity || 1;
|
|
1921
|
+
const D = params.diameter || 0.05;
|
|
1922
|
+
const L_pipe = params.length || 10;
|
|
1923
|
+
const roughness = params.roughness || 0.046e-3; // commercial steel
|
|
1924
|
+
const g = 9.81;
|
|
1925
|
+
const Re = rho * v * D / mu;
|
|
1926
|
+
const relRough = roughness / D;
|
|
1927
|
+
// Friction factor (Colebrook-White, solved iteratively)
|
|
1928
|
+
let f;
|
|
1929
|
+
if (Re < 2300) {
|
|
1930
|
+
f = 64 / Re; // Laminar
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
// Swamee-Jain approximation
|
|
1934
|
+
f = 0.25 / Math.pow(Math.log10(relRough / 3.7 + 5.74 / Math.pow(Re, 0.9)), 2);
|
|
1935
|
+
}
|
|
1936
|
+
// Darcy-Weisbach head loss
|
|
1937
|
+
const h_f = f * (L_pipe / D) * (v * v) / (2 * g);
|
|
1938
|
+
const dP = f * (L_pipe / D) * rho * v * v / 2;
|
|
1939
|
+
// Flow rate
|
|
1940
|
+
const A = Math.PI * D * D / 4;
|
|
1941
|
+
const Q = A * v;
|
|
1942
|
+
lines.push('### Pipe Flow (Darcy-Weisbach)');
|
|
1943
|
+
lines.push('');
|
|
1944
|
+
lines.push(`| Parameter | Value |`);
|
|
1945
|
+
lines.push(`|---|---|`);
|
|
1946
|
+
lines.push(`| Pipe diameter | ${fmtUnit(D, 'm')} (${fmtUnit(D * 1000, 'mm')}) |`);
|
|
1947
|
+
lines.push(`| Pipe length | ${fmtUnit(L_pipe, 'm')} |`);
|
|
1948
|
+
lines.push(`| Flow velocity | ${fmtUnit(v, 'm/s')} |`);
|
|
1949
|
+
lines.push(`| Reynolds number | ${fmt(Re)} |`);
|
|
1950
|
+
lines.push(`| Relative roughness | ${fmt(relRough)} |`);
|
|
1951
|
+
lines.push(`| Friction factor (f) | ${fmt(f)} |`);
|
|
1952
|
+
lines.push(`| **Head loss** | **${fmtUnit(h_f, 'm')}** |`);
|
|
1953
|
+
lines.push(`| **Pressure drop** | **${fmtUnit(dP, 'Pa')}** (${fmtUnit(dP / 1000, 'kPa')}) |`);
|
|
1954
|
+
lines.push(`| Flow rate | ${fmtUnit(Q, 'm\u00b3/s')} (${fmtUnit(Q * 1000, 'L/s')}) |`);
|
|
1955
|
+
lines.push(`| Flow regime | ${Re < 2300 ? 'Laminar' : Re < 4000 ? 'Transitional' : 'Turbulent'} |`);
|
|
1956
|
+
}
|
|
1957
|
+
else if (calc === 'drag') {
|
|
1958
|
+
const rho = params.density || FLUIDS.air.density;
|
|
1959
|
+
const v = params.velocity || 10;
|
|
1960
|
+
const A = params.area || 1;
|
|
1961
|
+
const Cd = params.Cd || params.cd || 0.47; // sphere
|
|
1962
|
+
const F_drag = 0.5 * Cd * rho * A * v * v;
|
|
1963
|
+
lines.push('### Drag Force');
|
|
1964
|
+
lines.push('');
|
|
1965
|
+
lines.push(`| Parameter | Value |`);
|
|
1966
|
+
lines.push(`|---|---|`);
|
|
1967
|
+
lines.push(`| Fluid density | ${fmtUnit(rho, 'kg/m\u00b3')} |`);
|
|
1968
|
+
lines.push(`| Velocity | ${fmtUnit(v, 'm/s')} |`);
|
|
1969
|
+
lines.push(`| Reference area | ${fmtUnit(A, 'm\u00b2')} |`);
|
|
1970
|
+
lines.push(`| Drag coefficient (C_d) | ${fmt(Cd)} |`);
|
|
1971
|
+
lines.push(`| **Drag force** | **${fmtUnit(F_drag, 'N')}** |`);
|
|
1972
|
+
lines.push('');
|
|
1973
|
+
lines.push('Common C_d values: sphere (0.47), cylinder (1.2), flat plate (1.28), streamlined (0.04), car (0.25\u20130.35)');
|
|
1974
|
+
}
|
|
1975
|
+
else if (calc === 'terminal_velocity') {
|
|
1976
|
+
const rho_fluid = params.density || params.fluid_density || FLUIDS.air.density;
|
|
1977
|
+
const rho_obj = params.object_density || 7800; // steel
|
|
1978
|
+
const mass = params.mass || 1;
|
|
1979
|
+
const A = params.area || 0.01;
|
|
1980
|
+
const Cd = params.Cd || params.cd || 0.47;
|
|
1981
|
+
const g = 9.81;
|
|
1982
|
+
const v_t = Math.sqrt(2 * mass * g / (rho_fluid * A * Cd));
|
|
1983
|
+
lines.push('### Terminal Velocity');
|
|
1984
|
+
lines.push('');
|
|
1985
|
+
lines.push(`| Parameter | Value |`);
|
|
1986
|
+
lines.push(`|---|---|`);
|
|
1987
|
+
lines.push(`| Mass | ${fmtUnit(mass, 'kg')} |`);
|
|
1988
|
+
lines.push(`| Fluid density | ${fmtUnit(rho_fluid, 'kg/m\u00b3')} |`);
|
|
1989
|
+
lines.push(`| Cross-section area | ${fmtUnit(A, 'm\u00b2')} |`);
|
|
1990
|
+
lines.push(`| Drag coefficient | ${fmt(Cd)} |`);
|
|
1991
|
+
lines.push(`| **Terminal velocity** | **${fmtUnit(v_t, 'm/s')}** (${fmtUnit(v_t * 3.6, 'km/h')}) |`);
|
|
1992
|
+
lines.push('');
|
|
1993
|
+
lines.push('**Formula**: v_t = \u221a(2mg / (\u03c1 A C_d))');
|
|
1994
|
+
// Check Reynolds number at terminal velocity
|
|
1995
|
+
const mu = params.viscosity || FLUIDS.air.viscosity;
|
|
1996
|
+
const D = params.diameter || Math.sqrt(4 * A / Math.PI);
|
|
1997
|
+
const Re = rho_fluid * v_t * D / mu;
|
|
1998
|
+
lines.push(`- Reynolds number at v_t: ${fmt(Re)}`);
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
return `**Error**: Unknown calculation "${calc}". Options: reynolds, bernoulli, pipe_flow, drag, terminal_velocity`;
|
|
2002
|
+
}
|
|
2003
|
+
return lines.join('\n');
|
|
2004
|
+
},
|
|
2005
|
+
});
|
|
2006
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2007
|
+
// 9. ELECTROMAGNETIC CALCULATOR
|
|
2008
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2009
|
+
registerTool({
|
|
2010
|
+
name: 'electromagnetic_calc',
|
|
2011
|
+
description: 'Electromagnetic calculations: Coulomb force, electric field, magnetic field (wire/loop/solenoid via Biot-Savart), inductance, capacitance, skin depth, antenna.',
|
|
2012
|
+
parameters: {
|
|
2013
|
+
calculation: { type: 'string', description: 'Calculation: coulomb, electric_field, magnetic_field, inductance, capacitance, skin_depth, antenna', required: true },
|
|
2014
|
+
params: { type: 'string', description: 'JSON params: {charge1, charge2, distance, current, radius, turns, length, area, plates, permittivity, frequency, conductivity, wire_radius}', required: true },
|
|
2015
|
+
},
|
|
2016
|
+
tier: 'free',
|
|
2017
|
+
async execute(args) {
|
|
2018
|
+
const calc = String(args.calculation).toLowerCase();
|
|
2019
|
+
let params;
|
|
2020
|
+
try {
|
|
2021
|
+
params = JSON.parse(String(args.params));
|
|
2022
|
+
}
|
|
2023
|
+
catch {
|
|
2024
|
+
return '**Error**: Invalid JSON in params.';
|
|
2025
|
+
}
|
|
2026
|
+
const lines = ['## Electromagnetic Calculations'];
|
|
2027
|
+
lines.push('');
|
|
2028
|
+
if (calc === 'coulomb') {
|
|
2029
|
+
const q1 = params.charge1 || params.q1 || e_charge;
|
|
2030
|
+
const q2 = params.charge2 || params.q2 || e_charge;
|
|
2031
|
+
const r = params.distance || params.r || 1e-10;
|
|
2032
|
+
const F = (1 / (4 * Math.PI * epsilon_0)) * q1 * q2 / (r * r);
|
|
2033
|
+
const U = (1 / (4 * Math.PI * epsilon_0)) * q1 * q2 / r;
|
|
2034
|
+
lines.push('### Coulomb Force');
|
|
2035
|
+
lines.push('');
|
|
2036
|
+
lines.push(`| Parameter | Value |`);
|
|
2037
|
+
lines.push(`|---|---|`);
|
|
2038
|
+
lines.push(`| Charge 1 | ${fmtUnit(q1, 'C')} (${fmtUnit(q1 / e_charge, 'e')}) |`);
|
|
2039
|
+
lines.push(`| Charge 2 | ${fmtUnit(q2, 'C')} (${fmtUnit(q2 / e_charge, 'e')}) |`);
|
|
2040
|
+
lines.push(`| Distance | ${fmtUnit(r, 'm')} |`);
|
|
2041
|
+
lines.push(`| **Force** | **${fmtUnit(F, 'N')}** |`);
|
|
2042
|
+
lines.push(`| Potential energy | ${fmtUnit(U, 'J')} (${fmtUnit(U / eV, 'eV')}) |`);
|
|
2043
|
+
lines.push(`| Direction | ${F > 0 ? 'Repulsive' : 'Attractive'} |`);
|
|
2044
|
+
lines.push('');
|
|
2045
|
+
lines.push('**Formula**: F = kq\u2081q\u2082/r\u00b2, k = 1/(4\u03c0\u03b5\u2080)');
|
|
2046
|
+
}
|
|
2047
|
+
else if (calc === 'electric_field') {
|
|
2048
|
+
const q = params.charge || params.q || e_charge;
|
|
2049
|
+
const r = params.distance || params.r || 1;
|
|
2050
|
+
const V = params.voltage || params.V;
|
|
2051
|
+
const d = params.plate_distance || params.d;
|
|
2052
|
+
if (V !== undefined && d !== undefined) {
|
|
2053
|
+
// Uniform field between parallel plates
|
|
2054
|
+
const E = V / d;
|
|
2055
|
+
lines.push('### Uniform Electric Field (Parallel Plates)');
|
|
2056
|
+
lines.push('');
|
|
2057
|
+
lines.push(`| Parameter | Value |`);
|
|
2058
|
+
lines.push(`|---|---|`);
|
|
2059
|
+
lines.push(`| Voltage | ${fmtUnit(V, 'V')} |`);
|
|
2060
|
+
lines.push(`| Plate separation | ${fmtUnit(d, 'm')} |`);
|
|
2061
|
+
lines.push(`| **Electric field** | **${fmtUnit(E, 'V/m')}** |`);
|
|
2062
|
+
}
|
|
2063
|
+
else {
|
|
2064
|
+
// Point charge
|
|
2065
|
+
const E = (1 / (4 * Math.PI * epsilon_0)) * Math.abs(q) / (r * r);
|
|
2066
|
+
const V_pot = (1 / (4 * Math.PI * epsilon_0)) * q / r;
|
|
2067
|
+
lines.push('### Electric Field (Point Charge)');
|
|
2068
|
+
lines.push('');
|
|
2069
|
+
lines.push(`| Parameter | Value |`);
|
|
2070
|
+
lines.push(`|---|---|`);
|
|
2071
|
+
lines.push(`| Charge | ${fmtUnit(q, 'C')} |`);
|
|
2072
|
+
lines.push(`| Distance | ${fmtUnit(r, 'm')} |`);
|
|
2073
|
+
lines.push(`| **Electric field** | **${fmtUnit(E, 'V/m')}** |`);
|
|
2074
|
+
lines.push(`| Electric potential | ${fmtUnit(V_pot, 'V')} |`);
|
|
2075
|
+
lines.push(`| Direction | ${q > 0 ? 'Radially outward' : 'Radially inward'} |`);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
else if (calc === 'magnetic_field') {
|
|
2079
|
+
const I = params.current || params.I || 1;
|
|
2080
|
+
const geometry = params.geometry || 0; // 0=wire, 1=loop, 2=solenoid
|
|
2081
|
+
const r = params.distance || params.r || 0.01;
|
|
2082
|
+
const R = params.radius || params.R || 0.05;
|
|
2083
|
+
const N = params.turns || params.N || 1;
|
|
2084
|
+
const L_sol = params.length || params.l || 0.1;
|
|
2085
|
+
lines.push('### Magnetic Field (Biot-Savart)');
|
|
2086
|
+
lines.push('');
|
|
2087
|
+
if (geometry === 0 || params.wire) {
|
|
2088
|
+
// Infinite straight wire: B = mu_0 * I / (2 * pi * r)
|
|
2089
|
+
const B = mu_0 * I / (2 * Math.PI * r);
|
|
2090
|
+
lines.push('**Geometry**: Infinite straight wire');
|
|
2091
|
+
lines.push('');
|
|
2092
|
+
lines.push(`| Parameter | Value |`);
|
|
2093
|
+
lines.push(`|---|---|`);
|
|
2094
|
+
lines.push(`| Current | ${fmtUnit(I, 'A')} |`);
|
|
2095
|
+
lines.push(`| Distance from wire | ${fmtUnit(r, 'm')} |`);
|
|
2096
|
+
lines.push(`| **Magnetic field B** | **${fmtUnit(B, 'T')}** (${fmtUnit(B * 1e6, '\u03bcT')}) |`);
|
|
2097
|
+
lines.push('');
|
|
2098
|
+
lines.push('**Formula**: B = \u03bc\u2080I / (2\u03c0r)');
|
|
2099
|
+
}
|
|
2100
|
+
if (geometry === 1 || params.loop) {
|
|
2101
|
+
// Circular loop at center: B = mu_0 * N * I / (2 * R)
|
|
2102
|
+
const B_center = mu_0 * N * I / (2 * R);
|
|
2103
|
+
// On axis at distance x: B = mu_0 * N * I * R^2 / (2 * (R^2 + x^2)^(3/2))
|
|
2104
|
+
const x = params.axial_distance || 0;
|
|
2105
|
+
const B_axis = mu_0 * N * I * R * R / (2 * Math.pow(R * R + x * x, 1.5));
|
|
2106
|
+
lines.push('**Geometry**: Circular loop');
|
|
2107
|
+
lines.push('');
|
|
2108
|
+
lines.push(`| Parameter | Value |`);
|
|
2109
|
+
lines.push(`|---|---|`);
|
|
2110
|
+
lines.push(`| Current | ${fmtUnit(I, 'A')} |`);
|
|
2111
|
+
lines.push(`| Radius | ${fmtUnit(R, 'm')} |`);
|
|
2112
|
+
lines.push(`| Turns | ${N} |`);
|
|
2113
|
+
lines.push(`| **B at center** | **${fmtUnit(B_center, 'T')}** |`);
|
|
2114
|
+
if (x > 0)
|
|
2115
|
+
lines.push(`| B at x=${fmtUnit(x, 'm')} on axis | ${fmtUnit(B_axis, 'T')} |`);
|
|
2116
|
+
lines.push(`| Magnetic moment | ${fmtUnit(N * I * Math.PI * R * R, 'A\u00b7m\u00b2')} |`);
|
|
2117
|
+
}
|
|
2118
|
+
if (geometry === 2 || params.solenoid) {
|
|
2119
|
+
// Solenoid: B = mu_0 * n * I where n = N/L
|
|
2120
|
+
const n = N / L_sol;
|
|
2121
|
+
const B = mu_0 * n * I;
|
|
2122
|
+
lines.push('**Geometry**: Solenoid');
|
|
2123
|
+
lines.push('');
|
|
2124
|
+
lines.push(`| Parameter | Value |`);
|
|
2125
|
+
lines.push(`|---|---|`);
|
|
2126
|
+
lines.push(`| Current | ${fmtUnit(I, 'A')} |`);
|
|
2127
|
+
lines.push(`| Turns | ${N} |`);
|
|
2128
|
+
lines.push(`| Length | ${fmtUnit(L_sol, 'm')} |`);
|
|
2129
|
+
lines.push(`| Turn density (n) | ${fmtUnit(n, 'turns/m')} |`);
|
|
2130
|
+
lines.push(`| **B (interior)** | **${fmtUnit(B, 'T')}** (${fmtUnit(B * 1000, 'mT')}) |`);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
else if (calc === 'inductance') {
|
|
2134
|
+
const N = params.turns || params.N || 100;
|
|
2135
|
+
const A = params.area || (params.radius ? Math.PI * params.radius ** 2 : 1e-4);
|
|
2136
|
+
const l = params.length || 0.1;
|
|
2137
|
+
const mu_r = params.permeability || 1;
|
|
2138
|
+
// Solenoid inductance: L = mu_0 * mu_r * N^2 * A / l
|
|
2139
|
+
const L_ind = mu_0 * mu_r * N * N * A / l;
|
|
2140
|
+
// Energy stored: U = 0.5 * L * I^2
|
|
2141
|
+
const I = params.current || 1;
|
|
2142
|
+
const U = 0.5 * L_ind * I * I;
|
|
2143
|
+
lines.push('### Inductance (Solenoid)');
|
|
2144
|
+
lines.push('');
|
|
2145
|
+
lines.push(`| Parameter | Value |`);
|
|
2146
|
+
lines.push(`|---|---|`);
|
|
2147
|
+
lines.push(`| Turns (N) | ${N} |`);
|
|
2148
|
+
lines.push(`| Cross-section area | ${fmtUnit(A, 'm\u00b2')} |`);
|
|
2149
|
+
lines.push(`| Length | ${fmtUnit(l, 'm')} |`);
|
|
2150
|
+
lines.push(`| Relative permeability | ${mu_r} |`);
|
|
2151
|
+
lines.push(`| **Inductance** | **${fmtUnit(L_ind, 'H')}** (${fmtUnit(L_ind * 1e3, 'mH')}) |`);
|
|
2152
|
+
lines.push(`| Energy (at ${fmtUnit(I, 'A')}) | ${fmtUnit(U, 'J')} |`);
|
|
2153
|
+
lines.push(`| Inductive reactance @ 50Hz | ${fmtUnit(2 * Math.PI * 50 * L_ind, '\u03a9')} |`);
|
|
2154
|
+
lines.push(`| Inductive reactance @ 1kHz | ${fmtUnit(2 * Math.PI * 1000 * L_ind, '\u03a9')} |`);
|
|
2155
|
+
}
|
|
2156
|
+
else if (calc === 'capacitance') {
|
|
2157
|
+
const A = params.area || 0.01;
|
|
2158
|
+
const d = params.distance || params.separation || 0.001;
|
|
2159
|
+
const epsilon_r = params.permittivity || params.dielectric || 1;
|
|
2160
|
+
// Parallel plate: C = epsilon_0 * epsilon_r * A / d
|
|
2161
|
+
const C_cap = epsilon_0 * epsilon_r * A / d;
|
|
2162
|
+
const V = params.voltage || 1;
|
|
2163
|
+
const Q = C_cap * V;
|
|
2164
|
+
const U = 0.5 * C_cap * V * V;
|
|
2165
|
+
lines.push('### Capacitance (Parallel Plate)');
|
|
2166
|
+
lines.push('');
|
|
2167
|
+
lines.push(`| Parameter | Value |`);
|
|
2168
|
+
lines.push(`|---|---|`);
|
|
2169
|
+
lines.push(`| Plate area | ${fmtUnit(A, 'm\u00b2')} |`);
|
|
2170
|
+
lines.push(`| Separation | ${fmtUnit(d, 'm')} (${fmtUnit(d * 1000, 'mm')}) |`);
|
|
2171
|
+
lines.push(`| Dielectric constant | ${epsilon_r} |`);
|
|
2172
|
+
lines.push(`| **Capacitance** | **${fmtUnit(C_cap, 'F')}** (${fmtUnit(C_cap * 1e12, 'pF')}) |`);
|
|
2173
|
+
lines.push(`| Charge (at ${fmtUnit(V, 'V')}) | ${fmtUnit(Q, 'C')} |`);
|
|
2174
|
+
lines.push(`| Energy (at ${fmtUnit(V, 'V')}) | ${fmtUnit(U, 'J')} |`);
|
|
2175
|
+
lines.push(`| E field | ${fmtUnit(V / d, 'V/m')} |`);
|
|
2176
|
+
lines.push(`| Capacitive reactance @ 50Hz | ${fmtUnit(1 / (2 * Math.PI * 50 * C_cap), '\u03a9')} |`);
|
|
2177
|
+
}
|
|
2178
|
+
else if (calc === 'skin_depth') {
|
|
2179
|
+
const f = params.frequency || 60;
|
|
2180
|
+
const sigma = params.conductivity || 5.8e7; // copper
|
|
2181
|
+
const mu_r = params.permeability || 1;
|
|
2182
|
+
const delta = 1 / Math.sqrt(Math.PI * f * mu_0 * mu_r * sigma);
|
|
2183
|
+
lines.push('### Skin Depth');
|
|
2184
|
+
lines.push('');
|
|
2185
|
+
lines.push(`| Parameter | Value |`);
|
|
2186
|
+
lines.push(`|---|---|`);
|
|
2187
|
+
lines.push(`| Frequency | ${fmtUnit(f, 'Hz')} |`);
|
|
2188
|
+
lines.push(`| Conductivity | ${fmtUnit(sigma, 'S/m')} |`);
|
|
2189
|
+
lines.push(`| Relative permeability | ${mu_r} |`);
|
|
2190
|
+
lines.push(`| **Skin depth (\u03b4)** | **${fmtUnit(delta, 'm')}** (${fmtUnit(delta * 1000, 'mm')}) |`);
|
|
2191
|
+
lines.push('');
|
|
2192
|
+
lines.push('Common conductivities: copper (5.8\u00d710\u2077), aluminum (3.77\u00d710\u2077), steel (1\u00d710\u2077), seawater (5)');
|
|
2193
|
+
lines.push('');
|
|
2194
|
+
// Show skin depth at common frequencies
|
|
2195
|
+
lines.push('| Frequency | Skin depth (copper) |');
|
|
2196
|
+
lines.push('|---|---|');
|
|
2197
|
+
for (const fq of [60, 1000, 1e6, 100e6, 1e9, 10e9]) {
|
|
2198
|
+
const d = 1 / Math.sqrt(Math.PI * fq * mu_0 * sigma);
|
|
2199
|
+
const fLabel = fq >= 1e9 ? `${fq / 1e9} GHz` : fq >= 1e6 ? `${fq / 1e6} MHz` : fq >= 1e3 ? `${fq / 1e3} kHz` : `${fq} Hz`;
|
|
2200
|
+
lines.push(`| ${fLabel} | ${fmtUnit(d * 1000, 'mm')} |`);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
else if (calc === 'antenna') {
|
|
2204
|
+
const f = params.frequency || 2.4e9;
|
|
2205
|
+
const lambda = c / f;
|
|
2206
|
+
const gain_dBi = params.gain || 2.15; // dipole
|
|
2207
|
+
lines.push('### Antenna Calculations');
|
|
2208
|
+
lines.push('');
|
|
2209
|
+
lines.push(`| Parameter | Value |`);
|
|
2210
|
+
lines.push(`|---|---|`);
|
|
2211
|
+
lines.push(`| Frequency | ${fmtUnit(f, 'Hz')} (${f >= 1e9 ? fmtUnit(f / 1e9, 'GHz') : fmtUnit(f / 1e6, 'MHz')}) |`);
|
|
2212
|
+
lines.push(`| Wavelength (\u03bb) | ${fmtUnit(lambda, 'm')} (${fmtUnit(lambda * 100, 'cm')}) |`);
|
|
2213
|
+
lines.push(`| Half-wave dipole length | ${fmtUnit(lambda / 2, 'm')} (${fmtUnit(lambda * 50, 'cm')}) |`);
|
|
2214
|
+
lines.push(`| Quarter-wave monopole | ${fmtUnit(lambda / 4, 'm')} (${fmtUnit(lambda * 25, 'cm')}) |`);
|
|
2215
|
+
lines.push(`| Gain | ${fmt(gain_dBi)} dBi |`);
|
|
2216
|
+
lines.push('');
|
|
2217
|
+
// Friis transmission equation
|
|
2218
|
+
const dist = params.distance || 100;
|
|
2219
|
+
const P_tx = params.power || 1; // 1W
|
|
2220
|
+
const G_tx = Math.pow(10, gain_dBi / 10);
|
|
2221
|
+
const G_rx = G_tx;
|
|
2222
|
+
const P_rx = P_tx * G_tx * G_rx * (lambda / (4 * Math.PI * dist)) ** 2;
|
|
2223
|
+
const pathLoss_dB = 20 * Math.log10(4 * Math.PI * dist / lambda);
|
|
2224
|
+
lines.push('### Link Budget (Friis)');
|
|
2225
|
+
lines.push('');
|
|
2226
|
+
lines.push(`| Parameter | Value |`);
|
|
2227
|
+
lines.push(`|---|---|`);
|
|
2228
|
+
lines.push(`| Tx power | ${fmtUnit(P_tx, 'W')} (${fmtUnit(10 * Math.log10(P_tx * 1000), 'dBm')}) |`);
|
|
2229
|
+
lines.push(`| Distance | ${fmtUnit(dist, 'm')} |`);
|
|
2230
|
+
lines.push(`| Free-space path loss | ${fmtUnit(pathLoss_dB, 'dB')} |`);
|
|
2231
|
+
lines.push(`| Rx power | ${fmtUnit(P_rx, 'W')} (${fmtUnit(10 * Math.log10(P_rx * 1000), 'dBm')}) |`);
|
|
2232
|
+
// Effective aperture
|
|
2233
|
+
const Ae = G_tx * lambda * lambda / (4 * Math.PI);
|
|
2234
|
+
lines.push(`| Effective aperture | ${fmtUnit(Ae, 'm\u00b2')} |`);
|
|
2235
|
+
}
|
|
2236
|
+
else {
|
|
2237
|
+
return `**Error**: Unknown calculation "${calc}". Options: coulomb, electric_field, magnetic_field, inductance, capacitance, skin_depth, antenna`;
|
|
2238
|
+
}
|
|
2239
|
+
return lines.join('\n');
|
|
2240
|
+
},
|
|
2241
|
+
});
|
|
2242
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2243
|
+
// 10. ASTRONOMY QUERY
|
|
2244
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2245
|
+
registerTool({
|
|
2246
|
+
name: 'astronomy_query',
|
|
2247
|
+
description: 'Query astronomical databases: SIMBAD for star/galaxy data, NASA Exoplanet Archive for exoplanets, NASA NEO for near-Earth objects.',
|
|
2248
|
+
parameters: {
|
|
2249
|
+
object: { type: 'string', description: 'Astronomical object name (e.g. "Sirius", "Proxima Centauri b", "Andromeda")', required: true },
|
|
2250
|
+
catalog: { type: 'string', description: 'Catalog to query: simbad, exoplanet, neo', required: true },
|
|
2251
|
+
},
|
|
2252
|
+
tier: 'free',
|
|
2253
|
+
timeout: 30_000,
|
|
2254
|
+
async execute(args) {
|
|
2255
|
+
const object = String(args.object).trim();
|
|
2256
|
+
const catalog = String(args.catalog).toLowerCase();
|
|
2257
|
+
const lines = [];
|
|
2258
|
+
if (catalog === 'simbad') {
|
|
2259
|
+
const script = encodeURIComponent(`output console=off script=off\nformat object "%IDLIST(1) | %OTYPELIST | %COO(A D) | %FLUXLIST(V) | %SP"\nquery id ${object}`);
|
|
2260
|
+
const url = `https://simbad.cds.unistra.fr/simbad/sim-script?script=${script}`;
|
|
2261
|
+
try {
|
|
2262
|
+
const res = await fetch(url, {
|
|
2263
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
2264
|
+
signal: AbortSignal.timeout(15_000),
|
|
2265
|
+
});
|
|
2266
|
+
const text = await res.text();
|
|
2267
|
+
lines.push(`## SIMBAD: ${object}`);
|
|
2268
|
+
lines.push('');
|
|
2269
|
+
if (text.includes('error') || text.includes('not found') || text.includes('No known object')) {
|
|
2270
|
+
lines.push(`Object "${object}" not found in SIMBAD.`);
|
|
2271
|
+
lines.push('');
|
|
2272
|
+
lines.push('Try a different name or identifier (e.g., "HD 48915" for Sirius, "M31" for Andromeda).');
|
|
2273
|
+
}
|
|
2274
|
+
else {
|
|
2275
|
+
// Parse the SIMBAD response
|
|
2276
|
+
const dataLines = text.split('\n').filter(l => l.trim() && !l.startsWith('::') && !l.startsWith('C.D.S.'));
|
|
2277
|
+
if (dataLines.length > 0) {
|
|
2278
|
+
const parts = dataLines[0].split('|').map(s => s.trim());
|
|
2279
|
+
lines.push(`| Field | Value |`);
|
|
2280
|
+
lines.push(`|---|---|`);
|
|
2281
|
+
if (parts[0])
|
|
2282
|
+
lines.push(`| Identifier | ${parts[0]} |`);
|
|
2283
|
+
if (parts[1])
|
|
2284
|
+
lines.push(`| Object type | ${parts[1]} |`);
|
|
2285
|
+
if (parts[2])
|
|
2286
|
+
lines.push(`| Coordinates (RA Dec) | ${parts[2]} |`);
|
|
2287
|
+
if (parts[3])
|
|
2288
|
+
lines.push(`| V magnitude | ${parts[3]} |`);
|
|
2289
|
+
if (parts[4])
|
|
2290
|
+
lines.push(`| Spectral type | ${parts[4]} |`);
|
|
2291
|
+
}
|
|
2292
|
+
// Include raw for debugging
|
|
2293
|
+
if (dataLines.length > 1) {
|
|
2294
|
+
lines.push('');
|
|
2295
|
+
lines.push('### Raw Data');
|
|
2296
|
+
lines.push('```');
|
|
2297
|
+
lines.push(dataLines.slice(0, 10).join('\n'));
|
|
2298
|
+
lines.push('```');
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
catch (err) {
|
|
2303
|
+
lines.push(`## SIMBAD Query Failed`);
|
|
2304
|
+
lines.push('');
|
|
2305
|
+
lines.push(`Could not reach SIMBAD for "${object}". Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2306
|
+
lines.push('');
|
|
2307
|
+
lines.push('SIMBAD URL: `https://simbad.cds.unistra.fr/simbad/`');
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
else if (catalog === 'exoplanet') {
|
|
2311
|
+
const encoded = encodeURIComponent(object);
|
|
2312
|
+
const url = `https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+*+from+pscomppars+where+pl_name+like+'%25${encoded}%25'&format=json`;
|
|
2313
|
+
try {
|
|
2314
|
+
const res = await fetch(url, {
|
|
2315
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
2316
|
+
signal: AbortSignal.timeout(15_000),
|
|
2317
|
+
});
|
|
2318
|
+
const data = await res.json();
|
|
2319
|
+
lines.push(`## NASA Exoplanet Archive: ${object}`);
|
|
2320
|
+
lines.push('');
|
|
2321
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
2322
|
+
lines.push(`No exoplanet found matching "${object}".`);
|
|
2323
|
+
lines.push('');
|
|
2324
|
+
lines.push('Try the full planet name (e.g., "Proxima Centauri b", "TRAPPIST-1 e", "Kepler-452 b").');
|
|
2325
|
+
}
|
|
2326
|
+
else {
|
|
2327
|
+
for (const planet of data.slice(0, 3)) {
|
|
2328
|
+
lines.push(`### ${planet.pl_name || object}`);
|
|
2329
|
+
lines.push('');
|
|
2330
|
+
lines.push(`| Property | Value |`);
|
|
2331
|
+
lines.push(`|---|---|`);
|
|
2332
|
+
if (planet.hostname)
|
|
2333
|
+
lines.push(`| Host star | ${planet.hostname} |`);
|
|
2334
|
+
if (planet.sy_dist)
|
|
2335
|
+
lines.push(`| Distance | ${planet.sy_dist} pc |`);
|
|
2336
|
+
if (planet.pl_orbper)
|
|
2337
|
+
lines.push(`| Orbital period | ${planet.pl_orbper} days |`);
|
|
2338
|
+
if (planet.pl_orbsmax)
|
|
2339
|
+
lines.push(`| Semi-major axis | ${planet.pl_orbsmax} AU |`);
|
|
2340
|
+
if (planet.pl_orbeccen !== undefined)
|
|
2341
|
+
lines.push(`| Eccentricity | ${planet.pl_orbeccen} |`);
|
|
2342
|
+
if (planet.pl_orbincl)
|
|
2343
|
+
lines.push(`| Inclination | ${planet.pl_orbincl}\u00b0 |`);
|
|
2344
|
+
if (planet.pl_bmassj)
|
|
2345
|
+
lines.push(`| Mass | ${planet.pl_bmassj} M_Jupiter |`);
|
|
2346
|
+
if (planet.pl_radj)
|
|
2347
|
+
lines.push(`| Radius | ${planet.pl_radj} R_Jupiter |`);
|
|
2348
|
+
if (planet.pl_eqt)
|
|
2349
|
+
lines.push(`| Eq. temperature | ${planet.pl_eqt} K |`);
|
|
2350
|
+
if (planet.pl_dens)
|
|
2351
|
+
lines.push(`| Density | ${planet.pl_dens} g/cm\u00b3 |`);
|
|
2352
|
+
if (planet.disc_year)
|
|
2353
|
+
lines.push(`| Discovery year | ${planet.disc_year} |`);
|
|
2354
|
+
if (planet.discoverymethod)
|
|
2355
|
+
lines.push(`| Discovery method | ${planet.discoverymethod} |`);
|
|
2356
|
+
if (planet.st_spectype)
|
|
2357
|
+
lines.push(`| Star spectral type | ${planet.st_spectype} |`);
|
|
2358
|
+
if (planet.st_teff)
|
|
2359
|
+
lines.push(`| Star temperature | ${planet.st_teff} K |`);
|
|
2360
|
+
if (planet.st_mass)
|
|
2361
|
+
lines.push(`| Star mass | ${planet.st_mass} M_Sun |`);
|
|
2362
|
+
if (planet.st_rad)
|
|
2363
|
+
lines.push(`| Star radius | ${planet.st_rad} R_Sun |`);
|
|
2364
|
+
lines.push('');
|
|
2365
|
+
}
|
|
2366
|
+
if (data.length > 3) {
|
|
2367
|
+
lines.push(`*... and ${data.length - 3} more results.*`);
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
catch (err) {
|
|
2372
|
+
lines.push(`## Exoplanet Query Failed`);
|
|
2373
|
+
lines.push('');
|
|
2374
|
+
lines.push(`Could not reach NASA Exoplanet Archive. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
else if (catalog === 'neo') {
|
|
2378
|
+
// NEO browse or search
|
|
2379
|
+
const url = `https://api.nasa.gov/neo/rest/v1/neo/browse?api_key=DEMO_KEY`;
|
|
2380
|
+
try {
|
|
2381
|
+
const res = await fetch(url, {
|
|
2382
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
2383
|
+
signal: AbortSignal.timeout(15_000),
|
|
2384
|
+
});
|
|
2385
|
+
const data = await res.json();
|
|
2386
|
+
lines.push(`## NASA Near-Earth Objects`);
|
|
2387
|
+
lines.push('');
|
|
2388
|
+
const neos = data.near_earth_objects || [];
|
|
2389
|
+
// Filter by name if provided
|
|
2390
|
+
const filtered = object.toLowerCase() === 'browse' || object.toLowerCase() === 'all'
|
|
2391
|
+
? neos.slice(0, 20)
|
|
2392
|
+
: neos.filter(n => String(n.name || '').toLowerCase().includes(object.toLowerCase()));
|
|
2393
|
+
if (filtered.length === 0 && neos.length > 0) {
|
|
2394
|
+
// Show first 15 regardless
|
|
2395
|
+
lines.push(`No NEO matching "${object}". Showing recent entries:`);
|
|
2396
|
+
lines.push('');
|
|
2397
|
+
filtered.push(...neos.slice(0, 15));
|
|
2398
|
+
}
|
|
2399
|
+
lines.push(`| Name | ID | Hazardous | Est. Diameter (m) | Absolute Mag |`);
|
|
2400
|
+
lines.push(`|---|---|---|---|---|`);
|
|
2401
|
+
for (const neo of filtered.slice(0, 20)) {
|
|
2402
|
+
const ed = neo.estimated_diameter;
|
|
2403
|
+
const diamMin = ed?.meters?.estimated_diameter_min?.toFixed(1) || '?';
|
|
2404
|
+
const diamMax = ed?.meters?.estimated_diameter_max?.toFixed(1) || '?';
|
|
2405
|
+
lines.push(`| ${neo.name || '?'} | ${neo.id || '?'} | ${neo.is_potentially_hazardous_asteroid ? 'YES' : 'No'} | ${diamMin}\u2013${diamMax} | ${neo.absolute_magnitude_h || '?'} |`);
|
|
2406
|
+
}
|
|
2407
|
+
lines.push('');
|
|
2408
|
+
lines.push(`Total NEOs in database: ${data.page?.total_elements || '?'}`);
|
|
2409
|
+
}
|
|
2410
|
+
catch (err) {
|
|
2411
|
+
lines.push(`## NEO Query Failed`);
|
|
2412
|
+
lines.push('');
|
|
2413
|
+
lines.push(`Could not reach NASA NEO API. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
else {
|
|
2417
|
+
return `**Error**: Unknown catalog "${catalog}". Options: simbad, exoplanet, neo`;
|
|
2418
|
+
}
|
|
2419
|
+
return lines.join('\n');
|
|
2420
|
+
},
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2423
|
+
//# sourceMappingURL=lab-physics.js.map
|