Mean-Motion Resonances
  • Home

Getting Started

  • Quick Start
  • Installation and updates
  • Examples

Features

  • Simulations, Bodies, MMRs
  • Libration module
  • Resonance Matrices
  • Output Files
    • Directory Structure
    • Main Data File:
      • File Structure
    • Periodogram Files
      • 1. Semi-major Axis Periodogram:
      • 2. Resonant Angle Periodogram:
      • File Structure (both periodogram files)
    • Summary File:
      • File Structure
      • Status Codes
    • Configuration File:
      • File Structure
        • 1. General Information
        • 2. Integration Parameters
        • 3. Analysis Parameters
    • Visualization Files:
    • Controlling Output Generation
      • Save Options
      • Plot Options
    • Working with Output Files
    • References to Source Code
  • Config

About

  • Copyright, license, references
Mean-Motion Resonances
  • »
  • Features »
  • Output Files
  • Edit on GitHub

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.

In [ ]:
Copied!
# 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()
# 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.

In [ ]:
Copied!
# 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")
# 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.

In [ ]:
Copied!
# 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()
# 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 identifier
  • Date: Epoch date for initial conditions
  • Source: 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 filtering
  • Filter order: Butterworth filter order
  • Frequency min/max: Periodogram frequency range
  • Critical: Critical threshold for resonance detection
  • Soft: Soft threshold for candidate detection
  • Period critical: Critical period for libration analysis

This file ensures complete reproducibility of your simulations.

In [ ]:
Copied!
# 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())
# 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:

  1. Resonant angle evolution - Time series of the resonant angle
  2. Filtered data - Smoothed resonant angle and semi-major axis
  3. Periodograms - Frequency analysis with critical thresholds
  4. 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 plots
  • False: Don't generate plots

Working with Output Files¶

Here's a complete example of loading and analyzing all output files from a simulation:

In [ ]:
Copied!
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')
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
Previous Next

Built with MkDocs using a theme provided by Read the Docs.
GitHub « Previous Next »