Public
Edited
Feb 16, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof kick_amplitude_control = slider({
min: 0.0,
max: 0.5,
step: 0.01,
value: 0.2,
title: "Kick Size",
description: "Momentum kick from absorbing/emitting a photon."
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
layout = {
const container = document.createElement('div');
container.classList.add("exo2");
container.style.margin = "0 auto";
container.style.maxWidth = "950px";
// LOAD SCREEN
const loader = document.createElement('div');
loader.style.width = canvas_width + "px";
loader.style.height = canvas_height + "px";
loader.style.position = "absolute";
loader.style.zIndex = 99;
loader.style.backgroundColor = "white";
loader.style.opacity = 0.9;
loader.style.border = "1px solid gray";
loader.style.display = "flex";
loader.style.justifyContent = "center";
loader.style.alignItems = "center";
const loadTitle = document.createElement("p");
loadTitle.style.flex = "0 0 " + width + "px";
loadTitle.style.textAlign = "center";
loadTitle.appendChild(document.createTextNode("Click to Start Animation"));
loader.appendChild(loadTitle);
if (!loaded) {
container.appendChild(loader)
Object.assign(loader, {onclick: () => mutable loaded = true})
}
// SIMULATION
const simulation_canvas = document.createElement('canvas');
simulation_canvas.style.position = "relative";
simulation_canvas.style.width = canvas_width + 'px';
simulation_canvas.style.height = canvas_height + 'px';
simulation_canvas.width = canvas_width * 2;
simulation_canvas.height = canvas_height * 2;
simulation_canvas.style.border = "1px solid black";
const simulation_container = document.createElement('div');
simulation_container.style.display = 'inline-block';
simulation_container.style.verticalAlign = "top";
simulation_container.appendChild(simulation_canvas);
container.appendChild(simulation_container);
// CONTROLS
const controls_container = document.createElement('div');
controls_container.style.display = 'inline-block';
controls_container.style.width = '400px';
if (width > 900) {
controls_container.style.marginLeft = '20px';
}
else {
controls_container.style.marginTop = '20px';
}
controls_container.style.verticalAlign = "top";
controls_container.append(controls);
// GRAPH
const graph_canvas = document.createElement('canvas');
graph_canvas.style.position = "relative";
graph_canvas.width = width > 400 ? 800 : width * 2;
graph_canvas.height = graph_canvas.width * 0.3;
graph_canvas.style.width = graph_canvas.width * 0.5 + 'px';
graph_canvas.style.height = graph_canvas.height * 0.5 + 'px';
graph_canvas.style.border = "1px solid black";
controls_container.append(graph_canvas);
controls_container.appendChild(graph_description);
container.appendChild(controls_container);
return {container, simulation_canvas, graph_canvas}
}
Insert cell
graph_description = html`<p class="exo2" style="font-size: 15px; margin-top: 10px">The best cooling takes place just to the left of the peak in this graph.</p>`
Insert cell
controls = html`
${viewof addAtoms}
${viewof reset}
<div class="exo2">
<p style="font-size: 15px; font-weight:600; margin-top: 25px">Trap Controls</p>
${viewof horizontal_lasers_are_on}
${viewof vertical_lasers_are_on}
${viewof restoring_force_on}
</div>
<div style="margin:25px 0" class="exo2">
<p style="font-size: 15px; font-weight:600; margin-top: 25px">Laser Color</p>
${laser_frequency}
</div>
`;
Insert cell
Insert cell
Insert cell
viewof reset = html`<button class="qa-btn qa-btn-outline-primary exo2">Reset</button>`
Insert cell
viewof addAtoms = html`<button class="qa-btn qa-btn-outline-primary exo2">Add Atoms</button>`
Insert cell
viewof cooling_is_on = checkbox({
description: "",
options: [{ value: "on", label: "Laser Cooling On" }],
value: "on"
})
Insert cell
viewof horizontal_lasers_are_on = checkbox({
description: "",
options: [{ value: "on", label: "Horizontal Laser" }],
value: "on"
})
Insert cell
viewof vertical_lasers_are_on = checkbox({
description: "",
options: [{ value: "on", label: "Vertical Laser" }],
value: "on"
})
Insert cell
laser_frequency = slider({
min: 0.40,
max: 0.60,
step: 0.001,
value: 0.47,
format: v => ``,
description: ""
})
Insert cell
viewof restoring_force_on = checkbox({
description: "",
options: [{ value: "on", label: "Restoring Force" }],
value: "on"
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Atom {
constructor(position, velocity, isExcited) {
this.position = position, // vector
this.velocity = velocity, // vector
this.absorptionFrequency = 0.5 // float
this.isExcited = isExcited // boolean
this.mixFactor = 0 // used for color mixing between excited and not excited, so the colors change over a number of frames
this.colorChangeFrames = blink_speed // duration of color change animation in frames
}

move() {
this.position = this.position.add(this.velocity)
}
isInside(area) {
return area[0].x <= this.position.x && this.position.x <= area[1].x &&
area[0].y <= this.position.y && this.position.y <= area[1].y;
}
incrementMixFactor() {
if (this.mixFactor < 1.0) {
this.mixFactor += 1.0 / this.colorChangeFrames
}
}
decrementMixFactor() {
if (this.mixFactor > 0) {
this.mixFactor -= 1.0 / this.colorChangeFrames
}
}
draw(context) {
context.save()
context.beginPath()
context.ellipse(this.position.x, this.position.y, atom_size * canvas_ratio, atom_size * canvas_ratio, 0, 0, 2*Math.PI)
context.closePath()
if(this.isExcited) {
this.incrementMixFactor()
}
else {
this.decrementMixFactor()
}
context.fillStyle = chroma.mix(atom_color_nonexcited, atom_color_excited, this.mixFactor).toString()
context.fill()
context.restore()
}
}
Insert cell
Insert cell
mutable atoms = {
reset; // recreate the initial atom array every time the reset button is pushed
let atoms = []
for (let i = 0; i < starting_number_of_atoms; i++) {
atoms.push(atomFromRandomCorner())
}
return atoms
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
addMoreAtoms = {
addAtoms;
if (mutable atoms.length < maximum_number_of_atoms) {
for (let i = 0; i < number_of_new_atoms; i++) {
mutable atoms = [...mutable atoms, atomFromRandomCorner()]
}
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function check_for_photon_absorption(atom) {
if (atom.isExcited) {
return; // break if atom has already absorbed a photon
}
let randomly_ordered_lasers = shuffle(lasers)
for (const laser of randomly_ordered_lasers) {
if (laser.isOn) {
if (atom.isInside(laser.area)) {
if (photon_is_absorbed(atom, laser)) {
atom.velocity = atom.velocity.add(laser.kick)
atom.isExcited = true
}
}
}
}
}
Insert cell
function distance_from_center(position) {
// used for a restoring force based on distance from center
let center = canvas_width / 2 // can be used for height as well, since canvas is square
let distance = (center - position) / center
return distance
}
Insert cell
function photon_is_absorbed(atom, laser) {
let photon_is_absorbed = false
// restoring force
let x_probability_adjustment = 1
let y_probability_adjustment = 1
if (restoring_force_on) {
x_probability_adjustment = Math.abs(laser.direction.x + distance_from_center(atom.position.x))
y_probability_adjustment = Math.abs(laser.direction.y + distance_from_center(atom.position.y))
}
let combined_probability = absorption_probability(atom.velocity.x, laser.direction.x, atom.absorptionFrequency) * x_probability_adjustment + absorption_probability(atom.velocity.y, laser.direction.y, atom.absorptionFrequency) * y_probability_adjustment
if (event_happens(combined_probability)) {
photon_is_absorbed = true
}
return photon_is_absorbed
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Laser {
constructor(direction, isOn) {
this.direction = direction, // vector
this.isOn = isOn // boolean
this.width = beam_width
this.kick = kick(this.direction, kick_amplitude)
// check if laser is oriented horzontally
if (this.direction.x != 0) {
this.orientation = "Horizontal"
this.area = [ // horizontal rectangle
new Vector(0, canvas_height / 2 - this.width / 2),
new Vector(canvas_width, canvas_height / 2 + this.width / 2),
]
}
// else, laser is oriented vertically
else {
this.orientation = "Vertical"
this.area = [ // vertical rectangle
new Vector(canvas_width / 2 - this.width / 2, 0),
new Vector(canvas_width / 2 + this.width / 2, canvas_height),
]
}
}
contains(vector) {
return this.x <= vector.x && vector.x <= this.x + this.width &&
this.y <= vector.y && vector.y <= this.y + this.height;
}
draw(ctx) {
ctx.save();
ctx.fillStyle = laser_fill();
ctx.beginPath();
let width = this.area[1].x - this.area[0].x
let height = this.area[1].y - this.area[0].y
ctx.rect(this.area[0].x, this.area[0].y, width, height);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
Insert cell
laser_left = new Laser(new Vector(1, 0), horizontal_lasers_are_on)
Insert cell
laser_right = new Laser(new Vector(-1, 0), horizontal_lasers_are_on)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function cauchy_distribution(gamma, x, x0) {
return (gamma**2)/((x-x0)**2 + gamma**2)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Vector {
constructor(x, y) {
this.x = x, // vector
this.y = y // vector
}

add(vector) {
return new Vector(this.x + vector.x, this.y + vector.y)
}
subtract(vector) {
return new Vector(this.x - vector.x, this.y - vector.y)
}
multiply(float) {
return new Vector(this.x * float, this.y * float)
}
divide(float) {
return new Vector(this.x / float, this.y / float)
}
magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y)
}
dot(vector) {
return this.x * vector.x + this.y * vector.y
}
normalize() {
return new Vector(this.x / this.magnitude(), this.y / this.magnitude())
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
canvas_width = width > 500? 500 : width
Insert cell
canvas_height = canvas_width
Insert cell
canvas_area = [
new Vector(0, 0),
new Vector(canvas_width, canvas_height),
]
Insert cell
canvas_corners = [
new Vector(0, 0),
new Vector(0,canvas_height),
new Vector(canvas_width, 0),
new Vector(canvas_width, canvas_height)
]
Insert cell
canvas_center = new Vector(canvas_width, canvas_height).divide(2)
Insert cell
canvas_ratio = canvas_width / 500
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more