Parking Function Simulation with Dissatisfaction¶

This code simulates a parking function where drivers arrive in order and attempt to park in their preferred spot. If their preferred spot is taken, they try the next higher-numbered spot. If no spot is available, they receive a penalty.

We also calculate total dissatisfaction, defined as the absolute difference between the assigned spot and preferred spot. If a driver cannot park, a fixed penalty is added.


Step 1: Define the simulation function¶

Simulates parking where each driver tries their preferred spot or the next available higher spot.

  • Returns the final parking assignments and total dissatisfaction.

  • Arguments:

    1. preferences: list of preferred spots for each driver (1-based)
    2. penalty: dissatisfaction added if a driver cannot park
In [1]:
def parking_function_with_penalties(preferences, penalty=10):
    n = len(preferences) # Number of cars/spots
    spots = [False] * n  # Creates list that tracks which spots are taken (False=open spot)
    assignments = [-1] * n  # Placeholder- no cars have parked at first
    dissatisfaction = 0 # Start with dissatisfaction at 0

    for i, pref in enumerate(preferences): # Loop over each car
        parked = False
        for j in range(pref - 1, n):  # try preferred and then higher
            if not spots[j]: # If spot is available
                spots[j] = True # Mark it as taken
                assignments[i] = j + 1  # Record the taken spot
                dissatisfaction += abs((j + 1) - pref) # Add how far off from preference
                parked = True # Car is now parked
                break

        if not parked:
            dissatisfaction += penalty  # Couldn't park add penalty

    return assignments, dissatisfaction

Step 2: Define a validity checker for classical parking functions¶

This checks if all drivers would be able to park following the standard parking function rules.

In [3]:
def is_valid_parking_function(preferences):
    n = len(preferences)
    available = [False] * n

    for pref in preferences:
        found = False
        for i in range(pref - 1, n): # starts checking from pref spot
            if not available[i]:
                available[i] = True
                found = True
                break
        if not found:
            return False  # A driver couldn’t park
    return True

Step 3: Run the simulation and show results¶

In [4]:
def run_and_report(preferences, penalty=10):
    assignments, dissatisfaction = parking_function_with_penalties(preferences, penalty)

    print("\nFinal Parking Assignments:")
    for i, spot in enumerate(assignments): # Go through each driver and check where they ended up
        if spot == -1: # Driver couldn't park
            print(f"Driver {i+1} → Could not park (Preferred: {preferences[i]})")
        else:
            print(f"Driver {i+1} → Spot {spot} (Preferred: {preferences[i]})")

    print(f"\nTotal Dissatisfaction: {dissatisfaction}")

    # Check if preferences are a valid parking function
    if is_valid_parking_function(preferences):
        print("The input preferences form a valid parking function.")
    else:
        print("The input preferences do NOT form a valid parking function.")

    # Check if all drivers actually parked
    if -1 not in assignments:
        print("All drivers successfully parked.")
    else:
        print("Some drivers could not park.")

Step 4: Example usage¶

Has user input preferences for drivers and runs the functions

In [25]:
# Get user input for preferences
try:
    n = int(input("Enter the number of drivers (and parking spots): "))
    if n <= 0:
        raise ValueError("Number must be positive.")

    preferences = []
    print(f"\nEnter the preferred parking spot (1 to {n}) for each driver:")
    for i in range(n):
        while True:
            try:
                pref = int(input(f"Driver {i+1} preference: "))
                if 1 <= pref <= n:
                    preferences.append(pref)
                    break
                else:
                    print(f"Please enter a number between 1 and {n}.")
            except ValueError:
                print("Invalid input. Please enter an integer.")

    print("\n Preferences recorded successfully!")
    print("π (Preferences):", preferences)

except ValueError as e:
    print(f"Error: {e}")
run_and_report(preferences)
Enter the number of drivers (and parking spots): 6

Enter the preferred parking spot (1 to 6) for each driver:
Driver 1 preference: 3
Driver 2 preference: 4
Driver 3 preference: 6
Driver 4 preference: 3
Driver 5 preference: 2
Driver 6 preference: 1

 Preferences recorded successfully!
π (Preferences): [3, 4, 6, 3, 2, 1]

Final Parking Assignments:
Driver 1 → Spot 3 (Preferred: 3)
Driver 2 → Spot 4 (Preferred: 4)
Driver 3 → Spot 6 (Preferred: 6)
Driver 4 → Spot 5 (Preferred: 3)
Driver 5 → Spot 2 (Preferred: 2)
Driver 6 → Spot 1 (Preferred: 1)

Total Dissatisfaction: 2
The input preferences form a valid parking function.
All drivers successfully parked.

