block by fil 6ee077159ca7dc26b2b697064a541dee

Not an Ideal Gas

Full Screen

Trying to make sense of the energy parameters of a simple simulation. forceCollide tends to inject energy into the system, while velocityDecay() removes some.

So, when the density of dots is low, energy tends to decrease; if it’s high, energy diverges.

forked from Fil‘s block: Brownian Motion Constrained in Rectangle

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.js"></script>
<script>

var width = 960,
    height = 300,
    τ = 2 * Math.PI;

var nodes = d3.range(800).map(function () {
    var angle = Math.random() * 2 * Math.PI, radius = 60 * Math.sqrt(Math.random());
    return {
        x: width/4 + radius * Math.sin(angle),
        y: height/2 + radius * Math.cos(angle),
        vx: Math.sin(angle) * Math.random(),
        vy: Math.cos(angle) * Math.random(),
    };
});

var force = d3.forceSimulation()
    .nodes(nodes)
    .force("collide", d3.forceCollide(2))
    .force("bounce-on-container", function () {
        for (var i = 0, n = nodes.length, node; i < n; ++i) {
            node = nodes[i];
            var dx = (node.x + node.vx) / width - 1 / 2,
                dy = (node.y + node.vy) / height - 1 / 2;
            if (dx*dx > 0.16) {
                node.vx *= -1;
            }
            if (dy*dy > 0.16) {
                node.vy *= -1;
            }

        }
    })

    .force("internal-barrier", function (alpha) {
        for (var i = 0, n = nodes.length, node; i < n; ++i) {
            node = nodes[i];
            var dx0 = (node.x) / width - 1 / 2,
                dy0 = (node.y) / height - 1 / 2;
            var dx1 = (node.x + node.vx) / width - 1 / 2,
                dy1 = (node.y + node.vy) / height - 1 / 2;

            if (dx0 * dx1 < 0 && Math.abs(dy0) > aperture(alpha) ) {
              node.vx *= -1;
            }
        }
    })
    .on("tick", ticked)
    .alphaDecay(0.01)
    .alphaMin(0)
    .velocityDecay(0.00001);

function aperture(alpha) {
  return 0.2 / (1-Math.log(alpha));
}

var canvas = d3.select("body").append("canvas")
    .attr("width", width)
    .attr("height", height)
    .on('click', click);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", 100);

var context = canvas.node().getContext("2d");

function click() {
    force.alpha(1).restart();
}


var speeds = [], ct = 0;
  
var y = d3.scaleLinear()
   .range([100,0]);
  
var line = d3.area().y0(100),
    speedline = svg.append('path')
.attr('fill','none')
.attr('stroke', 'black');
 
  
function ticked() {

  var speed = d3.mean(nodes.map(function(n){
    return n.vx*n.vx + n.vy*n.vy;
  }));
  if (ct++ > width) speeds.shift();
  speeds.push(speed);
  y.domain([0,d3.max(speeds)]);
  speedline.attr('d', line(speeds.map(function(d,i){
    return [i,y(d)];
  })));
  
  context.clearRect(0, 0, width, height);

    context.strokeStyle = "#444";
    context.beginPath();
    // rectangle
    context.moveTo(width * 0.1, height * 0.1);
    context.lineTo(width * 0.1, height * 0.9);
    context.lineTo(width * 0.9, height * 0.9);
    context.lineTo(width * 0.9, height * 0.1);
    context.lineTo(width * 0.1, height * 0.1);
    context.moveTo(width * 0.5, height * 0.1);
    // internal barrier
    context.lineTo(width * 0.5, height * (0.5 - aperture(force.alpha())));
    context.moveTo(width * 0.5, height * 0.9);
    context.lineTo(width * 0.5, height * (0.5 + aperture(force.alpha())));
    context.lineWidth = 1;
    context.stroke()

    context.beginPath();
    for (var i = 0, n = nodes.length; i < n; ++i) {
        var node = nodes[i];
        context.moveTo(node.x, node.y);
        context.arc(node.x, node.y, 1, 0, τ);
    }
    context.fillStyle = "#644";
    context.fill();

}

</script>