|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8" /> |
|
<title>Gradient-Free BOED via Interacting Particle Systems</title> |
|
<style> |
|
body { margin: 0; overflow: hidden; } |
|
#info { |
|
position: absolute; |
|
top: 10px; left: 10px; |
|
color: #fff; |
|
font-family: sans-serif; |
|
background: rgba(0,0,0,0.4); |
|
padding: 8px 12px; |
|
border-radius: 4px; |
|
font-size: 14px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="info"> |
|
<strong>Particles:</strong> Ensemble Kalman Inversion (attraction to mean)<br/> |
|
<strong>Noise:</strong> Affine-Invariant Langevin Dynamics |
|
</div> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> |
|
<script> |
|
|
|
let scene = new THREE.Scene(); |
|
let camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
camera.position.set(0, 0, 50); |
|
|
|
let renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
document.body.appendChild(renderer.domElement); |
|
|
|
let controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
|
|
window.addEventListener('resize', () => { |
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
}); |
|
|
|
|
|
const PARTICLE_COUNT = 100; |
|
const particles = []; |
|
const geometry = new THREE.SphereGeometry(0.5, 8, 8); |
|
|
|
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) { |
|
let material = new THREE.MeshBasicMaterial({ |
|
color: new THREE.Color(Math.random(), Math.random(), Math.random()) |
|
}); |
|
let p = new THREE.Mesh(geometry, material); |
|
p.position.set( |
|
(Math.random() - 0.5) * 40, |
|
(Math.random() - 0.5) * 40, |
|
(Math.random() - 0.5) * 40 |
|
); |
|
particles.push(p); |
|
scene.add(p); |
|
} |
|
|
|
|
|
function updateParticles() { |
|
|
|
let mean = new THREE.Vector3(); |
|
particles.forEach(p => mean.add(p.position)); |
|
mean.divideScalar(PARTICLE_COUNT); |
|
|
|
|
|
let cov = 0; |
|
particles.forEach(p => cov += p.position.distanceToSquared(mean)); |
|
cov /= PARTICLE_COUNT; |
|
|
|
|
|
const attractionStrength = 0.01; |
|
const noiseScale = 0.1 * Math.sqrt(cov); |
|
|
|
particles.forEach(p => { |
|
|
|
let toMean = mean.clone().sub(p.position).multiplyScalar(attractionStrength); |
|
|
|
|
|
let noise = new THREE.Vector3( |
|
(Math.random() - 0.5), |
|
(Math.random() - 0.5), |
|
(Math.random() - 0.5) |
|
).multiplyScalar(noiseScale); |
|
|
|
|
|
p.position.add(toMean).add(noise); |
|
}); |
|
} |
|
|
|
|
|
function animate() { |
|
requestAnimationFrame(animate); |
|
updateParticles(); |
|
controls.update(); |
|
renderer.render(scene, camera); |
|
} |
|
animate(); |
|
</script> |
|
</body> |
|
</html> |
|
|