Parking Function Simulation with VIP Drivers and Reserved Spots¶

This version allows user input for:

  • Preferences of each driver
  • Which drivers are VIPs

Rules:

  • VIP drivers get priority.
  • Non-VIP drivers cannot park in VIP-preferred spots.
  • Each driver tries their preferred spot and moves forward if it's taken.
  • If no valid spot is available, they do not park and receive a penalty.

Simulates parking with reserved VIP spots.¶

VIPs can park anywhere.¶

Non-VIPs cannot park in VIP-preferred spots.¶

Arguments:

  1. preferences -- list of preferred spots (1-based)
  2. vip_flags -- list of booleans (True if driver is VIP)
  3. penalty -- dissatisfaction added if driver cannot park
In [7]:
def parking_with_vip_reservations(preferences, vip_flags, penalty=10):
    n = len(preferences) # Number of drivers and spots
    spots = [False] * n  # All spots start off open
    assignments = [-1] * n # All cars start off as unparked
    total_dissatisfaction = 0

    # Determine reserved spots: spots preferred by VIPs
    reserved_spots = set()
    for i, is_vip in enumerate(vip_flags): # Go through each driver to check if VIP
        if is_vip:
            reserved_spots.add(preferences[i]) # If VIP, reserve their preferred spot

    for i, (pref, is_vip) in enumerate(zip(preferences, vip_flags)): # Loop through drivers and their VIP status
        parked = False

        for j in range(pref - 1, n): # Try from preferred spot
            spot_number = j + 1

            # If spot taken, move on
            if spots[j]:
                continue

            # Non-VIP drivers cannot take VIP-reserved spots
            if not is_vip and spot_number in reserved_spots:
                continue

            # If spot is available and allowed
            spots[j] = True # Mark it as takken
            assignments[i] = spot_number # Record the assignment
            total_dissatisfaction += abs(spot_number - pref) # Calculate dissatisfaction
            parked = True # Mark the car as parked
            break

        if not parked: # If driver can't park, add penalty
            total_dissatisfaction += penalty  # Could not park

    return assignments, total_dissatisfaction

Validity Check (same as before)¶

In [8]:
def is_valid_parking_function(preferences):
    n = len(preferences)
    spots = [False] * n

    for pref in preferences:
        parked = False
        for i in range(pref - 1, n):
            if not spots[i]:
                spots[i] = True
                parked = True
                break
        if not parked:
            return False
    return True

Function to Get User Input¶

In [9]:
def get_input():
    n = int(input("Enter the number of drivers (and spots): "))
    preferences = []

    print("\nEnter the preferred parking spot (1 to {}) for each driver:".format(n))
    for i in range(n):
        pref = int(input(f"Driver {i+1} preference: "))
        preferences.append(pref)

    print("\nEnter the driver numbers (1-based) of the VIPs, separated by commas (e.g., 1,3,5):")
    vip_input = input("VIP driver numbers: ")
    vip_indices = set(int(x.strip()) - 1 for x in vip_input.split(",") if x.strip().isdigit())

    vip_flags = [(i in vip_indices) for i in range(n)]

    return preferences, vip_flags

Final Report Function¶

In [13]:
def run_vip_parking_simulation():
    preferences, vip_flags = get_input()
    assignments, dissatisfaction = parking_with_vip_reservations(preferences, vip_flags)

    print("\nFinal Parking Assignments:")
    for i, spot in enumerate(assignments):
        vip_label = " (VIP)" if vip_flags[i] else ""
        if spot == -1:
            print(f"Driver {i+1}{vip_label} → Could not park (Preferred: {preferences[i]})")
        else:
            print(f"Driver {i+1}{vip_label} → Spot {spot} (Preferred: {preferences[i]})")

    print(f"\nTotal Dissatisfaction: {dissatisfaction}")

    # Check if preferences are a valid parking function (ignores VIP restrictions)
    if is_valid_parking_function(preferences):
        print("The input preferences form a valid parking function (without VIP restrictions).")
    else:
        print("The input preferences do NOT form a valid parking function (even without VIP restrictions).")

Run the Simulation¶

In [27]:
run_vip_parking_simulation()
Enter the number of drivers (and spots): 7

Enter the preferred parking spot (1 to 7) for each driver:
Driver 1 preference: 4
Driver 2 preference: 3
Driver 3 preference: 2
Driver 4 preference: 1
Driver 5 preference: 1
Driver 6 preference: 5
Driver 7 preference: 6

Enter the driver numbers (1-based) of the VIPs, separated by commas (e.g., 1,3,5):
VIP driver numbers: 1,5,6

