Any suggestions, on how to improve the following code?
Please reply with the full code of your improved app.

```import tkinter as tk
from tkinter import ttk, messagebox
import numpy as np
from scipy import special
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from math import log, exp
import threading

class CondorcetSimulation:
    def __init__(self, root):
        """Initialize the Condorcet Simulation GUI."""
        self.root = root
        self.root.title("Condorcet's Jury Theorem Simulator")
        self.root.geometry("900x900")  # Increased height to accommodate new widgets
        self.root.minsize(900, 900)
        
        # Configure grid layout
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        
        # Initialize variables
        self.n_voters = tk.IntVar(value=5)
        self.p = tk.DoubleVar(value=0.6)
        self.advanced_mode = tk.BooleanVar(value=False)
        self.simulation_runs = tk.IntVar(value=1000)  # For Advanced Mode
        self.p_min_threshold = tk.DoubleVar(value=0.3)  # Lower threshold for Advanced Mode
        self.p_max_threshold = tk.DoubleVar(value=0.7)  # Upper threshold for Advanced Mode
        
        # Create main frame
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
        
        # Configure grid weights
        self.main_frame.columnconfigure(1, weight=1)
        self.main_frame.rowconfigure(10, weight=1)
        
        # Create UI components
        self.create_widgets()
        
        # Placeholder for the plot canvas
        self.plot_canvas = None
        
        # Handle window close event
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        # Lock for threading
        self.lock = threading.Lock()

    def create_widgets(self):
        """Create and arrange widgets in the main frame."""
        
        # Number of Voters
        voters_label = ttk.Label(self.main_frame, text="Number of Voters (n):")
        voters_label.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        
        voters_spinbox = ttk.Spinbox(
            self.main_frame, from_=2, to=1000, textvariable=self.n_voters, width=18
        )
        voters_spinbox.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        voters_spinbox.bind("<FocusOut>", lambda e: self.validate_n())
        
        # Advanced Mode Checkbox
        advanced_checkbox = ttk.Checkbutton(
            self.main_frame, text="Advanced Mode: Random Voter Accuracies",
            variable=self.advanced_mode, command=self.toggle_mode
        )
        advanced_checkbox.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky=tk.W)
        
        # Voter Accuracy (p) Slider (Basic Mode)
        self.p_label = ttk.Label(self.main_frame, text="Voter Accuracy (p):")
        self.p_label.grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
        
        self.p_scale = ttk.Scale(
            self.main_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
            variable=self.p, command=self.update_p_label
        )
        self.p_scale.grid(row=2, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
        
        self.p_display = ttk.Label(self.main_frame, text=f"{self.p.get():.2f}")
        self.p_display.grid(row=2, column=2, padx=5, pady=5, sticky=tk.W)
        
        # Simulation Runs (Advanced Mode)
        self.sim_runs_label = ttk.Label(self.main_frame, text="Simulation Runs:")
        self.sim_runs_spinbox = ttk.Spinbox(
            self.main_frame, from_=1000, to=1000000, increment=1000,
            textvariable=self.simulation_runs, width=18
        )
        self.sim_runs_label.grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
        self.sim_runs_spinbox.grid(row=3, column=1, padx=5, pady=5, sticky=tk.W)
        
        # Lower Threshold for Voter Accuracy (Advanced Mode)
        self.min_threshold_label = ttk.Label(self.main_frame, text="Voter Accuracy Lower Threshold (p_min):")
        self.min_threshold_scale = ttk.Scale(
            self.main_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
            variable=self.p_min_threshold, command=self.update_min_threshold_label
        )
        self.min_threshold_display = ttk.Label(self.main_frame, text=f"{self.p_min_threshold.get():.2f}")
        
        self.min_threshold_label.grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
        self.min_threshold_scale.grid(row=4, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
        self.min_threshold_display.grid(row=4, column=2, padx=5, pady=5, sticky=tk.W)
        
        # Upper Threshold for Voter Accuracy (Advanced Mode)
        self.max_threshold_label = ttk.Label(self.main_frame, text="Voter Accuracy Upper Threshold (p_max):")
        self.max_threshold_scale = ttk.Scale(
            self.main_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
            variable=self.p_max_threshold, command=self.update_max_threshold_label
        )
        self.max_threshold_display = ttk.Label(self.main_frame, text=f"{self.p_max_threshold.get():.2f}")
        
        self.max_threshold_label.grid(row=5, column=0, padx=5, pady=5, sticky=tk.W)
        self.max_threshold_scale.grid(row=5, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
        self.max_threshold_display.grid(row=5, column=2, padx=5, pady=5, sticky=tk.W)
        
        # Initially hide simulation runs and thresholds
        self.sim_runs_label.grid_remove()
        self.sim_runs_spinbox.grid_remove()
        self.min_threshold_label.grid_remove()
        self.min_threshold_scale.grid_remove()
        self.min_threshold_display.grid_remove()
        self.max_threshold_label.grid_remove()
        self.max_threshold_scale.grid_remove()
        self.max_threshold_display.grid_remove()
        
        # Run Simulation Button
        self.run_button = ttk.Button(
            self.main_frame, text="Run Simulation", command=self.run_simulation
        )
        self.run_button.grid(row=6, column=0, columnspan=3, pady=10)
        
        # Results Label
        self.result_label = ttk.Label(
            self.main_frame, text="Probability Majority is Correct:",
            font=("Arial", 10, "bold")
        )
        self.result_label.grid(row=7, column=0, columnspan=3, pady=(10, 5), sticky=tk.W)
        
        self.result_value = ttk.Label(
            self.main_frame, text="", foreground="blue", font=("Arial", 10)
        )
        self.result_value.grid(row=8, column=0, columnspan=3, pady=(0, 10), sticky=tk.W)
        
        # Plot Frame
        self.plot_frame = ttk.Frame(self.main_frame)
        self.plot_frame.grid(row=9, column=0, columnspan=3, sticky=(tk.N, tk.S, tk.E, tk.W))
        self.plot_frame.columnconfigure(0, weight=1)
        self.plot_frame.rowconfigure(0, weight=1)
        
        # Progress Bar
        self.progress = ttk.Progressbar(self.main_frame, orient='horizontal',
                                        mode='indeterminate')
        self.progress.grid(row=10, column=0, columnspan=3, sticky=(tk.W, tk.E), padx=5, pady=5)
        self.progress.grid_remove()  # Initially hidden

    def toggle_mode(self):
        """Toggle between Basic and Advanced modes."""
        if self.advanced_mode.get():
            # Advanced Mode: Show Simulation Runs and Thresholds, disable p_slider
            self.p_label.config(state='disabled')
            self.p_scale.state(['disabled'])
            self.p_display.config(text="N/A")
            self.sim_runs_label.grid()
            self.sim_runs_spinbox.grid()
            self.min_threshold_label.grid()
            self.min_threshold_scale.grid()
            self.min_threshold_display.grid()
            self.max_threshold_label.grid()
            self.max_threshold_scale.grid()
            self.max_threshold_display.grid()
        else:
            # Basic Mode: Hide Simulation Runs and Thresholds, enable p_slider
            self.p_label.config(state='!disabled')
            self.p_scale.state(['!disabled'])
            self.p_display.config(text=f"{self.p.get():.2f}")
            self.sim_runs_label.grid_remove()
            self.sim_runs_spinbox.grid_remove()
            self.min_threshold_label.grid_remove()
            self.min_threshold_scale.grid_remove()
            self.min_threshold_display.grid_remove()
            self.max_threshold_label.grid_remove()
            self.max_threshold_scale.grid_remove()
            self.max_threshold_display.grid_remove()

    def update_p_label(self, event=None):
        """Update the display label for probability p."""
        if not self.advanced_mode.get():
            self.p_display.config(text=f"{self.p.get():.2f}")

    def update_min_threshold_label(self, event=None):
        """Update the display label for voter accuracy lower threshold."""
        if self.advanced_mode.get():
            self.min_threshold_display.config(text=f"{self.p_min_threshold.get():.2f}")
            # Ensure p_min <= p_max
            if self.p_min_threshold.get() > self.p_max_threshold.get():
                self.p_max_threshold.set(self.p_min_threshold.get())

    def update_max_threshold_label(self, event=None):
        """Update the display label for voter accuracy upper threshold."""
        if self.advanced_mode.get():
            self.max_threshold_display.config(text=f"{self.p_max_threshold.get():.2f}")
            # Ensure p_max >= p_min
            if self.p_max_threshold.get() < self.p_min_threshold.get():
                self.p_min_threshold.set(self.p_max_threshold.get())

    def validate_n(self):
        """Validate the number of voters input."""
        try:
            n = self.n_voters.get()
            if not isinstance(n, int) or n < 2:
                raise ValueError
        except (tk.TclError, ValueError):
            messagebox.showerror("Invalid Input", "Number of voters must be an integer >= 2.")
            self.n_voters.set(5)

    def validate_inputs(self):
        """Validate user inputs before running simulation."""
        try:
            n = self.n_voters.get()
            if n < 2:
                messagebox.showerror("Input Error", "Number of voters must be at least 2.")
                return None, None
        except tk.TclError:
            messagebox.showerror("Input Error", "Please enter a valid integer for voters.")
            return None, None

        if not self.advanced_mode.get():
            # Basic Mode: Validate p
            try:
                p = self.p.get()
                if not 0 <= p <= 1:
                    messagebox.showerror("Input Error", "Voter accuracy (p) must be between 0 and 1 (inclusive).")
                    return None, None
            except tk.TclError:
                messagebox.showerror("Input Error", "Please enter a valid number for voter accuracy (p).")
                return None, None
        else:
            # Advanced Mode: Validate simulation runs and p_thresholds
            try:
                sim_runs = self.simulation_runs.get()
                if sim_runs < 1000 or sim_runs > 1000000:
                    messagebox.showerror("Input Error", "Simulation runs must be between 1,000 and 1,000,000.")
                    return None, None
            except tk.TclError:
                messagebox.showerror("Input Error", "Please enter a valid integer for simulation runs.")
                return None, None

            try:
                p_min = self.p_min_threshold.get()
                p_max = self.p_max_threshold.get()
                if not (0 <= p_min <= 1 and 0 <= p_max <= 1):
                    messagebox.showerror("Input Error", "Voter accuracy thresholds (p_min and p_max) must be between 0 and 1.")
                    return None, None
                if p_min > p_max:
                    messagebox.showerror("Input Error", "Lower threshold (p_min) cannot be greater than upper threshold (p_max).")
                    return None, None
            except tk.TclError:
                messagebox.showerror("Input Error", "Please enter valid numbers for voter accuracy thresholds (p_min and p_max).")
                return None, None

        p = self.p.get() if not self.advanced_mode.get() else None
        return n, p

    def run_simulation(self):
        """Run the Condorcet simulation with validated inputs."""
        # Disable run button to prevent multiple clicks
        self.run_button.config(state='disabled')
        
        # Clear previous plot if exists
        if self.plot_canvas:
            self.plot_canvas.get_tk_widget().destroy()
            self.plot_canvas = None
        
        # Show progress bar
        self.progress.grid()
        self.progress.start(10)
        
        # Run simulation in a separate thread to keep UI responsive
        thread = threading.Thread(target=self.perform_simulation)
        thread.start()

    def perform_simulation(self):
        """Perform the simulation computation."""
        n, p = self.validate_inputs()
        if n is None:
            self.finish_simulation()
            return
        
        try:
            if not self.advanced_mode.get():
                # Basic Mode
                majority_success_prob = self.calculate_condorcet_optimized(n, p)
                # Update the result in the main thread
                self.root.after(0, self.update_result, f"{majority_success_prob:.4f}")
                # Plot the probability vs p
                self.root.after(0, self.plot_probability_vs_p, n)
            else:
                # Advanced Mode
                sim_runs = self.simulation_runs.get()
                p_min = self.p_min_threshold.get()
                p_max = self.p_max_threshold.get()
                majority_success_prob = self.calculate_condorcet_advanced(n, sim_runs, p_min, p_max)
                # Update the result in the main thread
                self.root.after(0, self.update_result, f"{majority_success_prob:.4f} (Estimated via {sim_runs} Simulations with {p_min:.2f} ≤ p_i ≤ {p_max:.2f})")
                # Plot the voter accuracies histogram
                self.root.after(0, self.plot_advanced_mode, n, sim_runs, p_min, p_max)
        except ValueError as e:
            self.root.after(0, self.show_calculation_error, str(e))
        finally:
            self.root.after(0, self.finish_simulation)

    def calculate_condorcet_optimized(self, n, p):
        """
        Calculate Condorcet probability using normal approximation for large n.
        Falls back to exact calculation for small n.
        """
        if p == 1.0:
            return 1.0
        if p == 0.0:
            return 0.0

        if n < 1000:  # Use exact calculation for small n
            return self.calculate_condorcet_exact(n, p)
        else:  # Use normal approximation for large n
            mean = n * p
            std = max((n * p * (1 - p)) ** 0.5, 1e-10)  # Avoid division by zero
            majority = n / 2
            z = (majority - mean) / std
            return 1 - special.ndtr(z)

    def calculate_condorcet_exact(self, n, p):
        """
        Exact calculation for small n using log space to avoid overflow.
        """
        if p == 1.0:
            return 1.0
        if p == 0.0:
            return 0.0

        majority = n // 2 + 1
        prob = 0.0
        for k in range(majority, n + 1):
            log_prob = (
                special.gammaln(n + 1) -
                special.gammaln(k + 1) -
                special.gammaln(n - k + 1) +
                k * np.log(p) +
                (n - k) * np.log(1 - p)
            )
            if log_prob > -700:  # Prevent underflow
                prob += np.exp(log_prob)
        return min(1.0, max(0.0, prob))  # Ensure result is between 0 and 1

    def calculate_condorcet_advanced(self, n, sim_runs, p_min, p_max):
        """
        Calculate majority success probability in Advanced Mode via Monte Carlo simulation.
        Each voter has a random accuracy p_i ~ Uniform(p_min, p_max).
        """
        if p_min == p_max:
            # If p_min equals p_max, degrade to Basic Mode
            return self.calculate_condorcet_optimized(n, p_min)

        majority_success = 0

        # To prevent memory issues, simulate in batches
        # Adjust the batch size based on available memory
        batches = 10000  # Number of simulations per batch
        num_batches = sim_runs // batches
        remainder = sim_runs % batches

        for _ in range(num_batches):
            p_i = np.random.uniform(p_min, p_max, (batches, n))  # p_min <= p_i <= p_max
            votes = np.random.binomial(1, p_i)
            sums = np.sum(votes, axis=1)
            majority_success += np.sum(sums > (n / 2))

        if remainder > 0:
            p_i = np.random.uniform(p_min, p_max, (remainder, n))
            votes = np.random.binomial(1, p_i)
            sums = np.sum(votes, axis=1)
            majority_success += np.sum(sums > (n / 2))

        return majority_success / sim_runs

    def calculate_condorcet_advanced_vectorized(self, n, sim_runs, p_min, p_max):
        """
        Vectorized calculation for Advanced Mode with voter accuracy thresholds.
        Each voter has a random accuracy p_i ~ Uniform(p_min, p_max).
        """
        if p_min == p_max:
            # If p_min equals p_max, degrade to Basic Mode
            return self.calculate_condorcet_optimized(n, p_min)

        p_i = np.random.uniform(p_min, p_max, (sim_runs, n))
        votes = np.random.binomial(1, p_i)
        sums = np.sum(votes, axis=1)
        majorities = sums > (n / 2)
        return np.mean(majorities)

    def plot_probability_vs_p(self, n):
        """Plot the probability that the majority is correct as a function of p."""
        # Clear previous plot if exists
        for widget in self.plot_frame.winfo_children():
            widget.destroy()
    
        # Generate p values
        p_values = np.linspace(0, 1, 500)
        prob_values = np.array([self.calculate_condorcet_optimized(n, p) for p in p_values])
    
        # Close all previous figures
        plt.close('all')
        
        # Create a figure
        fig, ax = plt.subplots(figsize=(8, 6), dpi=100)
        ax.plot(p_values, prob_values, label='Majority Correct Probability', color='blue')
        ax.axvline(self.p.get(), color='red', linestyle='--', label=f'p = {self.p.get():.2f}')
        ax.set_title(f'Condorcet Probability vs Voter Accuracy (n={n})')
        ax.set_xlabel('Voter Accuracy (p)')
        ax.set_ylabel('Probability Majority is Correct')
        ax.legend()
        ax.grid(True)
    
        # Embed the plot in Tkinter
        self.plot_canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
        self.plot_canvas.draw()
        self.plot_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def plot_advanced_mode(self, n, sim_runs, p_min, p_max):
        """Plot results for Advanced Mode."""
        # Clear previous plot if exists
        for widget in self.plot_frame.winfo_children():
            widget.destroy()
    
        # Close all previous figures
        plt.close('all')
    
        threshold_range = (p_min, p_max)
        
        try:
            max_simulations = 1e6  # Maximum number of simulations to plot
            plot_sim_runs = min(sim_runs, int(max_simulations))
    
            if plot_sim_runs < sim_runs:
                messagebox.showinfo("Plot Subset", f"Displaying a subset of {plot_sim_runs} simulations for the histogram plot.")
            
            p_i = np.random.uniform(p_min, p_max, (plot_sim_runs, n))
            votes = np.random.binomial(1, p_i)
            sums = np.sum(votes, axis=1)
            majorities = sums > (n / 2)
            majority_prob = np.mean(majorities)
            
            # Create a figure
            fig, ax = plt.subplots(figsize=(8, 6), dpi=100)
            bins = range(0, n + 2)
            ax.hist(sums, bins=bins, edgecolor='black', alpha=0.7, color='skyblue')
            ax.axvline(n/2, color='red', linestyle='--', label='Majority Threshold')
            ax.set_title(f'Histogram of Correct Votes per Simulation (n={n}, {p_min:.2f} ≤ p_i ≤ {p_max:.2f})')
            ax.set_xlabel('Number of Correct Votes')
            ax.set_ylabel('Frequency')
            ax.legend()
            ax.grid(True)
            
            # Embed the plot in Tkinter
            self.plot_canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
            self.plot_canvas.draw()
            self.plot_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
        except MemoryError:
            messagebox.showerror("Memory Error", "Simulation requires too much memory. Reduce the number of voters or simulation runs.")
    

    def update_result(self, message):
        """Update the result display with the given message."""
        self.result_value.config(text=message)

    def show_calculation_error(self, message):
        """Show a calculation error message."""
        messagebox.showerror("Calculation Error", message)

    def finish_simulation(self):
        """Reset UI elements after simulation."""
        # Stop and hide progress bar
        self.progress.stop()
        self.progress.grid_remove()
        # Re-enable run button
        self.run_button.config(state='normal')

    def on_closing(self):
        """Handle the window close event for clean termination."""
        try:
            if self.plot_canvas:
                self.plot_canvas.get_tk_widget().destroy()
        except:
            pass
        self.root.destroy()

# Create the main window and run the app
def main():
    root = tk.Tk()
    app = CondorcetSimulation(root)
    root.mainloop()

if __name__ == "__main__":
    main()```