Skip to content

Commit f0da331

Browse files
committed
Added support (Frequency) Modified Fourier Transforms.
This is based on David Nesvorny's code.
1 parent 38eb93b commit f0da331

File tree

18 files changed

+1319
-2
lines changed

18 files changed

+1319
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ problems/
4646
.vscode/
4747
*.pete
4848
examples/solar_system_tes
49+
rebound.html

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ include src/integrator_janus.c
1212
include src/integrator.c
1313
include src/gravity.c
1414
include src/server.c
15+
include src/frequency_analysis.c
1516
include src/collision.c
1617
include src/boundary.c
1718
include src/binarydiff.c
@@ -41,6 +42,7 @@ include src/collision.h
4142
include src/boundary.h
4243
include src/gravity.h
4344
include src/server.h
45+
include src/frequency_analysis.h
4446
include src/tree.h
4547
include src/tree.c
4648
include src/tools.h
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export OPENGL=0# Set this to 1 to enable OpenGL
2+
export SERVER=0# Set this to 1 to enable the visualization web server
3+
include ../../src/Makefile.defs
4+
5+
# CCPROBLEM is defined in Makefile.defs to allow for
6+
# a compact cross platform Makefile
7+
.PHONY: all librebound
8+
all: problem.c librebound
9+
@echo "Compiling $< ..."
10+
$(CCPROBLEM)
11+
@echo ""
12+
@echo "Compilation successful. To run REBOUND, execute the file '$(EXEREBOUND)'."
13+
@echo ""
14+
15+
librebound:
16+
@echo "Compiling shared library $(LIBREBOUND) ..."
17+
$(MAKE) -C ../../src/
18+
@-$(RM) $(LIBREBOUND)
19+
@$(LINKORCOPYLIBREBOUND)
20+
@echo ""
21+
22+
clean:
23+
@echo "Cleaning up shared library $(LIBREBOUND) ..."
24+
$(MAKE) -C ../../src/ clean
25+
@echo "Cleaning up local directory ..."
26+
@-$(RM) $(LIBREBOUND)
27+
@-$(RM) $(EXEREBOUND)
28+
29+
rebound_webgl.html: problem.c
30+
@echo "Compiling problem.c with emscripten (WebGL enabled)..."
31+
emcc -O3 -I../../src/ ../../src/*.c problem.c -DSERVERHIDEWARNING -DOPENGL=1 -sSTACK_SIZE=655360 -s USE_GLFW=3 -s FULL_ES3=1 -sASYNCIFY -sALLOW_MEMORY_GROWTH -sSINGLE_FILE -sEXPORTED_RUNTIME_METHODS="callMain" --shell-file ../../web_client/shell_rebound_webgl.html -o rebound_webgl.html
32+
33+
rebound_console.html: problem.c
34+
@echo "Compiling problem.c with emscripten (WebGL disabled)..."
35+
emcc -O3 -I../../src/ ../../src/*.c problem.c -DSERVERHIDEWARNING -sSTACK_SIZE=655360 -sASYNCIFY -sALLOW_MEMORY_GROWTH -sSINGLE_FILE -sEXPORTED_RUNTIME_METHODS="callMain" --shell-file ../../web_client/shell_rebound_console.html -o rebound_console.html
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Frequency Analysis
3+
*
4+
* This example demonstrates how to use REBOUND's built-in
5+
* frequency analysis tools to perform a Modified Fourier
6+
* Transform or a Frequency Modified Fourier Transform.
7+
*/
8+
9+
#include "rebound.h"
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <math.h>
13+
#include <assert.h>
14+
15+
// Secular modes for Jupiter. Taken from Laskar (1990).
16+
double nu5[] = {4.2488163, 28.2206942, 3.0895148, 52.1925732, 27.0613982, 29.3799573, 28.8679427, 27.5734578, 5.4070444, 0.6671228}; // frequency, "/yr
17+
double A5[] = {44119.0e-6, 15750.0e-6, 1800.0e-6, 516.0e-6, 183.0e-6, 178.0e-6, 107.0e-6, 95.0e-6, 62.0e-6, 58.0e-6}; // amplitude
18+
double phi5[] = {30.676, 308.112, 121.362, 45.551, 218.696, 217.460, 32.614, 43.733, 116.984, 74.116}; // phase, deg
19+
20+
int main(int argc, char* argv[]) {
21+
// Check all 3 types of frequency analysis implemented
22+
for (enum REB_FREQUENCY_ANALYSIS_TYPE type=0;type<3;type++){
23+
// Create artificial test signal based on Laskar (1990) model.
24+
int Nsamples = 32768;
25+
int nfreq = 10;
26+
double* input = calloc(Nsamples*2, sizeof(double));
27+
double datasep = 120000.0/365.25*2.0*M_PI; // 120000 days in units of year/2pi
28+
for (int i=0; i<Nsamples; i++){
29+
for (int j=0; j<nfreq; j++){
30+
double nu = nu5[j]/1296000.0; // frequency in units of radians/(year/2pi)
31+
input[i*2+0] += A5[j]*cos(nu*i*datasep+phi5[j]/180.0*M_PI);
32+
input[i*2+1] += A5[j]*sin(nu*i*datasep+phi5[j]/180.0*M_PI);
33+
}
34+
}
35+
36+
// Perform the frequency analysis
37+
// Output format is: freq_0, amp_0, phase_0, freq_1, amp_1, phase_1, ...
38+
// Units of frequency are radians/datasep.
39+
// Units of phase are radians.
40+
double* output = malloc(sizeof(double)*3*nfreq);
41+
double minfreq = 60.0/1296000.0*datasep; // look for frequencies in the range -60"/year to +60"/year
42+
int ret = reb_frequency_analysis(output, nfreq,-minfreq,minfreq,type,input,Nsamples);
43+
if (ret){
44+
printf("An error occured during the frequency analysis.\n");
45+
}
46+
47+
48+
// Check accuracy
49+
double max_nu_error = 0.0;
50+
double max_A_error = 0.0;
51+
double max_phi_error = 0.0;
52+
for (int i=0; i<nfreq; i++){
53+
double nu_error = fabs(output[0*nfreq+i]*1296000.0/datasep-nu5[i]); // frequency error in "/year
54+
if (nu_error > max_nu_error) max_nu_error = nu_error;
55+
double A_error = fabs((output[1*nfreq+i]-A5[i])/A5[i]); // relative amplitude error
56+
if (A_error > max_A_error) max_A_error = A_error;
57+
double phi_error = output[2*nfreq+i]/M_PI*180.0 - phi5[i];
58+
if (phi_error<-180.0) phi_error+= 360.0;
59+
if (phi_error>180.0) phi_error-= 360.0;
60+
phi_error = fabs(phi_error);
61+
if (phi_error > max_phi_error) max_phi_error = phi_error;
62+
}
63+
printf("Flag %d\n", type);
64+
printf("Max frequency error: %e \"/year\n", max_nu_error);
65+
printf("Max relative amplitude error: %e\n", max_A_error);
66+
printf("Max phase error: %e deg\n", max_phi_error);
67+
switch (type){
68+
case REB_FREQUENCY_ANALYSIS_MFT:
69+
// Least accurate but fastest.
70+
assert(max_nu_error<3e-4);
71+
assert(max_A_error<2e-3);
72+
assert(max_phi_error<5e-1);
73+
break;
74+
case REB_FREQUENCY_ANALYSIS_FMFT:
75+
assert(max_nu_error<4e-6);
76+
assert(max_A_error<1e-5);
77+
assert(max_phi_error<6e-3);
78+
break;
79+
case REB_FREQUENCY_ANALYSIS_FMFT2:
80+
// Most accurate but slowest.
81+
assert(max_nu_error<2e-8);
82+
assert(max_A_error<3e-7);
83+
assert(max_phi_error<4e-5);
84+
break;
85+
}
86+
free(input);
87+
free(output);
88+
}
89+
}
90+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export OPENGL=0# Set this to 1 to enable OpenGL
2+
export SERVER=0# Set this to 1 to enable the visualization web server
3+
include ../../src/Makefile.defs
4+
5+
# CCPROBLEM is defined in Makefile.defs to allow for
6+
# a compact cross platform Makefile
7+
.PHONY: all librebound
8+
all: problem.c librebound
9+
@echo "Compiling $< ..."
10+
$(CCPROBLEM)
11+
@echo ""
12+
@echo "Compilation successful. To run REBOUND, execute the file '$(EXEREBOUND)'."
13+
@echo ""
14+
15+
librebound:
16+
@echo "Compiling shared library $(LIBREBOUND) ..."
17+
$(MAKE) -C ../../src/
18+
@-$(RM) $(LIBREBOUND)
19+
@$(LINKORCOPYLIBREBOUND)
20+
@echo ""
21+
22+
clean:
23+
@echo "Cleaning up shared library $(LIBREBOUND) ..."
24+
$(MAKE) -C ../../src/ clean
25+
@echo "Cleaning up local directory ..."
26+
@-$(RM) $(LIBREBOUND)
27+
@-$(RM) $(EXEREBOUND)
28+
29+
rebound_webgl.html: problem.c
30+
@echo "Compiling problem.c with emscripten (WebGL enabled)..."
31+
emcc -O3 -I../../src/ ../../src/*.c problem.c -DSERVERHIDEWARNING -DOPENGL=1 -sSTACK_SIZE=655360 -s USE_GLFW=3 -s FULL_ES3=1 -sASYNCIFY -sALLOW_MEMORY_GROWTH -sSINGLE_FILE -sEXPORTED_RUNTIME_METHODS="callMain" --shell-file ../../web_client/shell_rebound_webgl.html -o rebound_webgl.html
32+
33+
rebound_console.html: problem.c
34+
@echo "Compiling problem.c with emscripten (WebGL disabled)..."
35+
emcc -O3 -I../../src/ ../../src/*.c problem.c -DSERVERHIDEWARNING -sSTACK_SIZE=655360 -sASYNCIFY -sALLOW_MEMORY_GROWTH -sSINGLE_FILE -sEXPORTED_RUNTIME_METHODS="callMain" --shell-file ../../web_client/shell_rebound_console.html -o rebound_console.html
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Secular Frequencies
3+
*
4+
* This example integrates the outer Solar System and then performs a
5+
* frequency analysis using the Frequency Modified Fourier Transform
6+
* to determine the secular frequencies (g-modes).
7+
*/
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <math.h>
11+
#include "rebound.h"
12+
13+
double ss_pos[6][3] =
14+
{
15+
{-4.06428567034226e-3, -6.08813756435987e-3, -1.66162304225834e-6}, // Sun
16+
{+3.40546614227466e+0, +3.62978190075864e+0, +3.42386261766577e-2}, // Jupiter
17+
{+6.60801554403466e+0, +6.38084674585064e+0, -1.36145963724542e-1}, // Saturn
18+
{+1.11636331405597e+1, +1.60373479057256e+1, +3.61783279369958e-1}, // Uranus
19+
{-3.01777243405203e+1, +1.91155314998064e+0, -1.53887595621042e-1}, // Neptune
20+
{-2.13858977531573e+1, +3.20719104739886e+1, +2.49245689556096e+0} // Pluto
21+
};
22+
double ss_vel[6][3] =
23+
{
24+
{+6.69048890636161e-6, -6.33922479583593e-6, -3.13202145590767e-9}, // Sun
25+
{-5.59797969310664e-3, +5.51815399480116e-3, -2.66711392865591e-6}, // Jupiter
26+
{-4.17354020307064e-3, +3.99723751748116e-3, +1.67206320571441e-5}, // Saturn
27+
{-3.25884806151064e-3, +2.06438412905916e-3, -2.17699042180559e-5}, // Uranus
28+
{-2.17471785045538e-4, -3.11361111025884e-3, +3.58344705491441e-5}, // Neptune
29+
{-1.76936577252484e-3, -2.06720938381724e-3, +6.58091931493844e-4} // Pluto
30+
};
31+
32+
double ss_mass[6] =
33+
{
34+
1.00000597682, // Sun + inner planets
35+
1. / 1047.355, // Jupiter
36+
1. / 3501.6, // Saturn
37+
1. / 22869., // Uranus
38+
1. / 19314., // Neptune
39+
0.0 // Pluto
40+
};
41+
42+
int main(int argc, char* argv[]) {
43+
struct reb_simulation* r = reb_simulation_create();
44+
const double k = 0.01720209895; // Gaussian constant
45+
r->dt = 120; // Timestep is 120 days.
46+
r->G = k * k; // These are the same units as used by the mercury6 code.
47+
r->integrator = REB_INTEGRATOR_WHFAST;
48+
49+
// Initial conditions
50+
for (int i = 0; i < 6; i++) {
51+
struct reb_particle p = {0};
52+
p.x = ss_pos[i][0];
53+
p.y = ss_pos[i][1];
54+
p.z = ss_pos[i][2];
55+
p.vx = ss_vel[i][0];
56+
p.vy = ss_vel[i][1];
57+
p.vz = ss_vel[i][2];
58+
p.m = ss_mass[i];
59+
reb_simulation_add(r, p);
60+
}
61+
62+
reb_simulation_move_to_com(r);
63+
64+
int Nsamples = 2048; // Number of samples. Must be a power of two.
65+
// Choose a larger number for better accuracy, e.g. 32768.
66+
double* inp = malloc(sizeof(double)*2*Nsamples);
67+
// Start integration
68+
for (int i=0; i<Nsamples; i++){
69+
// Integrate for 1000 steps (120000 days)
70+
reb_simulation_steps(r, 1000);
71+
// Calculate orbital elements of Jupiter
72+
struct reb_orbit o = reb_orbit_from_particle(r->G, r->particles[1], r->particles[0]);
73+
// Store complex eccentricity in array
74+
inp[i*2+0] = o.e*cos(o.pomega);
75+
inp[i*2+1] = o.e*sin(o.pomega);
76+
}
77+
78+
// Perform frequency analysis
79+
int nfreq = 5;
80+
double datasep = 120000.0/365.25*2.0*M_PI; // sampling interval in units of year/2pi
81+
double minfreq = 60.0/1296000.0*datasep; // min/max frequenxy 60"/year
82+
double* out = malloc(sizeof(double)*3*nfreq);
83+
// The next command performs the actual Frequency Modified Fourier Transform (FMFT).
84+
// Other options are MFT (faster) and FMFT2 (more accurate).
85+
// See Sidlichovsky and Nesvorny (1996) for more details:
86+
// https://ui.adsabs.harvard.edu/abs/1996CeMDA..65..137S/abstract
87+
int error = reb_frequency_analysis(out, nfreq, -minfreq, minfreq, REB_FREQUENCY_ANALYSIS_FMFT, inp, Nsamples);
88+
if (error){
89+
printf("An error occured during the frequency analysis.\n");
90+
}
91+
92+
// Output the nfreq most dominate modes
93+
for (int i=0; i<nfreq; i++){
94+
double nu = out[0*nfreq+i]*1296000.0/datasep; // frequency in "/year
95+
double A = out[1*nfreq+i]; // amplitude error
96+
double phi = out[2*nfreq+i]/M_PI*180.0; // phase in deg
97+
printf("nu = %5.2f\"/yr A = %0.6f phi = %5.1f°\n", nu, A, phi);
98+
}
99+
100+
// Cleanup
101+
free(out);
102+
free(inp);
103+
reb_simulation_free(r);
104+
}
105+

