295 lines
10 KiB
HTML
295 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Thermal Diffusion Solver | Cathy Mcmasters</title>
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:title" content="Thermal Diffusion Solver | Cathy Mcmasters">
|
||
<meta property="og:description" content="Calculating safe burial depth for root cellars in Hampton clay-loam (Q16019)">
|
||
<meta property="og:image" content="https://images.pexels.com/photos/11924683/pexels-photo-11924683.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940">
|
||
<meta property="og:url" content="https://cathy-mcmasters.4ort.net/thermal-solver.html">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="description" content="Calculating safe burial depth for root cellars in Hampton clay-loam (Q16019)">
|
||
<style>
|
||
:root {
|
||
--bg: #0f0f0f;
|
||
--surface: #1a1a1a;
|
||
--primary: #d4af37; /* Hampton Gold */
|
||
--text: #e0e0e0;
|
||
--muted: #808080;
|
||
--border: #333333;
|
||
}
|
||
body {
|
||
font-family: 'Georgia', serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
margin: 0;
|
||
padding: 2rem;
|
||
line-height: 1.6;
|
||
}
|
||
header {
|
||
border-bottom: 1px solid var(--primary);
|
||
padding-bottom: 1rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
h1 {
|
||
font-size: 2rem;
|
||
color: var(--primary);
|
||
letter-spacing: 0.05em;
|
||
margin: 0;
|
||
}
|
||
p.subtitle {
|
||
color: var(--muted);
|
||
font-style: italic;
|
||
margin-top: 0.5rem;
|
||
}
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 2rem;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
.panel {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
padding: 1.5rem;
|
||
border-radius: 4px;
|
||
}
|
||
label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
color: var(--primary);
|
||
font-weight: bold;
|
||
font-size: 0.9rem;
|
||
}
|
||
input[type="number"] {
|
||
width: 100%;
|
||
padding: 0.5rem;
|
||
background: #000;
|
||
border: 1px solid var(--border);
|
||
color: var(--text);
|
||
font-family: monospace;
|
||
margin-bottom: 1rem;
|
||
box-sizing: border-box;
|
||
}
|
||
button {
|
||
background: var(--primary);
|
||
color: #000;
|
||
border: none;
|
||
padding: 0.75rem 1.5rem;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
width: 100%;
|
||
}
|
||
button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(212, 175, 55, 0.2);
|
||
}
|
||
.result {
|
||
margin-top: 1rem;
|
||
padding: 1rem;
|
||
background: #000;
|
||
border-left: 3px solid var(--primary);
|
||
font-family: monospace;
|
||
white-space: pre-wrap;
|
||
}
|
||
canvas {
|
||
background: #000;
|
||
border: 1px solid var(--border);
|
||
width: 100%;
|
||
height: 300px;
|
||
margin-top: 1rem;
|
||
}
|
||
.citation {
|
||
font-size: 0.75rem;
|
||
color: var(--muted);
|
||
margin-top: 1rem;
|
||
border-top: 1px dashed var(--border);
|
||
padding-top: 0.5rem;
|
||
}
|
||
.strata-img {
|
||
width: 100%;
|
||
height: 200px;
|
||
object-fit: cover;
|
||
margin-top: 1rem;
|
||
border: 1px solid var(--primary);
|
||
opacity: 0.8;
|
||
}
|
||
@media (max-width: 768px) {
|
||
.grid { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<h1>Thermal Diffusion Solver</h1>
|
||
<p class="subtitle">Calculating safe burial depth for root cellars in Hampton clay-loam (Q16019)</p>
|
||
</header>
|
||
|
||
<div class="grid">
|
||
<section class="panel">
|
||
<h3>Input Parameters</h3>
|
||
<form id="solver-form">
|
||
<label for="deltaT">Surface Temperature Swing (ΔT)</label>
|
||
<input type="number" id="deltaT" step="0.1" value="28.0" title="Annual difference between summer peak and winter trough">
|
||
|
||
<label for="conductivity">Thermal Conductivity (k)</label>
|
||
<input type="number" id="conductivity" step="0.01" value="1.40" title="Watts per meter-Kelvin">
|
||
|
||
<label for="density">Soil Density (ρ)</label>
|
||
<input type="number" id="density" step="10" value="1600" title="Kilograms per cubic meter">
|
||
|
||
<label for="specificHeat">Specific Heat Capacity (c)</label>
|
||
<input type="number" id="specificHeat" step="100" value="2000" title="Joules per kilogram-Kelvin">
|
||
|
||
<label for="threshold">Safety Threshold (°C)</label>
|
||
<input type="number" id="threshold" step="0.1" value="2.0" title="Maximum allowable temp fluctuation at storage depth">
|
||
|
||
<button type="submit">Compute Burial Profile</button>
|
||
</form>
|
||
|
||
<div class="citation">
|
||
Constants derived from:<br>
|
||
• Soil thermal properties: ASTM C518<br>
|
||
• Hampton climate normals: NOAA NCEI (1991-2020)<br>
|
||
• Diffusion equation: Fourier (1822)
|
||
</div>
|
||
|
||
<img class="strata-img" src="https://images.pexels.com/photos/11924683/pexels-photo-11924683.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Vertical cross-section of soil strata with exposed root systems, illustrating the interface between biological and geological layers">
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<h3>Computation Results</h3>
|
||
<div id="output" class="result">Awaiting computation...</div>
|
||
<canvas id="gradientChart"></canvas>
|
||
</section>
|
||
</div>
|
||
|
||
<script>
|
||
/**
|
||
* Solves the 1D Heat Equation for periodic boundary conditions.
|
||
* Amplitude decay follows: A(z) = A_0 * exp(-z * sqrt(πω / k))
|
||
* where ω = angular frequency, k = thermal diffusivity (α)
|
||
*/
|
||
document.getElementById('solver-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const deltaT = parseFloat(document.getElementById('deltaT').value);
|
||
const k_cond = parseFloat(document.getElementById('conductivity').value); // W/(m·K)
|
||
const rho = parseFloat(document.getElementById('density').value); // kg/m³
|
||
const c_spec = parseFloat(document.getElementById('specificHeat').value); // J/(kg·K)
|
||
const threshold = parseFloat(document.getElementById('threshold').value); // °C
|
||
|
||
// Calculate thermal diffusivity (alpha)
|
||
const alpha = k_cond / (rho * c_spec); // m²/s
|
||
|
||
// Angular frequency for annual cycle (seconds/year ≈ 31,557,600)
|
||
const omega = (2 * Math.PI) / 31557600;
|
||
|
||
// Decay constant
|
||
const decayConst = Math.sqrt(Math.PI * omega / alpha);
|
||
|
||
// Solve for depth where amplitude drops to threshold
|
||
// threshold = (deltaT/2) * exp(-depth * decayConst)
|
||
const initialAmplitude = deltaT / 2;
|
||
const targetRatio = threshold / initialAmplitude;
|
||
|
||
let requiredDepth = 0;
|
||
if (targetRatio > 0 && targetRatio < 1) {
|
||
requiredDepth = -Math.log(targetRatio) / decayConst;
|
||
}
|
||
|
||
// Generate profile points
|
||
const points = [];
|
||
for (let z = 0; z <= requiredDepth * 1.5; z += 0.1) {
|
||
const ampAtZ = initialAmplitude * Math.exp(-z * decayConst);
|
||
points.push({ depth: z, amplitude: ampAtZ });
|
||
}
|
||
|
||
// Render results
|
||
const outputDiv = document.getElementById('output');
|
||
outputDiv.innerHTML = `
|
||
<span style="color:#d4af37">MINIMUM BURIAL DEPTH:</span> ${requiredDepth.toFixed(2)} meters (${(requiredDepth * 3.28084).toFixed(2)} ft)
|
||
|
||
<span style="color:#d4af37">THERMAL DIFFUSIVITY (α):</span> ${(alpha * 1e6).toFixed(2)} mm²/s
|
||
|
||
<span style="color:#d4af37">DECAY CONSTANT:</span> ${decayConst.toFixed(4)} m⁻¹
|
||
|
||
<span style="color:#d4af37">SAFETY FACTOR:</span> ${((initialAmplitude / threshold)).toFixed(2)}x
|
||
`;
|
||
|
||
// Draw Chart
|
||
drawGradient(points, requiredDepth, threshold);
|
||
});
|
||
|
||
function drawGradient(data, criticalDepth, threshold) {
|
||
const canvas = document.getElementById('gradientChart');
|
||
const ctx = canvas.getContext('2d');
|
||
const w = canvas.width = canvas.offsetWidth;
|
||
const h = canvas.height = 300;
|
||
|
||
ctx.fillStyle = '#000';
|
||
ctx.fillRect(0, 0, w, h);
|
||
|
||
// Axes
|
||
ctx.strokeStyle = '#333';
|
||
ctx.beginPath();
|
||
ctx.moveTo(40, 20); ctx.lineTo(40, h-40); // Y axis
|
||
ctx.lineTo(w-20, h-40); // X axis
|
||
ctx.stroke();
|
||
|
||
// Labels
|
||
ctx.fillStyle = '#808080';
|
||
ctx.font = '10px monospace';
|
||
ctx.fillText('DEPTH (m)', 10, h-20);
|
||
ctx.fillText('AMPLITUDE (°C)', 10, 20);
|
||
|
||
// Plot curve
|
||
ctx.strokeStyle = '#d4af37';
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
|
||
const xMax = data[data.length-1].depth;
|
||
const yMax = data[0].amplitude;
|
||
|
||
data.forEach(p => {
|
||
const x = 40 + (p.depth / xMax) * (w - 60);
|
||
const y = h - 40 - (p.amplitude / yMax) * (h - 80);
|
||
if (p.depth === 0) ctx.moveTo(x, y);
|
||
else ctx.lineTo(x, y);
|
||
});
|
||
ctx.stroke();
|
||
|
||
// Critical line
|
||
ctx.strokeStyle = '#ff4444';
|
||
ctx.setLineDash([5, 5]);
|
||
ctx.beginPath();
|
||
const critY = h - 40 - (threshold / yMax) * (h - 80);
|
||
ctx.moveTo(40, critY); ctx.lineTo(w-20, critY);
|
||
ctx.stroke();
|
||
ctx.setLineDash([]);
|
||
|
||
// Intersection marker
|
||
const intersectIndex = data.findIndex(p => p.amplitude <= threshold);
|
||
if (intersectIndex !== -1) {
|
||
const p = data[intersectIndex];
|
||
const ix = 40 + (p.depth / xMax) * (w - 60);
|
||
const iy = h - 40 - (p.amplitude / yMax) * (h - 80);
|
||
|
||
ctx.fillStyle = '#d4af37';
|
||
ctx.beginPath();
|
||
ctx.arc(ix, iy, 4, 0, Math.PI*2);
|
||
ctx.fill();
|
||
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(`${p.depth.toFixed(1)}m`, ix + 10, iy + 15);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|