Final Parking Assignments:
Driver 1 (VIP) → Spot 4 (Preferred: 4)
Driver 2 → Spot 3 (Preferred: 3)
Driver 3 → Spot 2 (Preferred: 2)
Driver 4 → Spot 6 (Preferred: 1)
Driver 5 (VIP) → Spot 1 (Preferred: 1)
Driver 6 (VIP) → Spot 5 (Preferred: 5)
Driver 7 → Spot 7 (Preferred: 6)

Total Dissatisfaction: 6
The input preferences form a valid parking function (without VIP restrictions).

Random Simulation¶

This function runs the VIP Parking Model many times with randomized inputs to see what happens on average.¶

  • n_drivers= 10 (10 drivers and spots)
  • vip_ratio= 0.3 (30% of drivers randomly marked as VIP)
  • penalty= 10 (dissatisfaction for not parking)
  • simulations= 1000 (runs the simulation 1000 times)
In [23]:
import random

def simulate_multiple_runs(n_drivers=100, vip_ratio=0.3, penalty=10, simulations=1000):
    total_dissatisfaction = 0
    total_parked = 0

    for _ in range(simulations): # Each driver randomly chooses a preferred spot between 1 and n_drivers
        preferences = [random.randint(1, n_drivers) for _ in range(n_drivers)]

        # Assign VIPs randomly
        num_vips = int(n_drivers * vip_ratio) # Calculate number of VIPs
        vip_indices = set(random.sample(range(n_drivers), num_vips)) # Randomly select that many driver indicies
        vip_flags = [(i in vip_indices) for i in range(n_drivers)] # Create list of True/False flags indicating VIPs
        # Simulate one parking run
        assignments, dissatisfaction = parking_with_vip_reservations(preferences, vip_flags, penalty)
        # Store results
        total_dissatisfaction += dissatisfaction
        total_parked += sum(1 for spot in assignments if spot != -1)
    # Calculate Averages
    avg_dissatisfaction = total_dissatisfaction / simulations
    avg_parked = total_parked / simulations

    print(f"\n--- Simulation Results ({simulations} runs) ---")
    print(f"Average Total Dissatisfaction: {avg_dissatisfaction:.2f}")
    print(f"Average Drivers Parked: {avg_parked:.2f} / {n_drivers}")
    print(f"Average Parking Success Rate: {100 * avg_parked / n_drivers:.2f}%")
In [28]:
simulate_multiple_runs(n_drivers=100, vip_ratio=0.3, penalty=10, simulations=1000)
--- Simulation Results (1000 runs) ---
Average Total Dissatisfaction: 362.36
Average Drivers Parked: 94.46 / 100
Average Parking Success Rate: 94.46%

Data collection for analysis and visualization¶

  1. How changing the VIP Ratio affects performance
  2. How changing the penalty value affects performance

For both we are tracking:

  1. Average total dissatisfaction
  2. Parking success rate (Percent of drivers who found a spot)
In [17]:
import matplotlib.pyplot as plt

# Collect data for varying VIP ratios
vip_ratios = [i / 10 for i in range(11)]  # 0.0 to 1.0
dissatisfaction_vip = []
success_rate_vip = []

for vip_ratio in vip_ratios:
    total_dissatisfaction = 0
    total_parked = 0
    simulations = 500
    n_drivers = 10
    penalty = 10

    for _ in range(simulations):
        preferences = [random.randint(1, n_drivers) for _ in range(n_drivers)]
        num_vips = int(n_drivers * vip_ratio)
        vip_indices = set(random.sample(range(n_drivers), num_vips))
        vip_flags = [(i in vip_indices) for i in range(n_drivers)]

        assignments, dissatisfaction = parking_with_vip_reservations(preferences, vip_flags, penalty)

        total_dissatisfaction += dissatisfaction
        total_parked += sum(1 for spot in assignments if spot != -1)

    avg_diss = total_dissatisfaction / simulations
    avg_parked = total_parked / simulations
    dissatisfaction_vip.append(avg_diss)
    success_rate_vip.append((avg_parked / n_drivers) * 100)
In [18]:
penalties = list(range(0, 55, 5)) # 0 to 50 in steps of 5
dissatisfaction_penalty = []
success_rate_penalty = []