ipython_examples/FrequencyAnalysis.ipynb

Lines changed: 254 additions & 0 deletions
Large diffs are not rendered by default.

mkdocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ nav:
7474
- ipython_examples/OrbitalElements.ipynb
7575
- c_examples/rotations.md
7676
- ipython_examples/Rotations.ipynb
77+
- ipython_examples/FrequencyAnalysis.ipynb
78+
- c_examples/secular_frequencies.md
79+
- c_examples/frequency_analysis.ipynb
7780
- "Small bodies":
7881
- c_examples/planetesimal_disk_migration.md
7982
- c_examples/solar_system_with_testparticles.md

rebound/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,6 @@ class ParticleNotFound(Exception):
9696
from .particle import Particle
9797
from .plotting import OrbitPlot, OrbitPlotSet
9898
from .simulationarchive import Simulationarchive
99+
from .frequency_analysis import frequency_analysis
99100

100101
__all__ = ["__libpath__", "__version__", "__build__", "__githash__", "Simulationarchive", "Simulation", "Orbit", "OrbitPlot", "OrbitPlotSet", "Particle", "GenericError", "Encounter", "Collision", "CollisionS", "Escape", "NoParticles", "ParticleNotFound", "Variation", "clibrebound", "mod2pi", "M_to_f", "E_to_f", "M_to_E", "ODE", "Rotation", "Vec3d", "spherical_to_xyz", "xyz_to_spherical"]

