In the following code, can we choose the threshold of the voter accuracy (p) RNG in the Advanced Mode?

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("800x700")
        self.root.minsize(800, 700)
        
        # 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=10000)  # 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(7, 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
        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)
        
        # Initially hide simulation runs
        self.sim_runs_label.grid_remove()
        self.sim_runs_spinbox.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=4, 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=5, 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=6, 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=7, 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=8, 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, 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()
        else:
            # Basic Mode: Hide Simulation Runs, 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()

    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 validate_n(self):
        """Validate the number of voters input."""
        n = self.n_voters.get()
        if not isinstance(n, int) or n < 2:
            messagebox.showerror("Invalid Input", "Number of voters must be an integer >= 2.")
            self.n_voters.set(5)

    def run_simulation(self):
        """Run the Condorcet simulation with validated inputs."""
        # Disable run button to prevent multiple clicks
        self.run_button.config(state='disabled')
        
        # 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()
                majority_success_prob = self.calculate_condorcet_advanced(n, sim_runs)
                # Update the result in the main thread
                self.root.after(0, self.update_result, f"{majority_success_prob:.4f} (Estimated via {sim_runs} Simulations)")
                # Plot the voter accuracies histogram
                self.root.after(0, self.plot_advanced_mode, n, sim_runs)
        except ValueError as e:
            self.root.after(0, self.show_calculation_error, str(e))
        finally:
            self.root.after(0, self.finish_simulation)

    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: Simulation runs already validated by Spinbox
            p = None  # Not used in Advanced Mode

        return n, p

    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):
        """
        Calculate majority success probability in Advanced Mode via Monte Carlo simulation.
        Each voter has a random accuracy p_i ~ Uniform(0,1).
        """
        # Generate all p_i once for reproducibility
        # Alternatively, p_i can be regenerated per simulation run
        # Here, assign random p_i per run
        majority_correct = 0
        for _ in range(sim_runs):
            p_i = np.random.uniform(0, 1, n)  # Random accuracies
            votes = np.random.binomial(1, p_i)  # Simulate votes
            if np.sum(votes) > n / 2:
                majority_correct += 1
        return majority_correct / sim_runs

    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])
        
        # Create a figure
        fig, ax = plt.subplots(figsize=(7, 5), 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):
        """Plot results for Advanced Mode."""
        # Clear previous plot if exists
        for widget in self.plot_frame.winfo_children():
            widget.destroy()
        
        # For visualization, let's plot a histogram of the number of correct votes across simulations
        # To do this efficiently, we'll vectorize the simulation
        batches = 1000  # Process in batches to manage memory
        num_batches = sim_runs // batches
        remainder = sim_runs % batches
        correct_votes = []
        
        for _ in range(num_batches):
            p_i = np.random.uniform(0, 1, (batches, n))
            votes = np.random.binomial(1, p_i)
            sums = np.sum(votes, axis=1)
            correct_votes.extend(sums)
        
        if remainder > 0:
            p_i = np.random.uniform(0, 1, (remainder, n))
            votes = np.random.binomial(1, p_i)
            sums = np.sum(votes, axis=1)
            correct_votes.extend(sums)
        
        correct_votes = np.array(correct_votes)
        majorities = correct_votes > (n / 2)
        probability = np.mean(majorities)
        
        # Create a figure
        fig, ax = plt.subplots(figsize=(7, 5), dpi=100)
        ax.hist(correct_votes, bins=range(0, n+2), edgecolor='black', alpha=0.7)
        ax.axvline(n/2, color='red', linestyle='--', label='Majority Threshold')
        ax.set_title(f'Histogram of Correct Votes per Simulation (n={n})')
        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)

    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()

    # Advanced Mode Simulation Optimization
    # Vectorized version to handle large simulation runs efficiently
    def calculate_condorcet_advanced_vectorized(self, n, sim_runs):
        """
        Calculate majority success probability in Advanced Mode via vectorized Monte Carlo simulation.
        Each voter has a random accuracy p_i ~ Uniform(0,1).
        """
        # Simulate all runs at once
        p_i = np.random.uniform(0, 1, (sim_runs, n))
        votes = np.random.binomial(1, p_i)
        sums = np.sum(votes, axis=1)
        majorities = sums > (n / 2)
        return np.mean(majorities)

    # Optionally, replace the loop with the vectorized version for better performance
    # To integrate this, replace the 'calculate_condorcet_advanced' method accordingly

    # Alternatively, comment out and use the loop-based method to prevent memory issues for very large sim_runs
    # Uncomment the line below and comment out the loop in 'calculate_condorcet_advanced' to use vectorization
    # return self.calculate_condorcet_advanced_vectorized(n, sim_runs)

    # End of class CondorcetSimulation

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

if __name__ == "__main__":
    main()