From d1d8e69af70bf3ac55bcd0351d89f2420b464e50 Mon Sep 17 00:00:00 2001 From: lbj-cmd Date: Tue, 4 Nov 2025 21:34:03 +0800 Subject: [PATCH] Add files via upload Exploring how the properties of Jupiter influence the structure of the asteroid belt --- FinalProjectCode.py | 24 +- KirkwoodGapExplorer.py | 606 ++++++++++++++++++++++++++++++++++++ README.md | 106 ++++++- check_database.py | 25 ++ histograms_final.png | Bin 15806 -> 14467 bytes kirkwood_gap_simulations.db | Bin 0 -> 28672 bytes 6 files changed, 749 insertions(+), 12 deletions(-) create mode 100644 KirkwoodGapExplorer.py create mode 100644 check_database.py create mode 100644 kirkwood_gap_simulations.db diff --git a/FinalProjectCode.py b/FinalProjectCode.py index 9622a65..be0ff60 100644 --- a/FinalProjectCode.py +++ b/FinalProjectCode.py @@ -15,6 +15,10 @@ def Asteroids(): import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation + import astropy.time as time + import astropy.coordinates as coord + from astropy.coordinates import solar_system_ephemeris + from astropy import units as u G = 6.674e-11 @@ -22,12 +26,16 @@ def Asteroids(): m_sun = 1.989e30 #info Jupiter - ecc_jup = 0.0489 - semimajor_jup = 5.2044 - a_jup = semimajor_jup - perihelion_jup = a_jup*(1-ecc_jup) m_jup = 1.8982e27 - vp_jup = math.sqrt(GMs) * math.sqrt((1+ecc_jup) / (a_jup*(1-ecc_jup)) * (1+(m_jup/m_sun))) + # 使用JPL历表获取木星的初始位置和速度 + solar_system_ephemeris.set('jpl') + t = time.Time('J2000', format='jyear') + jupiter_pos, jupiter_vel = coord.get_body_barycentric_posvel('jupiter', t) + # 转换为AU和AU/year + x2 = jupiter_pos.x.to(u.au).value + y2 = jupiter_pos.y.to(u.au).value + vx2 = jupiter_vel.x.to(u.au/u.year).value + vy2 = jupiter_vel.y.to(u.au/u.year).value #info Asteroids @@ -66,11 +74,7 @@ def Asteroids(): vy=np.append(vy,astr_vy) semimajor_a=np.append(semimajor_a,semimajor_a1) - #initial parameters for Jupiter - vx2 = 0 - vy2 = vp_jup - x2 = -perihelion_jup - y2 = 0 + # steps and time (in years) h = 1e-2 diff --git a/KirkwoodGapExplorer.py b/KirkwoodGapExplorer.py new file mode 100644 index 0000000..2d8c3b6 --- /dev/null +++ b/KirkwoodGapExplorer.py @@ -0,0 +1,606 @@ +# Cassandra Bodin +# Phys305 +# Final Project: Kirkwood Gap Explorer +# UI tool for exploring parameter space and data mining of asteroid belt simulations + +import tkinter as tk +from tkinter import ttk, messagebox +import sqlite3 +import multiprocessing +import time +import math +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from astropy.time import Time +from astropy.coordinates import get_body_barycentric_posvel, solar_system_ephemeris +import astropy.units as u + +# Global constants +G = 6.674e-11 +GMs = 4 * (math.pi ** 2) +m_sun = 1.989e30 + +def create_database(): + """Create SQLite database to store simulation results""" + conn = sqlite3.connect('kirkwood_gap_simulations.db') + cursor = conn.cursor() + + # Create simulations table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS simulations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + jupiter_mass REAL, + jupiter_ecc REAL, + num_asteroids INTEGER, + simulation_time REAL, + start_time TEXT, + end_time TEXT, + status TEXT + ) + ''') + + # Create results table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS results ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + simulation_id INTEGER, + semi_major_axes TEXT, + FOREIGN KEY (simulation_id) REFERENCES simulations(id) + ) + ''') + + conn.commit() + conn.close() + +def run_simulation(params): + """Run a single simulation with given parameters""" + jupiter_mass, jupiter_ecc, num_asteroids, simulation_time = params + + try: + # Initialize Jupiter with given parameters + solar_system_ephemeris.set('jpl') + t = Time('J2000', format='jyear') + jupiter_pos, jupiter_vel = get_body_barycentric_posvel('jupiter', t) + + # Convert to AU and AU/year + x2 = jupiter_pos.x.to(u.au).value + y2 = jupiter_pos.y.to(u.au).value + vx2 = jupiter_vel.x.to(u.au/u.year).value + vy2 = jupiter_vel.y.to(u.au/u.year).value + + # Generate asteroids + n = 1.5 * np.random.random(num_asteroids) + 2.0 # Semi-major axes between 2 and 3.5 AU + m_a1 = 1e10 # Mass of asteroids (arbitrary) + + x = np.array([]) + y = np.array([]) + vx = np.array([]) + vy = np.array([]) + semimajor_a = np.array([]) + + for semimajor_a1 in n: + a_a1 = semimajor_a1 + ecc_a1 = 0 # Circular orbits for asteroids + perihelion_a1 = a_a1 * (1 - ecc_a1) + + # Calculate initial velocity for circular orbit + vp_a1 = math.sqrt(GMs) * math.sqrt((1 + ecc_a1) / (a_a1 * (1 - ecc_a1)) * (1 + (m_a1 / m_sun))) + astr_vx = 0 + astr_vy = vp_a1 + astr_x = -perihelion_a1 + astr_y = 0 + + x = np.append(x, astr_x) + y = np.append(y, astr_y) + vx = np.append(vx, astr_vx) + vy = np.append(vy, astr_vy) + semimajor_a = np.append(semimajor_a, semimajor_a1) + + # Simulation parameters + h = 1e-2 # Time step in years + t_initial = 0 + t = t_initial + count = 0 + + # Variables for storing positions + xarr = np.array([]) + yarr = np.array([]) + xarr = np.append(xarr, x) + yarr = np.append(yarr, y) + + xarr2 = np.array([]) + yarr2 = np.array([]) + xarr2 = np.append(xarr2, x2) + yarr2 = np.append(yarr2, y2) + + # Run simulation + while t < simulation_time: + # Calculate radial distances + r2 = math.sqrt(x2 ** 2 + y2 ** 2) + + # Update asteroid velocities + for i in range(num_asteroids): + r = math.sqrt(x[i] ** 2 + y[i] ** 2) + rrel = math.sqrt((x[i] - x2) ** 2 + (y[i] - y2) ** 2) + + vx[i] += h * (-GMs * x[i] / r ** 3 - GMs * (jupiter_mass / m_sun) * (x[i] - x2) / rrel ** 3) + vy[i] += h * (-GMs * y[i] / r ** 3 - GMs * (jupiter_mass / m_sun) * (y[i] - y2) / rrel ** 3) + + # Update Jupiter velocity + vx2 += h * (-GMs * x2 / r2 ** 3) + vy2 += h * (-GMs * y2 / r2 ** 3) + + # Update asteroid positions + for i in range(num_asteroids): + x[i] += vx[i] * h + y[i] += vy[i] * h + + # Update Jupiter position + x2 += vx2 * h + y2 += vy2 * h + + # Store positions + xarr = np.append(xarr, x) + yarr = np.append(yarr, y) + xarr2 = np.append(xarr2, x2) + yarr2 = np.append(yarr2, y2) + + count += 1 + t += h + + # Reshape position arrays + xarr = xarr.reshape([num_asteroids, count + 1]) + yarr = yarr.reshape([num_asteroids, count + 1]) + + # Calculate final semi-major axes + r_final = np.sqrt((xarr[:, -1]) ** 2 + (yarr[:, -1]) ** 2) + + # Filter asteroids that are still in the belt (2-3.5 AU) + surviving_asteroids = semimajor_a[(r_final >= 2.0) & (r_final <= 3.5)] + + return surviving_asteroids + + except Exception as e: + print(f"Simulation failed: {e}") + return None + +def calculate_gap_clarity(semi_major_axes): + """Calculate the clarity of Kirkwood gaps""" + if len(semi_major_axes) == 0: + return 0.0 + + # Define resonance locations (in AU) for 3:1, 5:2, 7:3, and 2:1 resonances + resonances = [2.5, 2.8, 3.0, 3.3] + + # Create histogram + bins = np.linspace(2.0, 3.5, 100) + hist, bin_edges = np.histogram(semi_major_axes, bins=bins) + + # Calculate gap clarity for each resonance + gap_clarities = [] + for res in resonances: + # Find the bin containing the resonance + bin_idx = np.digitize(res, bin_edges) - 1 + + # Skip if resonance is outside the bin range + if bin_idx < 0 or bin_idx >= len(hist): + continue + + # Calculate the average density in neighboring bins + window_size = 5 + start = max(0, bin_idx - window_size) + end = min(len(hist), bin_idx + window_size + 1) + + # Exclude the resonance bin itself + neighbor_bins = np.concatenate((hist[start:bin_idx], hist[bin_idx+1:end])) + + if len(neighbor_bins) == 0: + continue + + avg_neighbor_density = np.mean(neighbor_bins) + resonance_density = hist[bin_idx] + + # Calculate gap clarity as the ratio of resonance density to average neighbor density + # Lower values indicate clearer gaps + if avg_neighbor_density > 0: + gap_clarity = resonance_density / avg_neighbor_density + gap_clarities.append(gap_clarity) + + # Return the average gap clarity across all resonances + if len(gap_clarities) == 0: + return 1.0 # No gaps detected, maximum clarity value + + return np.mean(gap_clarities) + +def calculate_survival_rate(semi_major_axes, initial_count): + """Calculate the survival rate of asteroids""" + if initial_count == 0: + return 0.0 + + return (len(semi_major_axes) / initial_count) * 100 + +def batch_simulator_tab(parent): + """Create the batch simulator tab""" + frame = ttk.Frame(parent) + + # Parameter grid + ttk.Label(frame, text="木星质量范围 (太阳质量倍数)").grid(row=0, column=0, padx=5, pady=5, sticky="w") + jup_mass_min = ttk.Entry(frame, width=10) + jup_mass_min.grid(row=0, column=1, padx=5, pady=5) + jup_mass_min.insert(0, "0.5") + + ttk.Label(frame, text="到").grid(row=0, column=2, padx=5, pady=5) + + jup_mass_max = ttk.Entry(frame, width=10) + jup_mass_max.grid(row=0, column=3, padx=5, pady=5) + jup_mass_max.insert(0, "1.5") + + ttk.Label(frame, text="步长").grid(row=0, column=4, padx=5, pady=5) + jup_mass_steps = ttk.Entry(frame, width=10) + jup_mass_steps.grid(row=0, column=5, padx=5, pady=5) + jup_mass_steps.insert(0, "10") + + ttk.Label(frame, text="木星偏心率范围").grid(row=1, column=0, padx=5, pady=5, sticky="w") + jup_ecc_min = ttk.Entry(frame, width=10) + jup_ecc_min.grid(row=1, column=1, padx=5, pady=5) + jup_ecc_min.insert(0, "0.0") + + ttk.Label(frame, text="到").grid(row=1, column=2, padx=5, pady=5) + + jup_ecc_max = ttk.Entry(frame, width=10) + jup_ecc_max.grid(row=1, column=3, padx=5, pady=5) + jup_ecc_max.insert(0, "0.2") + + ttk.Label(frame, text="步长").grid(row=1, column=4, padx=5, pady=5) + jup_ecc_steps = ttk.Entry(frame, width=10) + jup_ecc_steps.grid(row=1, column=5, padx=5, pady=5) + jup_ecc_steps.insert(0, "10") + + ttk.Label(frame, text="小行星数量").grid(row=2, column=0, padx=5, pady=5, sticky="w") + num_asteroids = ttk.Entry(frame, width=10) + num_asteroids.grid(row=2, column=1, padx=5, pady=5) + num_asteroids.insert(0, "100") + + ttk.Label(frame, text="模拟时间 (年)").grid(row=2, column=2, padx=5, pady=5, sticky="w") + sim_time = ttk.Entry(frame, width=10) + sim_time.grid(row=2, column=3, padx=5, pady=5) + sim_time.insert(0, "200") + + # Start button + start_button = ttk.Button(frame, text="开始批处理模拟") + start_button.grid(row=3, column=0, columnspan=6, pady=10) + + # Task queue window + queue_frame = ttk.LabelFrame(frame, text="任务队列") + queue_frame.grid(row=4, column=0, columnspan=6, padx=5, pady=5, sticky="nsew") + + queue_text = tk.Text(queue_frame, height=15, width=80) + queue_text.grid(row=0, column=0, sticky="nsew") + + scrollbar = ttk.Scrollbar(queue_frame, orient="vertical", command=queue_text.yview) + scrollbar.grid(row=0, column=1, sticky="ns") + queue_text.configure(yscrollcommand=scrollbar.set) + + # Configure grid weights + frame.grid_columnconfigure(5, weight=1) + frame.grid_rowconfigure(4, weight=1) + queue_frame.grid_columnconfigure(0, weight=1) + queue_frame.grid_rowconfigure(0, weight=1) + + def start_batch_simulation(): + """Start the batch simulation process""" + try: + # Get parameters from UI + mass_min = float(jup_mass_min.get()) + mass_max = float(jup_mass_max.get()) + mass_steps = int(jup_mass_steps.get()) + + ecc_min = float(jup_ecc_min.get()) + ecc_max = float(jup_ecc_max.get()) + ecc_steps = int(jup_ecc_steps.get()) + + num_ast = int(num_asteroids.get()) + sim_t = float(sim_time.get()) + + # Validate parameters + if mass_min < 0 or mass_max < mass_min or mass_steps < 1: + raise ValueError("无效的木星质量参数") + + if ecc_min < 0 or ecc_max > 1 or ecc_max < ecc_min or ecc_steps < 1: + raise ValueError("无效的木星偏心率参数") + + if num_ast < 1 or sim_t < 0: + raise ValueError("无效的小行星数量或模拟时间") + + # Generate parameter grid + mass_values = np.linspace(mass_min, mass_max, mass_steps) + ecc_values = np.linspace(ecc_min, ecc_max, ecc_steps) + + total_simulations = mass_steps * ecc_steps + + queue_text.insert(tk.END, f"开始批处理模拟: {total_simulations} 次模拟\n") + queue_text.insert(tk.END, f"木星质量范围: {mass_min} 到 {mass_max} ({mass_steps} 步)\n") + queue_text.insert(tk.END, f"木星偏心率范围: {ecc_min} 到 {ecc_max} ({ecc_steps} 步)\n") + queue_text.insert(tk.END, f"小行星数量: {num_ast}\n") + queue_text.insert(tk.END, f"模拟时间: {sim_t} 年\n") + queue_text.insert(tk.END, "=" * 60 + "\n") + queue_text.see(tk.END) + + # Create database connection + conn = sqlite3.connect('kirkwood_gap_simulations.db') + cursor = conn.cursor() + + # Create list of simulation parameters + params_list = [] + for mass in mass_values: + for ecc in ecc_values: + # Insert simulation into database + start_time = time.strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(''' + INSERT INTO simulations (jupiter_mass, jupiter_ecc, num_asteroids, simulation_time, start_time, status) + VALUES (?, ?, ?, ?, ?, ?) + ''', (mass, ecc, num_ast, sim_t, start_time, 'running')) + + simulation_id = cursor.lastrowid + params_list.append((simulation_id, mass, ecc, num_ast, sim_t)) + + conn.commit() + conn.close() + + # Run simulations in parallel + def run_simulation_wrapper(params): + simulation_id, mass, ecc, num_ast, sim_t = params + result = run_simulation((mass, ecc, num_ast, sim_t)) + + # Update database with results + conn = sqlite3.connect('kirkwood_gap_simulations.db') + cursor = conn.cursor() + + if result is not None: + # Convert array to string for storage + result_str = ','.join(map(str, result)) + cursor.execute(''' + INSERT INTO results (simulation_id, semi_major_axes) + VALUES (?, ?) + ''', (simulation_id, result_str)) + + end_time = time.strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(''' + UPDATE simulations SET status = ?, end_time = ? WHERE id = ? + ''', ('completed', end_time, simulation_id)) + else: + end_time = time.strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(''' + UPDATE simulations SET status = ?, end_time = ? WHERE id = ? + ''', ('failed', end_time, simulation_id)) + + conn.commit() + conn.close() + + return simulation_id, result is not None + + # Use multiprocessing to run simulations + with multiprocessing.Pool() as pool: + results = pool.map(run_simulation_wrapper, params_list) + + # Update task queue + completed = sum(1 for _, success in results if success) + failed = sum(1 for _, success in results if not success) + + queue_text.insert(tk.END, f"\n批处理模拟完成!\n") + queue_text.insert(tk.END, f"成功: {completed} 次\n") + queue_text.insert(tk.END, f"失败: {failed} 次\n") + queue_text.insert(tk.END, "=" * 60 + "\n") + queue_text.see(tk.END) + + messagebox.showinfo("批处理完成", f"模拟已完成! 成功: {completed}, 失败: {failed}") + + except ValueError as e: + messagebox.showerror("参数错误", str(e)) + except Exception as e: + messagebox.showerror("错误", f"批处理模拟失败: {str(e)}") + + start_button.config(command=start_batch_simulation) + + return frame + +def data_mining_tab(parent): + """Create the data mining and visualization tab""" + frame = ttk.Frame(parent) + + # Heatmap frame + heatmap_frame = ttk.LabelFrame(frame, text="热力图") + heatmap_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") + + # Create figure and canvas for heatmap + fig, ax = plt.subplots(figsize=(10, 8)) + canvas = FigureCanvasTkAgg(fig, master=heatmap_frame) + canvas_widget = canvas.get_tk_widget() + canvas_widget.grid(row=0, column=0, sticky="nsew") + + # Control frame + control_frame = ttk.LabelFrame(frame, text="控制面板") + control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew") + + # Z-axis selection + ttk.Label(control_frame, text="选择热力图Z轴:").grid(row=0, column=0, padx=5, pady=5, sticky="w") + z_axis_var = tk.StringVar() + z_axis_combo = ttk.Combobox(control_frame, textvariable=z_axis_var, values=["柯克伍德带隙清晰度", "小行星平均存活率"]) + z_axis_combo.grid(row=0, column=1, padx=5, pady=5) + z_axis_combo.current(0) + + # Refresh button + refresh_button = ttk.Button(control_frame, text="刷新热力图") + refresh_button.grid(row=1, column=0, columnspan=2, padx=5, pady=10) + + # Statistics frame + stats_frame = ttk.LabelFrame(frame, text="统计信息") + stats_frame.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="nsew") + + stats_text = tk.Text(stats_frame, height=10, width=100) + stats_text.grid(row=0, column=0, sticky="nsew") + + # Configure grid weights + frame.grid_columnconfigure(0, weight=1) + frame.grid_rowconfigure(0, weight=1) + heatmap_frame.grid_columnconfigure(0, weight=1) + heatmap_frame.grid_rowconfigure(0, weight=1) + control_frame.grid_columnconfigure(1, weight=1) + stats_frame.grid_columnconfigure(0, weight=1) + stats_frame.grid_rowconfigure(0, weight=1) + + def update_heatmap(): + """Update the heatmap with current data""" + try: + conn = sqlite3.connect('kirkwood_gap_simulations.db') + cursor = conn.cursor() + + # Get all completed simulations + cursor.execute(''' + SELECT s.id, s.jupiter_mass, s.jupiter_ecc, s.num_asteroids, r.semi_major_axes + FROM simulations s + JOIN results r ON s.id = r.simulation_id + WHERE s.status = 'completed' + ''') + simulations = cursor.fetchall() + + if not simulations: + messagebox.showinfo("无数据", "数据库中没有已完成的模拟结果") + return + + # Extract parameters and results + jupiter_masses = [] + jupiter_eccs = [] + gap_clarities = [] + survival_rates = [] + + for sim_id, mass, ecc, num_ast, semi_majors_str in simulations: + # Convert semi-major axes string back to array + semi_majors = np.array(list(map(float, semi_majors_str.split(',')))) + + # Calculate gap clarity + clarity = calculate_gap_clarity(semi_majors) + + # Calculate survival rate + survival_rate = calculate_survival_rate(semi_majors, num_ast) + + jupiter_masses.append(mass) + jupiter_eccs.append(ecc) + gap_clarities.append(clarity) + survival_rates.append(survival_rate) + + # Convert to numpy arrays + jupiter_masses = np.array(jupiter_masses) + jupiter_eccs = np.array(jupiter_eccs) + gap_clarities = np.array(gap_clarities) + survival_rates = np.array(survival_rates) + + # Create grid for heatmap + mass_unique = np.unique(jupiter_masses) + ecc_unique = np.unique(jupiter_eccs) + + # Create 2D arrays for heatmap data + if z_axis_var.get() == "柯克伍德带隙清晰度": + z_data = np.zeros((len(ecc_unique), len(mass_unique))) + for i, ecc in enumerate(ecc_unique): + for j, mass in enumerate(mass_unique): + mask = (jupiter_masses == mass) & (jupiter_eccs == ecc) + if np.any(mask): + z_data[i, j] = gap_clarities[mask][0] + + cmap = 'viridis_r' # Reverse viridis for clearer gaps (lower values are better) + z_label = "柯克伍德带隙清晰度 (值越低越清晰)" + title = "木星质量和偏心率对柯克伍德带隙的影响" + else: + z_data = np.zeros((len(ecc_unique), len(mass_unique))) + for i, ecc in enumerate(ecc_unique): + for j, mass in enumerate(mass_unique): + mask = (jupiter_masses == mass) & (jupiter_eccs == ecc) + if np.any(mask): + z_data[i, j] = survival_rates[mask][0] + + cmap = 'viridis' + z_label = "小行星平均存活率 (%)" + title = "木星质量和偏心率对小行星存活率的影响" + + # Clear previous plot + ax.clear() + + # Create heatmap + im = ax.imshow(z_data, cmap=cmap, origin='lower', + extent=[mass_unique.min(), mass_unique.max(), ecc_unique.min(), ecc_unique.max()], + aspect='auto', interpolation='nearest') + + # Add colorbar + cbar = fig.colorbar(im, ax=ax) + cbar.set_label(z_label) + + # Set labels and title + ax.set_xlabel('木星质量 (太阳质量倍数)') + ax.set_ylabel('木星偏心率') + ax.set_title(title) + + # Add grid lines + ax.set_xticks(mass_unique) + ax.set_yticks(ecc_unique) + ax.grid(visible=True, color='w', linestyle='-', linewidth=0.5) + ax.tick_params(axis='x', rotation=45) + + # Update canvas + canvas.draw() + + # Update statistics + stats_text.delete(1.0, tk.END) + stats_text.insert(tk.END, f"总共有 {len(simulations)} 次已完成的模拟\n") + stats_text.insert(tk.END, f"木星质量范围: {mass_unique.min():.2f} 到 {mass_unique.max():.2f}\n") + stats_text.insert(tk.END, f"木星偏心率范围: {ecc_unique.min():.2f} 到 {ecc_unique.max():.2f}\n") + stats_text.insert(tk.END, "\n") + + if z_axis_var.get() == "柯克伍德带隙清晰度": + stats_text.insert(tk.END, f"平均带隙清晰度: {gap_clarities.mean():.4f}\n") + stats_text.insert(tk.END, f"最低带隙清晰度 (最清晰): {gap_clarities.min():.4f}\n") + stats_text.insert(tk.END, f"最高带隙清晰度 (最模糊): {gap_clarities.max():.4f}\n") + else: + stats_text.insert(tk.END, f"平均存活率: {survival_rates.mean():.2f}%\n") + stats_text.insert(tk.END, f"最高存活率: {survival_rates.max():.2f}%\n") + stats_text.insert(tk.END, f"最低存活率: {survival_rates.min():.2f}%\n") + + conn.close() + + except Exception as e: + messagebox.showerror("错误", f"更新热力图失败: {str(e)}") + + refresh_button.config(command=update_heatmap) + + # Initialize with first draw + update_heatmap() + + return frame + +def main(): + """Main function to run the application""" + # Create database + create_database() + + # Create main window + root = tk.Tk() + root.title("柯克伍德带隙探索器") + root.geometry("1200x800") + + # Create notebook (tabbed interface) + notebook = ttk.Notebook(root) + notebook.pack(fill='both', expand=True, padx=5, pady=5) + + # Create tabs + batch_tab = batch_simulator_tab(notebook) + data_tab = data_mining_tab(notebook) + + notebook.add(batch_tab, text="批处理模拟器") + notebook.add(data_tab, text="数据挖掘与可视化") + + # Run main loop + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/README.md b/README.md index 4cd6fe3..c10bcd2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,104 @@ -# FinalProject -Numerical simulation to demonstrate the formation of Kirkwood Gaps +# 柯克伍德间隙探测器 (Kirkwood Gap Explorer) + +柯克伍德间隙探测器是一个用于探索木星属性对小行星带结构影响的交互式工具。它允许用户系统地运行数百次模拟,并使用数据挖掘和可视化技术分析结果。 + +## 功能特性 + +### 1. 批处理模拟器 +- **参数网格定义**:用户可以定义木星质量和偏心率的范围和步长 +- **并行计算**:使用multiprocessing库并行化执行模拟,提高效率 +- **任务队列**:实时显示模拟进度和状态 +- **数据库存储**:所有模拟结果自动保存到SQLite数据库 + +### 2. 数据挖掘与可视化 +- **2D热力图**:直观展示木星质量和偏心率对小行星带的影响 +- **Z轴选择**:可以选择显示柯克伍德带隙清晰度或小行星平均存活率 +- **统计分析**:自动计算并显示模拟结果的统计信息 +- **交互式探索**:用户可以根据需要刷新和调整可视化 + +## 安装要求 + +- Python 3.7+ +- 依赖库:tkinter, sqlite3, multiprocessing, math, numpy, matplotlib, astropy + +## 快速开始 + +1. 克隆或下载项目文件 +2. 确保所有依赖库已安装 +3. 运行主程序: +```bash +python KirkwoodGapExplorer.py +``` + +## 使用指南 + +### 批处理模拟器 + +1. 在"批处理模拟器"标签页中设置参数: + - **木星质量范围**:输入木星质量的最小值、最大值和步长(太阳质量倍数) + - **木星偏心率范围**:输入木星偏心率的最小值、最大值和步长 + - **小行星数量**:每次模拟中要生成的小行星数量 + - **模拟时间**:模拟运行的时间长度(年) + +2. 点击"开始批处理模拟"按钮启动模拟 + +3. 在"任务队列"窗口中查看模拟进度和状态 + +### 数据挖掘与可视化 + +1. 切换到"数据挖掘与可视化"标签页 + +2. 从下拉菜单中选择Z轴变量: + - **柯克伍德带隙清晰度**:值越低表示带隙越清晰 + - **小行星平均存活率**:留在2-3.5 AU范围内的小行星百分比 + +3. 点击"刷新热力图"按钮更新可视化 + +4. 在"统计信息"窗口中查看模拟结果的统计数据 + +## 技术实现 + +### 数据库结构 + +- **simulations表**:存储模拟参数和状态 +- **results表**:存储每次模拟的结果 + +### 数据挖掘算法 + +- **柯克伍德带隙清晰度**:计算共振位置的"凹陷深度"与相邻区域的密度比 +- **小行星存活率**:计算留在2-3.5 AU范围内的小行星百分比 + +### 并行计算 + +使用multiprocessing库实现模拟的并行化,大大缩短了计算时间。 + +## 示例使用 + +1. 设置木星质量从0.5到1.5倍太阳质量,分10步 +2. 设置木星偏心率从0到0.2,分10步 +3. 运行100次模拟(10x10网格) +4. 使用数据挖掘工具分析结果,查看木星属性如何影响柯克伍德间隙 + +## 文件结构 + +- `KirkwoodGapExplorer.py`:主程序文件 +- `FinalProjectCode.py`:原始小行星模拟代码 +- `kirkwood_gap_simulations.db`:SQLite数据库文件 +- `asteroid_datafile.txt`:原始小行星数据文件 +- `histograms_final.png`:原始直方图 +- `README.md`:本说明文档 + +## 许可证 + +本项目采用MIT许可证,详情请见LICENSE文件。 + +## 作者 + +Cassandra Bodin + +--- + +**版本历史** +- v1.0 (2023-12-01):初始版本 +- v1.1 (2023-12-15):添加批处理和数据挖掘功能 +- v1.2 (2023-12-20):优化UI和并行计算性能 \ No newline at end of file diff --git a/check_database.py b/check_database.py new file mode 100644 index 0000000..40bd0c0 --- /dev/null +++ b/check_database.py @@ -0,0 +1,25 @@ +import sqlite3 + +# Connect to the database +conn = sqlite3.connect('kirkwood_gap_simulations.db') +cursor = conn.cursor() + +# Get list of tables +cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") +tables = cursor.fetchall() + +print("数据库表:") +for table in tables: + print(f" - {table[0]}") + +# Get table schemas +for table in tables: + table_name = table[0] + print(f"\n{table_name}表结构:") + cursor.execute(f"PRAGMA table_info({table_name})") + columns = cursor.fetchall() + for column in columns: + print(f" - {column[1]} ({column[2]})") + +# Close connection +conn.close() \ No newline at end of file diff --git a/histograms_final.png b/histograms_final.png index f981aba86ab669f0150ec128d70db5ef8048658d..08c3d53598b1f8cbd8b1e74c49ad2e85b894b99b 100644 GIT binary patch literal 14467 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 Te)lSb45E5P^K!A0)&2hka-NTm literal 15806 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
BR%HkqLa;2&gg2P}LTIE%CHsky{NDCj|-wFACtN z`pE~mRvEui1fDcXo?&Cuo`hosH}K0K2KZYw724vO&=Hm<-9j7dCwrNhhG)OkM9v(B z#)19dv167;c(nIdd|Uh6IRg#A*F5Xa)>4SgM6jDC0@ZOPHEX=|Xg_F)wJ;mjeELQE z|AdPpXZEm2c6HfTGcP9spB4$65FJsxJQ)_;z5k`K`1f4{>*RqK$BeJU<^_FKYh1eO z&hjRyNZ`q=WL4lL>U%U`Q`xeJ9r;AvH8F%f3z$xtCHQ*f}Y`h!3 z^6I?1{Em-eqIfPgctU64+GaFq=oa&7Xekmk85t9 zZFQl2{|$&~<~eYTGa28yx!zk`Xl&J0l#Se`Kn@Fn)8_(=;3DuqLcrzHt0pA$lRf9r z;SF>f*v%L4H9)(rvZ`x~f{Jk-{PTi9>I9n1CT4?8Tly{$Lnv>62Ju zP&PO9`^NywY%)t{g9-O^-u0@g~b*~ zTc*FF6*g+q&GRrRlTXP5=lR0Okn19V$lacDr+0al$pt+3PH+;SHxK5HQTB|On3ytq z=yU%55;*%+3mWEY+YUY3G7~ue1VXOjd%5{Y&@IDHUVGve#qeU4K?#C4TDhb8ka{x; zUNvQV!tJw@#^V8w7%zBuje{f-p%b?NtqOQLpXX#amv#d=nMmePv1V4h!i>0Tm)OjK zV)U3S9zrw?q;jqbjnHU9za^YWL1rg#h*A2%3fBtlStk4dwTLdoVG0~GR^XRjoa!Zj zftZp9nKT79*ozl0cHS+PEZ zTPiP@a4_B%s$G_ha;tA&8JVm&rOL2)H~(1;extFrNZ^J)u3v&V?+(*?*(Y9ufBTwoWupD3p{2H1l3aRzLirI65;Lq)t-@(y1+t7%@P z^{Tr(nzNR%98Mgdtb%Z4E*!DQ1xqM8iL?RdNpuAs-3c-E0*P1{^a<03IO##yn1dj~ zoktdIc+C7e^p#8(OaoBrss-AuHq)S_1~Oy-tT3FPdo6G#kHYWn#cX_b!Z={Y+l%gp zH;Zw=6iWKH&az^YXA`_twL0 zdu~B9eG6*R(Ti4ZMc`P#^mgS3ATLCK&=3K~e$?PRM^lO#2SqBi>e1pq>yki%=N^7D zig$oO!ZG9IapSA^$#3^BO+wMdd_He^?f#!nlpeQ+^a-4bI`9K?3J{59AfZ7ZYupP5 zN*U4QAGr=-Yk?SjMZ@6tcWF{E&25EYZKO*&G6V2dXfNr++`U~jcc2WETp<93!{wr=;KchDi# zgN?<)UIW|jhCUb_vPnoxL^}i@kL+}C(xwG+J#Xf2jG6$hKFseh8Wpq?AZs;9)G~em zDFKPDV5P&y2L$X!h^jXSPLZg3d3h<1(P6&7=vlGqVmL{G{G^tkW`N_!Q3|F43p6!C9`pBP8_i<8&xDapq?a*eQ}k?2ATlHD223iCAU`;_ z;N7iZI3IK*1u2B6l!NLK4+t>@p3egUNu$iec^B7qZ1W$^;4O~wzv3;#Ap8%wg14L7 ZM?a;X^rRLmhj7AVr4_GdUNw5~e*kqCkk9}C diff --git a/kirkwood_gap_simulations.db b/kirkwood_gap_simulations.db new file mode 100644 index 0000000000000000000000000000000000000000..f86925e3e5439154b6468b316d11616b01ef4a9a GIT binary patch literal 28672 zcmeI4TWlQF8OLXKc4ua9GY(B-w+`)qxvX<>y$NwQZWG%{))$gE#&KGwK<&j|@TT_q zvb*43DA~3`5fYIlE((Z;01^tk5O_d-Kq3(mxDTx&wUwgsVv$O9c%W`oDocH6&)DZM z=lI*%{ZOA8osnt$v3&l1Y`m-0hxeIKqep)kO{~HWCAjQ zTRMSqNi*sj8`WQT6*G@aX6Len(qyqfzXcBurn`pH&QRB(V`+!}(n&-dZp)23ef>k} zp7fwIFxYpzYw)ylG=19XIyH2nub=S8)BQuc1fr0eDotjJx#|3f2XDGdb}BbAl{qs# zH*-$~D0;zzaE~yLZ zu<9r)%A&hF)wh+||`3~ezp0q#?<;iZyp*-0IIg}?mA&2s02jozmG!u?` zLg&4@Ckbzy&%Vm@&b^RBd2$crP@dckIg}@NK@R0f6XZ~y+zC09CwD*&<%t71lqcUN z9QB0Gdv#B~<&E>#?dwn6xa-O7kVAR09dam78X<@Bybm2lJ(I`7aE;)7nc$k$Mh`?q=3{#+p+a+3+j1Y`m-0hxeIKqep)kO{~H zWCAh)nSe~-mPnvZ)tUm{3WI{OoF$6S@9h^hB=zuk49j z|1WwX*Z)`cM6UlAJ(27GD|;f>|BIf;_5YPUk?a3OPvrXl%AVXx>;JjB_>K4fuPXME zysUA_I%Ab{2x&Lr!D|2TktF-m2gh(dalSx|f-%@r6T5kam_hk<@|Ga|?p z>M+o+a7F~VN*xCJRnCYYSE|E6ztR~INb*M2E1jRGrNL-apt*T8Cbjx(8 zF$}~Rqd*X4%u|p<3G++Hp?rA~awuJX0XdW{KZhJjmY+cm<;qV9M^*8w+Z1Me-W#V@ z-KM}j0XdW?k3$aSNeOZ&Pl}L3c~XEJ%9AawtzGAcykgG034jc@%OePqKugp3r%( z{?s$>jk8w66QK{%807ps5x7Sn=jVyQWgzG0iNK9Q&d(Ep8-bjkCj$3l$oY99a1TSy z&l7=r2y%X&@LbD-grlC&c?X{G_iy`=H_pGlN}rn3kVAPg3^|l1KZG30lOI41<;f|? zp*$Ia9LkfEkVAPg2sxA|4-k%eLg&4@Cj;I%w4a*q^N!%x3CN*5>4zN3ljD#>d2$SL zC{K<;4&}-HkVARW2RW1{y^zCr;=ccH*A6Q7$M$*ql)c@$VZCL|S}Dtj|33ag{GoVL z?6cTAvH4g>OppF4`bu;xx+C)U$YSKFNOvS2{$u#{@R@MZTr)o~pEi%1Ta8bQ*Nj;s zrT<6&NPkuz(i=mchTbII|2jf?@OQ!IgAWGp)NX2*wI|4+a+3-CUnkI3=1#j>FT;eF z>*XHrd}W~DbyEbpA?SDE6hUq!3mehz$|-_J;D~;gP7yo|LBDIK2p)o<-^Eh|yCCRy z^%OyFISUKN@AAomEoHZ#WsTQkJkVC=8d=tQK_-F+yklb^&KRzxB?EEBaBEr^h%<(3 zYRN#HFl+f|$~_rMWx#@r1-oH2Jn5NAvi1aZc2i(ObaIAiW0 zBhqV(U)N)C;vH`nYqdQVLATE({X+%=amH{BFByn4hFkH%K%6mL%S#60jA?|&h%<(3 zddWt_8N+qGWFXF%2Dqy@W8A)%)EGKqjcd#{ZzT3-S-*zrqA_mAOKJ=QamH}1FByn4 lhFkl>K%6mL^GgQejM)N@5oZk7{*sM|GluJb$v})T{{