rebound/frequency_analysis.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ctypes
2+
3+
FREQUENCY_ANALYSIS_TYPES = {"mft": 0, "fmft": 1, "fmft2": 2}
4+
FREQUENCY_ANALYSIS_ERRORS = [
5+
(-1, "Frequency analysis error: minfreq must be smaller than maxfreq."),
6+
(-2, "Frequency analysis error: nfreq must be larger than 0."),
7+
(-3, "Frequency analysis error: ndata must be power of 2."),
8+
(-4, "Frequency analysis error: input array is NULL."),
9+
(-5, "Frequency analysis error: pointer to output array is NULL."),
10+
]
11+
12+
def frequency_analysis(inp, type=0, nfreq=10, minfreq=-1e-3, maxfreq=1e-3):
13+
"""Performs a frequency analysis on the timeseries data inp. Returns
14+
dominant modes (frequency, amplitude, phase).
15+
16+
Arguments
17+
---------
18+
inp: numpy.array
19+
Input data in the order x[0], y[0], x[1], y[1], ... where
20+
x and y are the real and imaginary components of the signal.
21+
type: string
22+
Determines the type of the frequency analysis:
23+
"mft" = Modified Fourier Transform. See Laskar (1988).
24+
https://ui.adsabs.harvard.edu/abs/1988A%26A...198..341L/abstract
25+
"fmft" = Frequency Modified Transform. See Sidlichovsky and Nesvorny (1996).
26+
https://ui.adsabs.harvard.edu/abs/1996CeMDA..65..137S/abstract
27+
"fmft2" = Frequency Modified Transform with additional corrections. Most
28+
accurate but also slowest. See Sidlichovsky and Nesvorny (1996).
29+
nfreq: Int
30+
The number of frequencies to find.
31+
minfreq: Float
32+
The minimum frequency to consider. Units are [radians/datasep] where
33+
datasep is the timeinterval between sampling points.
34+
maxfreq: Float
35+
The maximim frequency to consider. Units are [radians/datasep] where
36+
datasep is the timeinterval between sampling points.
37+
38+
Returns
39+
-------
40+
A list of lists, containing the frequencies, amplitudes, and phases of the
41+
nfreq most dominant modes.
42+
The output units for the frequencies are [radians/datasep] where datasep is
43+
the timeinterval between sampling points. The output units for the phases
44+
are radians.
45+
"""
46+
import numpy as np
47+
if not isinstance(inp, np.ndarray):
48+
raise ValueError("Input array must be a numpy array")
49+
if inp.dtype != np.float64:
50+
raise ValueError("Input array must be have datatype np.float64")
51+
52+
ndata = len(inp)//2
53+
inp_cont = np.ascontiguousarray(inp)
54+
inp_ptr = inp_cont.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
55+
56+
out = np.zeros(nfreq*3,dtype=np.double)
57+
out_ptr = out.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
58+
clibrebound.reb_frequency_analysis.restype = ctypes.c_int
59+
ret = clibrebound.reb_frequency_analysis(out_ptr, ctypes.c_int(nfreq), ctypes.c_double(minfreq), ctypes.c_double(maxfreq), ctypes.c_int(type), inp_ptr, ctypes.c_uint(ndata))
60+
for value, message in FREQUENCY_ANALYSIS_ERRORS:
61+
if ret & value:
62+
raise RuntimeError(message)
63+
return np.split(out,3)
64+
65+
from . import clibrebound

0 commit comments

Comments
 (0)