http://www.articlesbyaphysicist.com/quantum4prog.html
Meu artigo principal sobre mecânica quântica está aqui .
Este artigo fala sobre como fazer simulações como as que estão aqui.
Para as simulações reais, usei C #, porque é um bom meio-termo entre velocidade e simplicidade. Lamento, às vezes, que não seja tão rápido quanto C, mas é mais fácil de depurar e isso me economiza muito tempo.
Vou tentar explicar como fazer as simulações em Javascript, porque é mais fácil de compartilhar com os leitores, e significa que você pode brincar com as simulações no seu navegador.
A desvantagem do javascript é que ele é um pouco mais lento. Normalmente é apenas um fator de 2 a 10 vezes mais lento, mas o bit lento nas simulações abaixo é a transformada de Fourier. Para linguagens não javascript, você usa a biblioteca de transformação de Fourier de outra pessoa, e elas são muito mais rápidas porque usam todos os truques que os computadores modernos permitem, desde ordenar operações de forma otimizada até garantir que os caches sejam usados da melhor forma possível. Minha versão javascript? Não muito. O resultado é que a versão javascript abaixo é, na verdade, * muito * mais lenta que a versão C #.
Objetivos do artigo:
- Para dar uma simulação realmente simples de um sistema de mecânica quântica para que as pessoas que já conhecem javascript tenham a chance de "obter" a mecânica quântica sem necessariamente ter que entender as equações. As equações vão ajudar, no entanto.
- Para responder à pergunta de "como você faz as simulações".
Como normal, a ideia é tentar explicar as coisas da forma mais simples possível e não da maneira mais simples. Aqui está o artigo:
A mecânica quântica pode ser simulada com programas bastante simples. Entender o que eles fazem pode ajudar a aprender a mecânica quântica e pode ajudar os físicos a entender como simular a matemática.
Este artigo vai começar fácil e ficar mais difícil. Se for muito fácil, vá em frente.
Ponto de partida: Euler
Aqui está a equação de Schrödinger para um elétron 1-D em um potencial harmônico. Quer você entenda ou não, está tudo bem, espero que o artigo possa ser lido de qualquer maneira.
Vamos reescrever isso, apresentando um tempo de intervalo de tempo, δt
Você pode escrever
Essas duas aproximações nos fornecem uma função de cronômetro simples. Esta não é a única (ou melhor) função de cronômetro que resolve o problema, mas talvez seja uma das mais simples.
Você, leitor, deve tentar entender a função abaixo. Esperançosamente, fará sentido com as duas equações acima.
// This returns an empty wavefunction of length n.
function wavefunction(n){
var psi = [];
for(var i = 0; i < n; i++){
psi[i] = {real: 0, imag: 0}
}
return psi; // for example [{real:0, imag:0}, {real:0, imag:0}, ...]
}
// This takes the starting wavefunction, and returns the wavefunction a short time later.
function timestep(psi)
{
// This is how many units of time we're going to try to step forward.
var dt = 0.002;
var n = psi.length;
// This is the wavefunction we're going to return eventually.
var ret = wavefunction(n);
// We miss off the first and last elements because it looks at the element to the
// left and right of this point.
for(var i = 1; i < n-1; i++)
{
// This is the x that is in the equation above.
var x = (i-n/2)
// This is the potential at this point.
var V = x*x * 0.0015; // a here = 0.0015
// We start from the original wavefunction (and later add (dt * dpsi/dt) to it).
ret[i].real = psi[i].real;
ret[i].imag = psi[i].imag;
// This is the kinetic energy applied to psi.
var KPsi = {
real: psi[i].real * 2 - psi[i-1].real - psi[i+1].real,
imag: psi[i].imag * 2 - psi[i-1].imag - psi[i+1].imag
};
// This is the potential, applied to psi
var VPsi = {
real: psi[i].real * V,
imag: psi[i].imag * V
};
// This is the whole right hand side of the schrodinger equation.
var rhsReal = KPsi.real + VPsi.real;
var rhsImag = KPsi.imag + VPsi.imag;
// This adds it, multiplied by dt, and multiplied by i.
// The multiplication by i is what swaps the real and imaginary parts.
ret[i].real += rhsImag * dt;
ret[i].imag -= rhsReal * dt;
}
return ret;
}
// This returns the initial state of the simulation.
function init(){
var n = 128;
var psi = wavefunction(n);
for(var i = 0; i < n; i++){
psi[i].real = Math.exp(-(i-20)*(i-20)/(5*5))*0.75;
psi[i].imag = 0;
}
return psi;
}
resultado:
A simulação acima é a densidade de probabilidade de um elétron em um x2x2potencial. Isso é semelhante a se estivesse em uma tigela e rolasse para a frente e para trás nessa tigela.
A simulação funciona, mas tem problemas:
- A quantidade total de probabilidade aumenta com o tempo.
- Se você cronometrar a oscilação, não está certo: na verdade, está se movendo um pouco devagar demais quando está mais rápido
- Há dispersão: a oscilação não fica focada. Deve: este potencial particular é especialmente escolhido para fazer com que a oscilação fique focada.
Operador de divisão
Isso vai ficar mais difícil rapidamente. Vamos começar com.
Teremos que olhar para a equação original novamente.
Uma maneira diferente de convertê-lo em um intervalo de tempo é fazer isso.
Aqui, ψ0 é a função de onda antes do passo de tempo.
TimestepV avança de acordo com a seguinte equação:
TimestepV é realmente muito fácil de implementar, porque:
TimestepT pode ser implementado observando que se ψ =ei k xψ=eeukx, então:
E há uma boa notícia: podemos implementar isso transformando de Fourier e, em seguida, multiplicando por eeuk2eeuk2, e então a transformação inversa de Fourier. O código transformada de Fourier é:
/*
This version of fft.js is a translation of the wikipedia example for the cooley-tucker algorithm.
Feel free to use this as you want, but the author does not accept liability.
License: MIT.
It only works with powers of two.
*/
var FFT = function () {
var me = this;
var b = [];
var separate = function (a, S, E) {
var n = E - S;
for (var i = 0; i < n / 2; i++) {
b[i] = a[S + i * 2 + 1];
}
//for(int i=0; i<n/2; i++) // copy all odd elements to heap storage
//b[i] = a[i*2+1];
for (var i = 0; i < n / 2; i++) // copy all even elements to lower-half of a[]
a[S + i] = a[S + i * 2];
for (var i = 0; i < n / 2; i++) // copy all odd (from heap) to upper-half of a[]
a[S + i + n / 2] = b[i];
}
var fft2 = function(X,S, E) {
var N = E-S;
if(N < 2) {
// bottom of recursion.
// Do nothing here, because already X[0] = x[0]
} else {
separate(X, S, E); // all evens to lower half, all odds to upper half
fft2(X, S, S + N/2); // recurse even items
fft2(X, S +N/2, E); // recurse odd items
// combine results of two half recursions
for(var k=0; k<N/2; k++) {
var ereal = X[S + k].real; // even
var eimag = X[S + k].imag; // even
var oreal = X[S + k + N / 2].real; // odd
var oimag = X[S + k + N / 2].imag; // odd
// w is the "twiddle-factor"
var theta = -2.*Math.PI*k/N;
var wi = Math.sin(theta);
var wr = Math.cos(theta);
X[S + k ].real = ereal + wr * oreal - wi * oimag;
X[S + k ].imag = eimag + wr * oimag + wi * oreal;
X[S + k+N/2].real = ereal - wr * oreal + wi * oimag;
X[S + k+N/2].imag = eimag - wr * oimag - wi * oreal;
}
}
}
me.fft = function (X) {
var a = [];
for (var i = 0; i < X.length; i++) {
a[i] = {
real : X[i].real,
imag : X[i].imag
};
}
fft2(a, 0, a.length);
var l = 1.0 / Math.sqrt(a.length);
for (var i = 0; i < a.length; i++) {
a[i].imag = -a[i].imag * l;
a[i].real = a[i].real * l;
}
return a;
}
var transpose = function (X) {
var ret = [];
for (var i = 0; i < X[0].length; i++) {
ret[i] = [];
for (var j = 0; j < X.length; j++) {
ret[i][j] = X[j][i];
}
}
return ret;
}
me.fft2d = function (X) {
var ret = [];
for (var i = 0; i < X.length; i++) {
ret[i] = me.fft(X[i]);
}
ret = transpose(ret);
for (var i = 0; i < X.length; i++) {
ret[i] = me.fft(ret[i]);
}
return transpose(ret);
}
}
var FFT = new FFT();
// Tests:
function empty(n) {
var psi = [];
for (var i = 0; i < n; i++) {
psi[i] = { real: 0, imag: 0 }
}
return psi; // for example [{real:0, imag:0}, {real:0, imag:0}, ...]
}
var a = empty(8);
a[0].real = 1;
a[3].real = 2;
a[7].imag = 3;
b = FFT.fft(a);
c = FFT.fft(b);
Este código de transformação de Fourier específico funciona apenas em potências de dois.
// This returns an empty wavefunction of length n.
function wavefunction(n){
var psi = [];
for(var i = 0; i < n; i++){
psi[i] = {real: 0, imag: 0}
}
return psi; // for example [{real:0, imag:0}, {real:0, imag:0}, ...]
}
// This takes the starting wavefunction, and returns the wavefunction a short time later.
function timestep(psi)
{
// This is how many units of time we're going to try to step forward.
var dt = 0.02;
psi = timestepV(psi, dt);
psi = timestepT(psi, dt);
return psi;
}
function timestepV(psi, dt)
{
var n = psi.length;
for(var i = 0; i < n; i++)
{
// This is the x that is in the equation above.
var x = (i-n/2)
// This is the potential at this point.
var V = x*x * 0.0015;
var theta = dt * V;
var c = Math.cos(theta);
var s = Math.sin(theta);
var re = psi[i].real * c - psi[i].imag * s;
var im = psi[i].imag * c + psi[i].real * s;
psi[i].real = re;
psi[i].imag = im;
}
return psi;
}
function timestepT(psi, dt){
psi = FFT.fft(psi);
var n = psi.length;
for(var i = 1; i < n/2; i++)
{
var k = 2 * 3.1415927 * i / n;
var theta = k * k * dt;
var c = Math.cos(-theta);
var s = Math.sin(-theta);
var re = psi[i].real * c - psi[i].imag * s;
var im = psi[i].imag * c + psi[i].real * s;
psi[i].real = re;
psi[i].imag = im;
var j = n - i;
re = psi[j].real * c - psi[j].imag * s;
im = psi[j].imag * c + psi[j].real * s;
psi[j].real = re;
psi[j].imag = im;
}
return FFT.fft(psi);
}
// This returns the initial state of the simulation.
function init(){
var n = 128;
var psi = wavefunction(n);
for(var i = 0; i < n; i++){
psi[i].real = Math.exp(-(i-20)*(i-20)/(5*5))*0.75;
psi[i].imag = 0;
}
return psi;
}
Este código funciona muito melhor. É mais rápido, principalmente porque o intervalo de tempo foi mais longo. Mas o intervalo de tempo pode ser mais longo porque é mais preciso. Da mesma forma, você pode se safar com uma grade mais grosseira e ainda obter a mesma precisão.
A próxima página examina as simulações bidimensionais.
Vamos tentar uma equação de Schrodinger 2D:
O parâmetro extra, α não é uma coisa física, mas vamos precisar para ter certeza de que não temos um valor 1/0 para o potencial em nenhum lugar.
// This returns an empty wavefunction of length n.
function wavefunction(n){
var psi = [];
for(var i = 0; i < n; i++){
psi[i]=[];
for(var j = 0; j < n; j++){
psi[i][j] = {real: 0, imag: 0}
}
}
return psi; // for example [{real:0, imag:0}, {real:0, imag:0}, ...]
}
// This takes the starting wavefunction, and returns the wavefunction a short time later.
function timestep(psi)
{
// This is how many units of time we're going to try to step forward.
var dt = 0.02;
psi = timestepV(psi, dt);
psi = timestepT(psi, dt);
return psi;
}
function timestepV(psi, dt)
{
var n = psi.length;
for(var i = 0; i < n; i++)
{
for(var j = 0; j < n; j++){
// This is the x that is in the equation above.
var x = (i-n/2)
var y = (j-n/2)
// This is the potential at this point.
var V = 1.0 / Math.sqrt(x*x + y*y + .01);
var theta = dt * V;
var c = Math.cos(theta);
var s = Math.sin(theta);
var re = psi[i][j].real * c - psi[i][j].imag * s;
var im = psi[i][j].imag * c + psi[i][j].real * s;
psi[i][j].real = re;
psi[i][j].imag = im;
}
}
return psi;
}
function timestepT(psi, dt){
psi = FFT.fft2d(psi);
var n = psi.length;
for(var i = 0; i < n; i++)
{
for(var j=0;j < n; j++)
{
var k = 2 * 3.1415927 * Math.min(i, n-i) / n;
var l = 2 * 3.1415927 * Math.min(j, n-j) / n;
var theta = (k * k + l*l) * dt;
var c = Math.cos(-theta);
var s = Math.sin(-theta);
var re = psi[i][j].real * c - psi[i][j].imag * s;
var im = psi[i][j].imag * c + psi[i][j].real * s;
psi[i][j].real = re;
psi[i][j].imag = im;
}
}
return FFT.fft2d(psi);
}
// This returns the initial state of the simulation.
function init(){
var n = 64;
var psi = wavefunction(n);
for(var i = 0; i < n; i++){
for(var j = 0; j < n; j++){
psi[i][j].real = Math.exp(-((i-20)*(i-20) + (j-20)*(j-20))/(5*5))*4;
psi[i][j].imag = 0;
}
}
return psi;
}