viewof params = Inputs.form({
T: Inputs.range([0., 4], {label: "Temp.", value: 1.0, step: 0.1,}),
g: Inputs.range([-2, 2], {label: "Tilt", value: 0, step: 0.1}),
reset: Inputs.button("Reset simulation")
})
// Simulation parameters
dt = 0.005
kB = 1
gamma = 1
// Double well potential: V(x) = x^4 - 2x^2 + g*x
V = (x, g) => x**4 - 2*x**2 + g*x
force = (x, g) => -(4*x**3 - 4*x + g)
// Live simulation with temperature control
simulationState = {
let x = 0
let positions = [0]
let step = 0
while (true) {
if (params.reset) {
x = 0
positions = [0]
step = 0
}
let f = force(x, params.g)
let noise = Math.sqrt(2 * kB * params.T * dt / gamma) * (Math.random() - 0.5) * 2
x += (f / gamma) * dt + noise
positions.push(x)
// Keep only last 500 points for performance
if (positions.length > 500) {
positions = positions.slice(-500)
}
yield {x, positions: [...positions], step: step++}
await new Promise(resolve => setTimeout(resolve, 10))
}
}
// Plot
Plot.plot({
width: 700,
height: 400,
grid: true,
x: {domain: [-2.5, 2.5], label: "Position"},
y: {domain: [-2, 3], label: "Potential Energy"},
marks: [
// Potential curve
Plot.line(
Array.from({length: 200}, (_, i) => {
let x = -2.5 + 5 * i / 199
return {x, y: V(x, params.g)}
}),
{x: "x", y: "y", stroke: "red", strokeWidth: 2}
),
// Particle trajectory (trail)
Plot.line(
simulationState.positions.map((x, i) => ({x, y: V(x, params.g), step: i})),
{x: "x", y: "y", stroke: "blue", strokeWidth: 1, opacity: 0.5}
),
// Current particle position
Plot.dot([{
x: simulationState.x,
y: V(simulationState.x, params.g)
}], {x: "x", y: "y", fill: "blue", r: 6, stroke: "white", strokeWidth: 2})
]
})