for penalty in penalties:
    total_dissatisfaction = 0
    total_parked = 0
    simulations = 500
    n_drivers = 10
    vip_ratio = 0.3

    for _ in range(simulations):
        preferences = [random.randint(1, n_drivers) for _ in range(n_drivers)]
        num_vips = int(n_drivers * vip_ratio)
        vip_indices = set(random.sample(range(n_drivers), num_vips))
        vip_flags = [(i in vip_indices) for i in range(n_drivers)]

        assignments, dissatisfaction = parking_with_vip_reservations(preferences, vip_flags, penalty)

        total_dissatisfaction += dissatisfaction
        total_parked += sum(1 for spot in assignments if spot != -1)

    avg_diss = total_dissatisfaction / simulations
    avg_parked = total_parked / simulations
    dissatisfaction_penalty.append(avg_diss)
    success_rate_penalty.append((avg_parked / n_drivers) * 100)

Plot the Results¶

In [21]:
# Plot 1: VIP Ratio vs Parking Outcomes
plt.figure(figsize=(10, 5))
plt.plot(vip_ratios, dissatisfaction_vip, marker='o', label="Avg Dissatisfaction")
plt.plot(vip_ratios, success_rate_vip, marker='s', label="Success Rate (%)")
plt.title("Effect of VIP Ratio on Parking Outcomes")
plt.xlabel("VIP Ratio")
plt.ylabel("Metric Value")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
No description has been provided for this image

Results: Effect of VIP Ratio on Parking Outcomes¶

Success Rate¶

  • Remains very stable regardless of how many drivers are VIP

Dissatisfaction¶

  • Stays fairly stable showing that with even more VIPs the simulation handles it well

Conclusion:¶

The parking system is resilient to the proportion of VIps. Even with 100% VIPs, people still manage to park at about the same rate and dissatisfaction levels don't spike

In [22]:
# Plot 2: Penalty vs Parking Outcomes
plt.figure(figsize=(10, 5))
plt.plot(penalties, dissatisfaction_penalty, marker='o', label="Avg Dissatisfaction")
plt.plot(penalties, success_rate_penalty, marker='s', label="Success Rate (%)")
plt.title("Effect of Penalty on Parking Outcomes (VIP Ratio = 0.3)")
plt.xlabel("Penalty Value")
plt.ylabel("Metric Value")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
No description has been provided for this image

Results: Effect of Penalty Value on Parking Outcomes (VIP Ratio= 0.3)¶

Success Rate¶

  • Remains very stable, even with higher penalties

Dissatisfaction¶

  • Increases significanlty as penalties increase (as expected)
  • Around penalty= 30-50, the dissatisfaction starts to rise very fast- showing that not parking becomes very costly

Conclusion:¶

Penalty affects the cost, not the outcome. It doesn't change how many people can park, but increases dissatisfaction when parking fails.

Number of parking function¶

In [29]:
import random
import math

def simulate_multiple_runs(n_drivers=100, vip_ratio=0.3, penalty=10, simulations=1000):
    total_dissatisfaction = 0
    total_parked = 0

    for _ in range(simulations):
        # Each driver randomly chooses a preferred spot between 1 and n_drivers
        preferences = [random.randint(1, n_drivers) for _ in range(n_drivers)]

        # Assign VIPs randomly
        num_vips = int(n_drivers * vip_ratio)
        vip_indices = set(random.sample(range(n_drivers), num_vips))
        vip_flags = [(i in vip_indices) for i in range(n_drivers)]

        # Simulate one parking run
        assignments, dissatisfaction = parking_with_vip_reservations(preferences, vip_flags, penalty)

        # Store results
        total_dissatisfaction += dissatisfaction
        total_parked += sum(1 for spot in assignments if spot != -1)

    # Calculate Averages
    avg_dissatisfaction = total_dissatisfaction / simulations
    avg_parked = total_parked / simulations

    # Theoretical number of parking functions for comparison
    theoretical_parking_functions = (n_drivers + 1) ** (n_drivers - 1)

    print(f"\n--- Simulation Results ({simulations} runs) ---")
    print(f"Average Total Dissatisfaction: {avg_dissatisfaction:.2f}")
    print(f"Average Drivers Parked: {avg_parked:.2f} / {n_drivers}")
    print(f"Average Parking Success Rate: {100 * avg_parked / n_drivers:.2f}%")
    print(f"\nTheoretical Valid Parking Functions (n={n_drivers}): {(n_drivers + 1)}^{n_drivers - 1} = {theoretical_parking_functions:,}")
In [31]:
# run to function
simulate_multiple_runs(n_drivers=10, vip_ratio=0.3, penalty=10, simulations=1000)
--- Simulation Results (1000 runs) ---
Average Total Dissatisfaction: 19.28
Average Drivers Parked: 8.71 / 10
Average Parking Success Rate: 87.08%

Theoretical Valid Parking Functions (n=10): 11^9 = 2,357,947,691