Output Files¶
The resonances package generates several output files during simulation to help you analyze and understand the resonant behavior of celestial bodies. All files are saved in the simulation directory specified by the save_path
configuration parameter (typically in cache/
directory).
This section provides a comprehensive guide to understanding all output files generated by the package.
Directory Structure¶
When you run a simulation, the package creates a directory with the following structure:
cache/
└── simulation_name/
├── data-{body}-{resonance}.csv # Main simulation data
├── data-{body}-{resonance}-periodogram-axis.csv # Semi-major axis periodogram
├── data-{body}-{resonance}-periodogram-angle.csv # Resonant angle periodogram
├── {body}_{resonance}.png # Visualization plots
├── summary.csv # Summary of all resonances
└── simulation.cfg # Configuration details
Where:
{body}
is the name or ID of the celestial body (e.g., "759", "Pluto"){resonance}
is the resonance identifier (e.g., "4J-2S-1", "nu6_Saturn")
Main Data File: data-{body}-{resonance}.csv
¶
This is the primary output file containing the time evolution of orbital elements and resonant angles.
File Structure¶
The file is generated in resonances/simulation/data_manager.py:53-66
and contains the following columns:
Column | Description | Units/Range |
---|---|---|
times |
Integration time | Years (converted from radians) |
angle |
Resonant angle | Radians |
a |
Semi-major axis | AU |
e |
Eccentricity | 0-1 |
inc |
Inclination | Radians |
Omega |
Longitude of ascending node | Radians |
omega |
Argument of perihelion | Radians |
M |
Mean anomaly | Radians |
longitude |
Mean longitude | Radians |
varpi |
Longitude of perihelion | Radians |
a_filtered |
Filtered semi-major axis | AU (if filtering is enabled) |
angle_filtered |
Filtered resonant angle | Radians (if filtering is enabled) |
The data is generated from Body.mmr_to_dict()
(resonances/body.py:81-105) for mean motion resonances and Body.secular_to_dict()
(resonances/body.py:107-134) for secular resonances.
# Example: Reading and analyzing the main data file
import pandas as pd
import matplotlib.pyplot as plt
# Load the data
df = pd.read_csv('cache/tests/2025-07-08_14:21:45/data-759-nu6_Saturn.csv')
# Display first few rows
print("Sample data:")
print(df.head())
# Plot resonant angle evolution
plt.figure(figsize=(10, 4))
plt.plot(df['times'], df['angle'], 'b-', alpha=0.5, label='Original')
if 'angle_filtered' in df.columns:
plt.plot(df['times'], df['angle_filtered'], 'r-', label='Filtered')
plt.xlabel('Time (years)')
plt.ylabel('Resonant Angle (rad)')
plt.legend()
plt.title('Resonant Angle Evolution')
plt.show()
Periodogram Files¶
The package generates two periodogram files for frequency analysis:
1. Semi-major Axis Periodogram: data-{body}-{resonance}-periodogram-axis.csv
¶
Generated in resonances/simulation/data_manager.py:87-95
, this file contains the frequency analysis of the semi-major axis variations.
2. Resonant Angle Periodogram: data-{body}-{resonance}-periodogram-angle.csv
¶
Generated in resonances/simulation/data_manager.py:77-85
, this file contains the frequency analysis of the resonant angle.
File Structure (both periodogram files)¶
Column | Description | Units |
---|---|---|
frequency |
Frequency of oscillation | 1/years |
power |
Power spectral density | Normalized (0-1) |
period |
Period of oscillation | Years (= 1/frequency) |
These files are crucial for identifying libration periods and determining resonant status.
# Example: Analyzing periodogram data
import pandas as pd
import matplotlib.pyplot as plt
# Load periodogram data
df_axis = pd.read_csv('cache/tests/2025-07-08_14:21:45/data-759-nu6_Saturn-periodogram-axis.csv')
df_angle = pd.read_csv('cache/tests/2025-07-08_14:21:45/data-759-nu6_Saturn-periodogram-angle.csv')
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
# Plot semi-major axis periodogram
ax1.plot(df_axis['period'], df_axis['power'])
ax1.set_xlabel('Period (years)')
ax1.set_ylabel('Power')
ax1.set_title('Semi-major Axis Periodogram')
ax1.set_xscale('log')
# Plot resonant angle periodogram
ax2.plot(df_angle['period'], df_angle['power'])
ax2.set_xlabel('Period (years)')
ax2.set_ylabel('Power')
ax2.set_title('Resonant Angle Periodogram')
ax2.set_xscale('log')
plt.tight_layout()
plt.show()
# Find dominant periods
max_power_axis = df_axis.loc[df_axis['power'].idxmax()]
max_power_angle = df_angle.loc[df_angle['power'].idxmax()]
print(f"Dominant period in semi-major axis: {max_power_axis['period']:.1f} years")
print(f"Dominant period in resonant angle: {max_power_angle['period']:.1f} years")
Summary File: summary.csv
¶
The summary file provides a comprehensive overview of all resonances analyzed in the simulation. Generated by DataManager.get_simulation_summary()
(resonances/simulation/data_manager.py:113-217).
File Structure¶
Column | Description | Values/Range |
---|---|---|
name |
Body identifier | String/number |
resonance |
Resonance code | e.g., "4J-2S-1", "nu6_Saturn" |
type |
Resonance type | "MMR" or "Secular" |
status |
Resonant status | -2 to 2 (see below) |
pure |
Pure libration flag | True/False |
num_libration_periods |
Number of complete librations | Integer |
max_libration_length |
Longest libration period | Years |
monotony |
Monotonicity measure | 0-1 (1 = perfectly monotonic) |
overlapping |
Overlapping frequency ranges | "(start, end)" pairs |
a |
Initial semi-major axis | AU |
e |
Initial eccentricity | 0-1 |
inc |
Initial inclination | Radians |
Omega |
Initial ascending node | Radians |
omega |
Initial perihelion argument | Radians |
M |
Initial mean anomaly | Radians |
Status Codes¶
2
: Pure resonance (libration only)1
: Transient resonance (libration + circulation)0
: Non-resonant-1
: Controversial, possibly transient-2
: Controversial, possibly pure
See the Libration section for detailed explanations of status determination.
# Example: Analyzing the summary file
import pandas as pd
# Load summary
df_summary = pd.read_csv('cache/tests/2025-07-08_14:21:45/summary.csv')
# Filter for resonant bodies only
resonant = df_summary[df_summary['status'] != 0]
print("Resonant bodies found:")
for _, row in resonant.iterrows():
status_type = "Pure" if abs(row['status']) == 2 else "Transient"
print(f" {row['name']}: {row['resonance']} ({status_type})")
print(f" - {row['num_libration_periods']} libration periods")
print(f" - Max period: {row['max_libration_length']:.1f} years")
print(f" - Monotony: {row['monotony']:.3f}")
if row['overlapping']:
print(f" - Overlapping peaks: {row['overlapping']}")
print()
Configuration File: simulation.cfg
¶
The configuration file preserves all simulation parameters for reproducibility. Generated by DataManager.save_configuration_details()
(resonances/simulation/data_manager.py:219-241).
File Structure¶
The file contains three main sections:
1. General Information¶
Name
: Simulation identifierDate
: Epoch date for initial conditionsSource
: Data source (e.g., "nasa", "astdys")Number of bodies
: Total bodies simulated
2. Integration Parameters¶
Tmax
: Maximum integration time (years × 2π)Integrator
: Integration method (e.g., "whfast", "ias15")dt
: Time step (years)
3. Analysis Parameters¶
Cutoff
: Frequency cutoff for filteringFilter order
: Butterworth filter orderFrequency min/max
: Periodogram frequency rangeCritical
: Critical threshold for resonance detectionSoft
: Soft threshold for candidate detectionPeriod critical
: Critical period for libration analysis
This file ensures complete reproducibility of your simulations.
# Example: Reading configuration file
config_file = 'cache/tests/2025-07-08_14:21:45/simulation.cfg'
print("Simulation Configuration:")
print("="*40)
with open(config_file, 'r') as f:
for line in f:
if line.strip() and not line.startswith('='):
print(line.rstrip())
Visualization Files: {body}_{resonance}.png
¶
The package generates comprehensive visualization plots showing:
- Resonant angle evolution - Time series of the resonant angle
- Filtered data - Smoothed resonant angle and semi-major axis
- Periodograms - Frequency analysis with critical thresholds
- Eccentricity evolution - Changes in orbital eccentricity
These plots include:
- Gray dashed lines: Frequency peaks (start and end of each peak)
- Green horizontal line: Soft threshold (possible resonance)
- Red horizontal line: Critical threshold (confirmed resonance)
- Crosses: Peak values in periodograms
Generated by the plotting module when plot
configuration is enabled.
Controlling Output Generation¶
You can control which files are generated using configuration parameters:
import resonances
sim = resonances.Simulation(
name="my_simulation",
save='all', # Save all data files
plot='resonant', # Plot only resonant cases
save_summary=True, # Generate summary.csv
save_path='cache/my_results' # Custom output directory
)
Save Options¶
'all'
: Save data for all bodies and resonances'resonant'
: Save only confirmed resonant cases (status > 0)'nonzero'
: Save resonant and controversial cases (status ≠ 0)'candidates'
: Save only controversial cases (status < 0)None
: Don't save data files
Plot Options¶
'all'
: Plot all resonances'resonant'
: Plot only resonant cases'nonzero'
: Plot resonant and controversial cases'candidates'
: Plot only controversial cases'show'
: Display plots in notebook instead of saving'both'
: Both save and display plotsFalse
: Don't generate plots
Working with Output Files¶
Here's a complete example of loading and analyzing all output files from a simulation:
import pandas as pd
import numpy as np
from pathlib import Path
def analyze_simulation_output(sim_path):
"""Analyze all output files from a simulation."""
sim_path = Path(sim_path)
# Load summary
summary = pd.read_csv(sim_path / 'summary.csv')
print(f"Simulation: {sim_path.name}")
print(f"Total bodies analyzed: {summary['name'].nunique()}")
print(f"Total resonances checked: {len(summary)}")
print(f"Resonant cases found: {(summary['status'] > 0).sum()}")
print()
# Analyze each resonant case
resonant_cases = summary[summary['status'] != 0]
for _, row in resonant_cases.iterrows():
body = row['name']
res = row['resonance']
# Load main data
data_file = sim_path / f'data-{body}-{res}.csv'
if data_file.exists():
df = pd.read_csv(data_file)
# Calculate libration amplitude
if 'angle' in df.columns:
amplitude = df['angle'].max() - df['angle'].min()
print(f"{body} in {res}:")
print(f" - Status: {'Pure' if row['pure'] else 'Transient'}")
print(f" - Libration amplitude: {np.degrees(amplitude):.1f}°")
print(f" - Number of periods: {row['num_libration_periods']}")
# Check for filtering
if 'angle_filtered' in df.columns:
print(f" - Filtering applied: Yes")
# Load periodogram if exists
period_file = sim_path / f'data-{body}-{res}-periodogram-angle.csv'
if period_file.exists():
df_period = pd.read_csv(period_file)
max_idx = df_period['power'].idxmax()
print(f" - Dominant period: {df_period.loc[max_idx, 'period']:.1f} years")
print(f" - Peak power: {df_period.loc[max_idx, 'power']:.3f}")
print()
# Example usage
analyze_simulation_output('cache/tests/2025-07-08_14:21:45')
References to Source Code¶
- Main data saving:
resonances/simulation/data_manager.py:41-52
- Body data export:
resonances/body.py:81-134
- Periodogram saving:
resonances/simulation/data_manager.py:75-95
- Summary generation:
resonances/simulation/data_manager.py:113-217
- Configuration saving:
resonances/simulation/data_manager.py:219-241
- Plot generation:
resonances/resonance/plot.py