Skip to content

Commit 9b7f55c

Browse files
rajdakinberquist
authored andcommitted
Added C++ lang implementation of the split operator (#420)
This pull request is one of the two new pull requests requested in #409 and is made to integrate the C++ lang with the split operator method.
1 parent 3e4c4f7 commit 9b7f55c

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
#include <complex>
2+
#include <vector>
3+
#include <iostream>
4+
#include <cstring>
5+
#include <fstream>
6+
7+
// Using fftw3 library.
8+
#include <fftw3.h>
9+
10+
#ifndef M_PI
11+
#define M_PI 3.14159265358979323846
12+
#endif
13+
14+
using complex = std::complex<double>;
15+
using vector_real = std::vector<double>;
16+
using vector_complex = std::vector<complex>;
17+
18+
struct Params {
19+
Params(double _xmax, unsigned int _res, double _dt, unsigned int _timesteps, bool im) {
20+
xmax = _xmax;
21+
res = _res;
22+
dt = _dt;
23+
timesteps = _timesteps;
24+
dx = 2.0 * xmax / res;
25+
x.reserve(res);
26+
dk = M_PI / xmax;
27+
k.reserve(res);
28+
im_time = im;
29+
30+
for (size_t i = 0; i < res; ++i) {
31+
x.emplace_back(xmax / res - xmax + i * (2.0 * xmax / res));
32+
if (i < res / 2) {
33+
k.push_back(i * M_PI / xmax);
34+
} else {
35+
k.push_back((static_cast<double>(i) - res) * M_PI / xmax);
36+
}
37+
}
38+
}
39+
40+
double xmax;
41+
unsigned int res;
42+
double dt;
43+
unsigned int timesteps;
44+
double dx;
45+
vector_real x;
46+
double dk;
47+
vector_real k;
48+
bool im_time;
49+
};
50+
51+
struct Operators {
52+
public:
53+
Operators(Params &par, double voffset,
54+
double wfcoffset) {
55+
size = par.res;
56+
v.reserve(size);
57+
pe.reserve(size);
58+
ke.reserve(size);
59+
wfc.reserve(size);
60+
61+
for (size_t i = 0; i < size; ++i) {
62+
v.push_back(0.5 * pow(par.x[i] - voffset, 2));
63+
wfc.push_back(exp(-pow(par.x[i] - wfcoffset, 2) / 2.0));
64+
65+
if (par.im_time) {
66+
ke.push_back(exp(-0.5 * par.dt * pow(par.k[i], 2)));
67+
pe.push_back(exp(-0.5 * par.dt * v[i]));
68+
} else {
69+
ke.push_back(exp(-0.5 * par.dt * pow(par.k[i], 2) * complex(0.0, 1.0)));
70+
pe.push_back(exp(-0.5 * par.dt * v[i] * complex(0.0, 1.0)));
71+
}
72+
}
73+
}
74+
75+
size_t size;
76+
vector_complex v;
77+
vector_complex pe;
78+
vector_complex ke;
79+
vector_complex wfc;
80+
};
81+
82+
void fft(vector_complex &x, int n, bool inverse) {
83+
complex y[n];
84+
memset(y, 0, sizeof(y));
85+
fftw_plan p;
86+
87+
fftw_complex *in = reinterpret_cast<fftw_complex*>(x.data());
88+
fftw_complex *out = reinterpret_cast<fftw_complex*>(y);
89+
p = fftw_plan_dft_1d(n, in, out,
90+
(inverse ? FFTW_BACKWARD : FFTW_FORWARD), FFTW_ESTIMATE);
91+
92+
fftw_execute(p);
93+
fftw_destroy_plan(p);
94+
95+
for (size_t i = 0; i < n; ++i) {
96+
x[i] = y[i] / sqrt(static_cast<double>(n));
97+
}
98+
}
99+
100+
void split_op(Params &par, Operators &opr) {
101+
double density[opr.size];
102+
103+
for (size_t i = 0; i < par.timesteps; ++i) {
104+
for (size_t j = 0; j < opr.size; ++j) {
105+
opr.wfc[j] *= opr.pe[j];
106+
}
107+
108+
fft(opr.wfc, opr.size, false);
109+
110+
for (size_t j = 0; j < opr.size; ++j) {
111+
opr.wfc[j] *= opr.ke[j];
112+
}
113+
114+
fft(opr.wfc, opr.size, true);
115+
116+
for (size_t j = 0; j < opr.size; ++j) {
117+
opr.wfc[j] *= opr.pe[j];
118+
}
119+
120+
for (size_t j = 0; j < opr.size; ++j) {
121+
density[j] = pow(abs(opr.wfc[j]), 2);
122+
}
123+
124+
if (par.im_time) {
125+
double sum = 0;
126+
127+
for (size_t j = 0; j < opr.size; ++j) {
128+
sum += density[j];
129+
}
130+
131+
sum *= par.dx;
132+
133+
for (size_t j = 0; j < opr.size; ++j) {
134+
opr.wfc[j] /= sqrt(sum);
135+
}
136+
}
137+
138+
// Writing data into a file in the format of:
139+
// index, density, real potential.
140+
std::stringstream filename_stream;
141+
filename_stream << "output" << i << ".dat";
142+
143+
std::ofstream fstream = std::ofstream(filename_stream.str());
144+
145+
if (fstream) {
146+
for (int i = 0; i < opr.size; ++i) {
147+
std::stringstream data_stream;
148+
149+
data_stream << i << "\t" << density[i] << "\t" << real(opr.v[i]) << "\n";
150+
151+
fstream.write(data_stream.str().c_str(), data_stream.str().length());
152+
}
153+
}
154+
155+
fstream.close();
156+
}
157+
}
158+
159+
double calculate_energy(Params &par, Operators &opr) {
160+
vector_complex wfc_r(opr.wfc);
161+
vector_complex wfc_k(opr.wfc);
162+
vector_complex wfc_c(opr.size);
163+
fft(wfc_k, opr.size, false);
164+
165+
for (size_t i = 0; i < opr.size; ++i) {
166+
wfc_c[i] = conj(wfc_r[i]);
167+
}
168+
169+
vector_complex energy_k(opr.size);
170+
vector_complex energy_r(opr.size);
171+
172+
for (size_t i = 0; i < opr.size; ++i) {
173+
energy_k[i] = wfc_k[i] * pow(complex(par.k[i], 0.0), 2);
174+
}
175+
176+
fft(energy_k, opr.size, true);
177+
178+
for (size_t i = 0; i < opr.size; ++i) {
179+
energy_k[i] *= 0.5 * wfc_c[i];
180+
energy_r[i] = wfc_c[i] * opr.v[i] * wfc_r[i];
181+
}
182+
183+
double energy_final = 0;
184+
185+
for (size_t i = 0; i < opr.size; ++i) {
186+
energy_final += real(energy_k[i] + energy_r[i]);
187+
}
188+
189+
return energy_final * par.dx;
190+
}
191+
192+
int main() {
193+
Params par = Params(5.0, 256, 0.05, 100, true);
194+
Operators opr = Operators(par, 0.0, -1.0);
195+
196+
split_op(par, opr);
197+
198+
std::cout << "The energy is " << calculate_energy(par, opr) << "\n";
199+
200+
return 0;
201+
}

contents/split-operator_method/split-operator_method.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ Regardless, we first need to set all the initial parameters, including the initi
102102
{% sample lang="c" %}
103103
[import:11-21, lang:"c_cpp"](code/c/split_op.c)
104104
[import:52-73, lang:"c_cpp"](code/c/split_op.c)
105+
{% sample lang="cpp" %}
106+
[import:14-49, lang:"c_cpp"](code/c++/split_op.cpp)
105107
{% sample lang="py" %}
106108
[import:11-30, lang:"python"](code/python/split_op.py)
107109
{% sample lang="hs" %}
@@ -121,6 +123,8 @@ Afterwards, we turn them into operators:
121123
{% sample lang="c" %}
122124
[import:23-29, lang:"c_cpp"](code/c/split_op.c)
123125
[import:75-96, lang:"c_cpp"](code/c/split_op.c)
126+
{% sample lang="cpp" %}
127+
[import:51-80, lang:"c_cpp"](code/c++/split_op.cpp)
124128
{% sample lang="py" %}
125129
[import:33-54, lang:"python"](code/python/split_op.py)
126130
{% sample lang="hs" %}
@@ -140,6 +144,8 @@ The final step is to do the iteration, itself.
140144
[import:65-112, lang:"julia"](code/julia/split_op.jl)
141145
{% sample lang="c" %}
142146
[import:98-148, lang:"c_cpp"](code/c/split_op.c)
147+
{% sample lang="cpp" %}
148+
[import:100-157, lang:"c_cpp"](code/c++/split_op.cpp)
143149
{% sample lang="py" %}
144150
[import:57-95, lang:"python"](code/python/split_op.py)
145151
{% sample lang="hs" %}
@@ -165,6 +171,8 @@ Checking to make sure your code can output the correct energy for a harmonic tra
165171
[import, lang:"julia"](code/julia/split_op.jl)
166172
{% sample lang="c" %}
167173
[import, lang:"c_cpp"](code/c/split_op.c)
174+
{% sample lang="cpp" %}
175+
[import, lang:"c_cpp"](code/c++/split_op.cpp)
168176
{% sample lang="py" %}
169177
[import:5-127, lang:"python"](code/python/split_op.py)
170178
{% sample lang="hs" %}

0 commit comments

Comments
 (0)