C++ 临界温度的伊辛模型模拟
我正在写一个二维伊辛模型的模拟。该模型的行为与预测的一样,除了一件事:临界温度大约为3.5,而它应该接近C++ 临界温度的伊辛模型模拟,c++,montecarlo,C++,Montecarlo,我正在写一个二维伊辛模型的模拟。该模型的行为与预测的一样,除了一件事:临界温度大约为3.5,而它应该接近2/ln(2+sqrt(2)) 项目是一个生成数据的C++程序,和一个执行程序的shell脚本。可以找到完整的代码。还有lattice.cpp #include <iostream> #include "include/lattice.h" using namespace std; /* Copy assignment operator, too long to include
2/ln(2+sqrt(2))
项目是一个生成数据的C++程序,和一个执行程序的shell脚本。可以找到完整的代码。还有lattice.cpp
#include <iostream>
#include "include/lattice.h"
using namespace std;
/*
Copy assignment operator, too long to include in the header.
*/
lattice &lattice::operator=(const lattice &other) {
size_ = other.size_;
spins_ = other.spins_;
J_ = other.J_;
H_ = other.H_;
delete spins_;
return *this;
}
void lattice::print() {
unsigned int area = size_ * size_;
for (unsigned int i = 0; i < area; i++) {
cout << to_symbol(spins_->at(i));
if (i % size_ == size_ - 1)
cout << endl;
}
cout << endl;
}
/*
Computes the energy associated with a spin at the given point.
It is explicitly float as that would allow the compiler to make use of multiple
registers instead of keeping track of unneeded precision. (typically J, H ~ 1).
*/
float lattice::compute_point_energy(int row, int col) {
int accumulator = get(row + 1, col) + get(row - 1, col) + get(row, col - 1) +
get(row, col + 1);
return -get(row, col) * (accumulator * J_ + H_);
}
/*
Computes total magnetisation in O(n^2). Thread safe
*/
int lattice::total_magnetisation() {
int sum = 0;
#pragma omp parallel for reduction(+ : sum)
for (unsigned int i = 0; i < size_ * size_; i++) {
sum += spins_->at(i);
}
return sum;
}
int inline to_periodic(int row, int col, int size) {
if (row < 0 || row >= size)
row = abs(size - abs(row));
if (col < 0 || col >= size)
col = abs(size - abs(col));
return row * size + col;
}
#包括
#包括“include/lattice.h”
使用名称空间std;
/*
复制分配运算符,太长,无法包含在标头中。
*/
晶格和晶格::运算符=(常数晶格和其他){
尺寸=其他尺寸;
自旋=其他。自旋;
J_u=other.J_u;
H_uu=other.H_u;
删除自旋;
归还*这个;
}
void lattice::print(){
无符号整数区域=大小*大小;
for(无符号整数i=0;i=大小)
col=abs(尺寸-abs(col));
返回行*大小+列;
}
具有晶格.h
#ifndef lattice_h
#define lattice_h
#include <cmath>
#include <vector>
/* Converts spin up/down to easily printable symbols. */
char inline to_symbol(int in) { return in == -1 ? '-' : '+'; }
/* Converts given pair of indices to those with periodic boundary conditions. */
int inline to_periodic(int row, int col, int size) {
if (row < 0 || row >= size)
row = abs(size - abs(row));
if (col < 0 || col >= size)
col = abs(size - abs(col));
return row * size + col;
}
class lattice {
private:
unsigned int size_;
// vector<bool> would be more space efficient, but it would not allow
// multithreading
std::vector<short> *spins_;
float J_;
float H_;
public:
lattice() noexcept : size_(0), spins_(NULL), J_(1.0), H_(0.0) {}
lattice(int new_size, double new_J, double new_H) noexcept
: size_(new_size), spins_(new std::vector<short>(size_ * size_, 1)),
J_(new_J), H_(new_H) {}
lattice(const lattice &other) noexcept
: lattice(other.size_, other.J_, other.H_) {
#pragma omp parallel for
for (unsigned int i = 0; i < size_ * size_; i++)
spins_->at(i) = other.spins_->at(i);
}
lattice &operator=(const lattice &);
~lattice() { delete spins_; }
void print();
short get(int row, int col) {
return spins_->at(to_periodic(row, col, size_));
}
unsigned int get_size() { return size_; }
void flip(int row, int col) { spins_->at(to_periodic(row, col, size_)) *= -1; }
int total_magnetisation();
float compute_point_energy(int row, int col);
};
#endif
#ifndef lattice\u h
#定义格
#包括
#包括
/*将旋转向上/向下转换为易于打印的符号*/
字符内联到_符号(int in){return in==-1?'-':'+';}
/*将给定的索引对转换为具有周期性边界条件的索引对*/
int内联到_周期(int行、int列、int大小){
如果(行<0 | |行>=大小)
行=abs(尺寸-abs(行));
如果(列<0 | |列>=大小)
col=abs(尺寸-abs(col));
返回行*大小+列;
}
类格{
私人:
无符号整数大小;
//向量将更节省空间,但它不允许
//多线程
标准::矢量*自旋;
浮动J_;
浮动H_;
公众:
lattice()noexcept:size_0(0)、spins_0(NULL)、J_0(1.0)、H_0(0.0){
晶格(int new_size,双new_J,双new_H)无例外
:size_u(新大小),spins_u(新标准::向量(大小*size_u,1)),
J_u(新的),H_u(新的{}
晶格(常数晶格和其他)无例外
:晶格(other.size_ux、other.J_x、other.H_x){
#pragma-omp并行
for(无符号整数i=0;i在(i)=其他。自旋->在(i);
}
格&算子=(常数格&);
~lattice(){delete spins_;}
作废打印();
短get(整数行,整数列){
返回自旋->在(到周期(行、列、大小);
}
无符号int get_size(){return size_;}
void flip(int row,int col){spins->at(to_periodic(row,col,size))*=-1;}
int总磁化率();
浮点计算点能量(整数行,整数列);
};
#恩迪夫
和模拟。cpp
#include <iostream>
#include <math.h>
#include "include/simulation.h"
using namespace std;
/*
Advances the simulation a given number of steps, and updates/prints the statistics
into the given file pointer.
Defaults to stdout.
The number of time_steps is explcitly unsigned, so that linters/IDEs remind
the end user of the file that extra care needs to be taken, as well as to allow
advancing the simulation a larger number of times.
*/
void simulation::advance(unsigned int time_steps, FILE *output) {
unsigned int area = spin_lattice_.get_size() * spin_lattice_.get_size();
for (unsigned int i = 0; i < time_steps; i++) {
// If we don't update mean_energy_ every time, we might get incorrect
// thermodynamic behaviour.
total_energy_ = compute_energy(spin_lattice_);
double temperature_delta = total_energy_/area - mean_energy_;
if (abs(temperature_delta) < 1/area){
cerr<<temperature_delta<<"! Reached equilibrium "<<endl;
}
temperature_ += temperature_delta;
mean_energy_ = total_energy_ / area;
if (time_ % print_interval_ == 0) {
total_magnetisation_ = spin_lattice_.total_magnetisation();
mean_magnetisation_ = total_magnetisation_ / area;
print_status(output);
}
advance();
}
}
/*
Advances the simulation a single step.
DOES NOT KEEP TRACK OF STATISTICS. Hence private.
*/
void simulation::advance() {
#pragma omp parallel for collapse(2)
for (unsigned int row = 0; row < spin_lattice_.get_size(); row++) {
for (unsigned int col = 0; col < spin_lattice_.get_size(); col++) {
double dE = compute_dE(row, col);
double p = r_.random_uniform();
float rnd = rand() / (RAND_MAX + 1.);
if (exp(-dE / temperature_) > rnd) {
spin_lattice_.flip(row, col);
}
}
}
time_++;
}
/*
Computes change in energy due to flipping one single spin.
The function returns a single-precision floating-point number, as data cannot under
most circumstances make use of greater precision than that (save J is set to a
non-machine-representable value).
The code modifies the spin lattice, as an alternative (copying the neighborhood
of a given point), would make the code run slower by a factor of 2.25
*/
float simulation::compute_dE(int row, int col) {
float e_0 = spin_lattice_.compute_point_energy(row, col);
return -4*e_0;
}
/*
Computes the total energy associated with spins in the spin_lattice_.
I originally used this function to test the code that tracked energy as the lattice
itself was modified, but that code turned out to be only marginally faster, and
not thread-safe. This is due to a race condition: when one thread uses a neighborhood
of a point, while another thread was computing the energy of one such point in
the neighborhood of (row, col).
*/
double simulation::compute_energy(lattice &other) {
double energy_sum = 0;
unsigned int max = other.get_size();
#pragma omp parallel for reduction(+ : energy_sum)
for (unsigned int i = 0; i < max; i++) {
for (unsigned int j = 0; j < max; j++) {
energy_sum += other.compute_point_energy(i, j);
}
}
return energy_sum;
}
void simulation::set_to_chequerboard(int step){
if (time_ !=0){
return;
}else{
for (unsigned int i=0; i< spin_lattice_.get_size(); ++i){
for (unsigned int j=0; j<spin_lattice_.get_size(); ++j){
if ((i/step)%2-(j/step)%2==0){
spin_lattice_.flip(i, j);
}
}
}
}
}
#ifndef simulation_h
#define simulation_h
#include "lattice.h"
#include "rng.h"
#include <gsl/gsl_rng.h>
/*
The logic of the entire simulation of the Ising model of magnetism.
This simulation will run and print statistics at a given time interval.
A simulation can be advanced a single time step, or many at a time,
*/
class simulation {
private:
unsigned int time_ = 0; // Current time of the simulation.
rng r_ = rng();
lattice spin_lattice_;
double temperature_;
double mean_magnetisation_ = 1;
double mean_energy_;
double total_magnetisation_;
double total_energy_;
unsigned int print_interval_ = 1;
void advance();
public:
void set_print_interval(unsigned int new_print_interval) { print_interval_ = new_print_interval; }
simulation(int new_size, double new_temp, double new_J, double new_H)
: time_(0), spin_lattice_(lattice(new_size, new_J, new_H)), temperature_(new_temp),
mean_energy_(new_J * (-4)), total_magnetisation_(new_size * new_size),
total_energy_(compute_energy(spin_lattice_)) {}
void print_status(FILE *f) {
f = f==NULL? stdout : f;
fprintf(f, "%4d\t%e \t%e\t%e\n", time_, mean_magnetisation_,
mean_energy_, temperature_);
}
void advance(unsigned int time_steps, FILE *output);
double compute_energy(lattice &other);
float compute_dE(int row, int col);
void set_to_chequerboard(int step);
void print_lattice(){
spin_lattice_.print();
};
// void load_custom(const lattice& custom);
};
#endif
#包括
#包括
#包括“include/simulation.h”
使用名称空间std;
/*
将模拟推进给定数量的步骤,并更新/打印统计信息
输入给定的文件指针。
默认为标准输出。
时间步数明显无符号,因此linter/ide提醒
文件的最终用户需要格外小心,并允许
将模拟推进更多次。
*/
void simulation::advance(无符号整数时间步长,文件*输出){
无符号整数区域=自旋\晶格\获取\大小()*自旋\晶格\获取\大小();
for(无符号整数i=0;i<时间步长;i++){
//如果我们不每次更新mean_energy,我们可能会出错
//热力学行为。
总能量=计算能量(自旋晶格);
双温δ=总能量/面积-平均能量;
if(abs(温度增量)<1/面积){
cerr我在您的代码中发现了一些问题:
compute\u dE
方法返回错误的能量,因为因子2不应该存在。伊辛系统的哈密顿量为
当您有效地使用
compute\u energy
方法返回了错误的能量。该方法应该只在每个自旋对上迭代一次。类似这样的方法应该可以做到:
for(无符号整数i=0;i
- 你使用的是动态更新的温度,而不是目标温度。我真的不明白这样做的目的
你应该在你的问题中添加lattice.h
和simulation.h
标题。我不认为这是问题所在。首先,答案不会偏移2倍,如果有任何偏移小于50%(即1单位J)第二,当我将compute dE
更改为其一半时,系统停止会聚。它并没有按照我所希望的那样移动,而是完全改变了行为。你能解释更多吗?更新:你是对的。不完全正确,但你推动了我正确的方向。compute dE
的关闭系数是3/2,而不是2。还有dou由于自旋翻转,我们可以改变点能量,但也可以改变周围四个自旋的相互作用能量,另一个点能量(不是两个,因为重复计数)事实上,真正的问题是你不使用输入温度,但是,出于某种原因,你在整个模拟过程中更新温度,并在接受率中使用。这是不对的。我将据此编辑答案。我不确定能量反馈。我假设系统被隔离,因此能量被释放rom对齐旋转会加热系统。如果不是这样,我愿意更新代码…好吧,你使用的是与恒温系统相关的验收规则,这意味着你将系统隐式耦合到一个热浴。我怀疑,如果在模拟过程中温度是可变的ion,您必须使用不同的接受规则。