diff --git a/EXPERIMENT.md b/EXPERIMENT.md index d352b44..e5f9952 100644 --- a/EXPERIMENT.md +++ b/EXPERIMENT.md @@ -30,7 +30,7 @@ This guide explains how to use the interactive notebook to experiment with diffe 3. **Use the Interactive Interface** - The notebook will open automatically in your browser. Execute the cells in order to start experimenting with different participation scenarios. + The notebook will open automatically in your browser. Execute it by clicking "Run All" from `Run` tab in order to start experimenting with different participation scenarios. ## Using the Interactive Interface diff --git a/lockdrop-calculations-simulated.ipynb b/lockdrop-calculations-simulated.ipynb index af72dd5..174b6b4 100644 --- a/lockdrop-calculations-simulated.ipynb +++ b/lockdrop-calculations-simulated.ipynb @@ -5,7 +5,7 @@ "id": "09975f67", "metadata": {}, "source": [ - "## Z Token Lockdrop Distribution" + "## Z Token Lockdrop Distribution (Simulation)" ] }, { @@ -23,7 +23,7 @@ "outputs": [], "source": [ "# Import shared calculation module\n", - "from lockdrop_calculations import (\n", + "from lockdrop import (\n", " configure_matplotlib, print_constants_summary,\n", " run_simulation_analysis, create_visualization\n", ")\n", diff --git a/lockdrop-calculations.ipynb b/lockdrop-calculations.ipynb index 50bbb28..6ceff8f 100644 --- a/lockdrop-calculations.ipynb +++ b/lockdrop-calculations.ipynb @@ -24,7 +24,7 @@ "outputs": [], "source": [ "# Import shared calculation module\n", - "from lockdrop_calculations import (\n", + "from lockdrop import (\n", " configure_matplotlib, print_constants_summary,\n", " create_experimental_interface\n", ")\n", @@ -64,14 +64,6 @@ "# Create complete experimental interface\n", "create_experimental_interface()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "17264a50-2d4b-4c83-aa08-c3d2e1d90fdd", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/lockdrop/__init__.py b/lockdrop/__init__.py new file mode 100644 index 0000000..ef71743 --- /dev/null +++ b/lockdrop/__init__.py @@ -0,0 +1,25 @@ +""" +Lockdrop calculations module. + +This package provides a modular implementation of lockdrop token distribution calculations, +penalty systems, bonus pools, and interactive experimental interfaces. +""" + +# Import all public functions for backward compatibility +from .constants import * +from .calculations import * +from .display import ( + print_constants_summary, + print_analysis_tables +) +from .visualization import * +from .widgets import ( + create_experimental_interface +) +from .simulation import ( + run_simulation_analysis +) + +__version__ = "1.0.0" + +# TODO: Use this package in zenithd notebooks diff --git a/lockdrop/calculations.py b/lockdrop/calculations.py new file mode 100644 index 0000000..fdfec26 --- /dev/null +++ b/lockdrop/calculations.py @@ -0,0 +1,185 @@ +""" +Core lockdrop calculation functions. + +This module contains the main mathematical functions for calculating token allocations, +penalties, bonuses, and final distributions. +""" + +from decimal import Decimal, ROUND_DOWN +from .constants import ( + TOKEN_PRECISION, LOCKDROP_ALLOCATION, STAR_ALLOCATION_PERCENT, + GALAXY_ALLOCATION_PERCENT, + LOCKDROP_DURATION_BLOCKS, PENALTY_RATES +) + + +def calculate_dynamic_allocations(participation_counts): + """ + Calculate allocations based on actual participation counts. + + Args: + participation_counts: Dict with keys like 'stars_1_years', 'stars_2_years', etc. + """ + # Extract participation data + stars_counts = {year: Decimal(participation_counts[f'stars_{year}_years']) for year in range(1, 6)} + galaxies_counts = {year: Decimal(participation_counts[f'galaxies_{year}_years']) for year in range(1, 6)} + + total_stars_locked = sum(stars_counts.values()) + total_galaxies_locked = sum(galaxies_counts.values()) + + lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT + lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT + + # Dynamic allocations based on actual participation + max_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0') + max_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0') + + # Quanta calculation + z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS + z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS + + # Round down to 6 decimals + adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) + adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) + + # Adjusted max allocation + adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS + adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS + + # Rounding errors + rounding_error_per_star = max_allocation_per_star - adjusted_max_allocation_per_star + rounding_error_per_galaxy = max_allocation_per_galaxy - adjusted_max_allocation_per_galaxy + + total_rounding_error_stars = lockdrop_allocation_stars - (adjusted_max_allocation_per_star * total_stars_locked) + total_rounding_error_galaxies = lockdrop_allocation_galaxies - (adjusted_max_allocation_per_galaxy * total_galaxies_locked) + + return { + 'stars_counts': stars_counts, + 'galaxies_counts': galaxies_counts, + 'total_stars_locked': total_stars_locked, + 'total_galaxies_locked': total_galaxies_locked, + 'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star, + 'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy, + 'rounding_error_per_star': rounding_error_per_star, + 'rounding_error_per_galaxy': rounding_error_per_galaxy, + 'total_rounding_error_stars': total_rounding_error_stars, + 'total_rounding_error_galaxies': total_rounding_error_galaxies, + 'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block, + 'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block + } + + +def calculate_bonus_pools(allocation_data): + """Calculate bonus pools from penalties and rounding errors.""" + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] + adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] + total_rounding_error_stars = allocation_data['total_rounding_error_stars'] + total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] + + # Calculate penalty pools + star_penalty_pool = Decimal('0') + for years in [1, 2, 3, 4]: + penalty = PENALTY_RATES[years] + star_penalty_pool += adjusted_max_allocation_per_star * stars_counts[years] * penalty + + galaxy_penalty_pool = Decimal('0') + for years in [1, 2, 3, 4]: + penalty = PENALTY_RATES[years] + galaxy_penalty_pool += adjusted_max_allocation_per_galaxy * galaxies_counts[years] * penalty + + star_penalty_pool = star_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + galaxy_penalty_pool = galaxy_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + + # Add rounding errors to bonus pools + star_bonus_pool_total = star_penalty_pool + total_rounding_error_stars + galaxy_bonus_pool_total = galaxy_penalty_pool + total_rounding_error_galaxies + + # Calculate bonus per 5-year participant + stars_5_years = stars_counts[5] + galaxies_5_years = galaxies_counts[5] + + bonus_per_star_5_years = star_bonus_pool_total / stars_5_years if stars_5_years > 0 else Decimal('0') + bonus_per_galaxy_5_years = galaxy_bonus_pool_total / galaxies_5_years if galaxies_5_years > 0 else Decimal('0') + + return { + 'star_penalty_pool': star_penalty_pool, + 'galaxy_penalty_pool': galaxy_penalty_pool, + 'star_bonus_pool_total': star_bonus_pool_total, + 'galaxy_bonus_pool_total': galaxy_bonus_pool_total, + 'bonus_per_star_5_years': bonus_per_star_5_years, + 'bonus_per_galaxy_5_years': bonus_per_galaxy_5_years + } + + +def calculate_final_allocations(allocation_data, bonus_data): + """Calculate final allocations for all lock periods.""" + adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] + adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] + bonus_per_star_5_years = bonus_data['bonus_per_star_5_years'] + bonus_per_galaxy_5_years = bonus_data['bonus_per_galaxy_5_years'] + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + + # Calculate final allocations + final_star_allocations = {} + final_galaxy_allocations = {} + + for years in range(1, 6): + penalty = PENALTY_RATES[years] + + if years == 5: + # 5-year participants get base + bonus + final_star_allocations[years] = (adjusted_max_allocation_per_star + bonus_per_star_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy + bonus_per_galaxy_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + else: + # Other years get penalized amounts + final_star_allocations[years] = (adjusted_max_allocation_per_star * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + + # Calculate Z per block for each lock period + star_z_per_block = {} + galaxy_z_per_block = {} + + for years in range(1, 6): + lock_duration_fraction = Decimal(years) / Decimal('5') # Fraction of full 5-year period + effective_blocks = LOCKDROP_DURATION_BLOCKS * lock_duration_fraction + + star_z_per_block[years] = (final_star_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + galaxy_z_per_block[years] = (final_galaxy_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) + + # Calculate total allocations for verification + total_stars_allocation = sum(final_star_allocations[year] * stars_counts[year] for year in range(1, 6)) + total_galaxies_allocation = sum(final_galaxy_allocations[year] * galaxies_counts[year] for year in range(1, 6)) + + return { + 'final_star_allocations': final_star_allocations, + 'final_galaxy_allocations': final_galaxy_allocations, + 'star_z_per_block': star_z_per_block, + 'galaxy_z_per_block': galaxy_z_per_block, + 'total_stars_allocation': total_stars_allocation, + 'total_galaxies_allocation': total_galaxies_allocation + } + + +def generate_test_output(final_data): + """Generate JSON output for test validation.""" + final_star_allocations = final_data['final_star_allocations'] + final_galaxy_allocations = final_data['final_galaxy_allocations'] + total_allocation = final_data['total_stars_allocation'] + final_data['total_galaxies_allocation'] + + # Convert to $sZ units (multiply by 10^8) + output = { + "stars": { + f"{year}_years": int(final_star_allocations[year] * Decimal('1e8')) + for year in range(1, 6) + }, + "galaxies": { + f"{year}_years": int(final_galaxy_allocations[year] * Decimal('1e8')) + for year in range(1, 6) + }, + "total_allocation_sz": int(total_allocation * Decimal('1e8')) + } + + return output diff --git a/lockdrop/constants.py b/lockdrop/constants.py new file mode 100644 index 0000000..8503888 --- /dev/null +++ b/lockdrop/constants.py @@ -0,0 +1,64 @@ +""" +Lockdrop calculation constants and configuration. + +This module contains all constants used in lockdrop token distribution calculations. +""" + +from decimal import Decimal, ROUND_DOWN, getcontext + +# Configure decimal precision +getcontext().prec = 28 +getcontext().rounding = ROUND_DOWN + +# Token Constants +TOKEN_PRECISION = Decimal('0.00000001') # 8 decimal precision +TOTAL_SUPPLY = 4294967296 # 1 $Z per Urbit ID +LOCKDROP_ALLOCATION_PERCENT = Decimal('0.3') +LOCKDROP_ALLOCATION = TOTAL_SUPPLY * LOCKDROP_ALLOCATION_PERCENT + +# Urbit Point Counts +NUM_GALAXIES = pow(2, 8) +NUM_STARS = pow(2, 16) - pow(2, 8) +NUM_PLANETS = pow(2, 32) - pow(2, 16) + +# Allocation Distribution +STAR_ALLOCATION_PERCENT = Decimal(NUM_STARS / pow(2, 16)) +GALAXY_ALLOCATION_PERCENT = 1 - STAR_ALLOCATION_PERCENT + +# Lockdrop Duration (5 years including 1 leap year) +LOCKDROP_DURATION_SECONDS = 5 * 365.25 * 24 * 60 * 60 +BLOCK_DURATION_SECONDS = 2 +LOCKDROP_DURATION_BLOCKS = int(LOCKDROP_DURATION_SECONDS / BLOCK_DURATION_SECONDS) + +# Penalty Rates by Lock Period +PENALTY_RATES = { + 5: Decimal('0'), + 4: Decimal('0.2'), + 3: Decimal('0.4'), + 2: Decimal('0.6'), + 1: Decimal('0.8') +} + +# Preset Scenarios for Experimentation +SCENARIOS = { + "balanced": { + "description": 'Balanced distribution across all lock periods', + "stars": {"1_year": 8000, "2_year": 8000, "3_year": 8000, "4_year": 8000, "5_year": 8000}, + "galaxies": {"1_year": 40, "2_year": 40, "3_year": 40, "4_year": 40, "5_year": 40} + }, + "five_year_focused": { + "description": 'Most participants choose 5-year lock (maximum bonus scenario)', + "stars": {"1_year": 2000, "2_year": 2000, "3_year": 3000, "4_year": 5000, "5_year": 28000}, + "galaxies": {"1_year": 10, "2_year": 10, "3_year": 20, "4_year": 30, "5_year": 130} + }, + "short_term_focused": { + "description": 'Most participants choose shorter locks (high penalty scenario)', + "stars": {"1_year": 20000, "2_year": 15000, "3_year": 4000, "4_year": 800, "5_year": 200}, + "galaxies": {"1_year": 100, "2_year": 80, "3_year": 15, "4_year": 3, "5_year": 2} + }, + "low_participation": { + "description": 'Low overall participation scenario', + "stars": {"1_year": 1000, "2_year": 800, "3_year": 600, "4_year": 400, "5_year": 200}, + "galaxies": {"1_year": 8, "2_year": 6, "3_year": 4, "4_year": 2, "5_year": 5} + } +} diff --git a/lockdrop/display.py b/lockdrop/display.py new file mode 100644 index 0000000..8d04169 --- /dev/null +++ b/lockdrop/display.py @@ -0,0 +1,265 @@ +""" +Display and table formatting functions for lockdrop analysis. + +This module contains functions for displaying analysis results in formatted tables +and organizing the presentation of calculation results. +""" + +import pandas as pd +from decimal import Decimal +from tabulate import tabulate +from .constants import ( + PENALTY_RATES, LOCKDROP_ALLOCATION, STAR_ALLOCATION_PERCENT, + GALAXY_ALLOCATION_PERCENT, LOCKDROP_DURATION_BLOCKS, TOTAL_SUPPLY, + NUM_STARS, NUM_GALAXIES, NUM_PLANETS, LOCKDROP_ALLOCATION_PERCENT +) + + +def print_table_with_borders(df, title=""): + """Print DataFrame with borders using tabulate.""" + if title: + print(f"\n{title}") + print(tabulate(df, headers='keys', tablefmt='grid', showindex=False)) + + +def print_constants_summary(): + """Display core constants as formatted tables.""" + print("=" * 80) + print("šŸ“Š $Z LOCKDROP DISTRIBUTION - CORE CONSTANTS") + print("=" * 80) + + # Lockdrop Allocation Table + lockdrop_df = pd.DataFrame({ + 'Parameter': ['Total Supply (1 $Z per Urbit ID)', 'Lockdrop Allocation %', 'Lockdrop Allocation ($Z)'], + 'Value': [f"{TOTAL_SUPPLY:,}", f"{LOCKDROP_ALLOCATION_PERCENT:.1%}", f"{LOCKDROP_ALLOCATION:,.1f}"] + }) + print_table_with_borders(lockdrop_df, "šŸ”’ LOCKDROP ALLOCATION") + + # Points Distribution Table + points_df = pd.DataFrame({ + 'Point Type': ['Galaxies', 'Stars', 'Planets (excl.)'], + 'Count': [f"{NUM_GALAXIES:,}", f"{NUM_STARS:,}", f"{NUM_PLANETS:,}"], + 'Allocation %': [f"{GALAXY_ALLOCATION_PERCENT:.3%}", f"{STAR_ALLOCATION_PERCENT:.3%}", "0%"] + }) + print_table_with_borders(points_df, "🌟 URBIT POINT DISTRIBUTION") + + # Penalty Schedule Table + penalty_df = pd.DataFrame({ + 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], + 'Penalty Rate': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], + 'Token % of Max': ['100%', '80%', '60%', '40%', '20%'] + }) + print_table_with_borders(penalty_df, "āš–ļø PENALTY SCHEDULE") + print("\n" + "="*80) + + +def print_allocation_calculations(allocation_data): + """Print basic allocation calculation tables.""" + lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT + lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT + + total_stars_locked = allocation_data['total_stars_locked'] + total_galaxies_locked = allocation_data['total_galaxies_locked'] + rounding_error_per_star = allocation_data['rounding_error_per_star'] + rounding_error_per_galaxy = allocation_data['rounding_error_per_galaxy'] + adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] + adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] + adjusted_z_per_star_per_block = allocation_data['adjusted_z_per_star_per_block'] + adjusted_z_per_galaxy_per_block = allocation_data['adjusted_z_per_galaxy_per_block'] + total_rounding_error_stars = allocation_data['total_rounding_error_stars'] + total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] + + print("=" * 80) + print("šŸ”¢ ALLOCATION CALCULATIONS") + print("=" * 80) + + # Raw Allocations Table + raw_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0') + raw_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0') + + raw_allocations_df = pd.DataFrame({ + 'Point Type': ['Stars', 'Galaxies'], + 'Total Allocation ($Z)': [f"{lockdrop_allocation_stars:,.1f}", f"{lockdrop_allocation_galaxies:,.1f}"], + 'Max Per Point ($Z)': [f"{raw_allocation_per_star:,.6f}", f"{raw_allocation_per_galaxy:,.6f}"] + }) + print_table_with_borders(raw_allocations_df, "šŸ’° RAW PARTICIPANT ALLOCATIONS") + + # Quanta Calculations Table + raw_z_per_star_per_block = raw_allocation_per_star / LOCKDROP_DURATION_BLOCKS if total_stars_locked > 0 else Decimal('0') + raw_z_per_galaxy_per_block = raw_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS if total_galaxies_locked > 0 else Decimal('0') + + quanta_df = pd.DataFrame({ + 'Point Type': ['Stars', 'Galaxies'], + 'Raw Z per Block': [f"{raw_z_per_star_per_block:.15f}", f"{raw_z_per_galaxy_per_block:.15f}"], + 'Adjusted Z per Block (q)': [f"{adjusted_z_per_star_per_block:.6f}", f"{adjusted_z_per_galaxy_per_block:.6f}"] + }) + print_table_with_borders(quanta_df, "āš™ļø QUANTA CALCULATION") + + # Adjusted Allocations Table (after quanta calculation) + percentage_rounding_error_stars = total_rounding_error_stars / TOTAL_SUPPLY * 100 if total_stars_locked > 0 else Decimal('0') + percentage_rounding_error_galaxies = total_rounding_error_galaxies / TOTAL_SUPPLY * 100 if total_galaxies_locked > 0 else Decimal('0') + + # Separate tables for better readability + star_adjusted_df = pd.DataFrame({ + 'Metric': ['Adjusted Max per Star', 'Rounding Error per Star', 'Total Rounding Error', 'Rounding Error %'], + 'Value': [f"{adjusted_max_allocation_per_star:,.6f} $Z", f"{rounding_error_per_star:.9f} $Z", + f"{total_rounding_error_stars:,.6f} $Z", f"{percentage_rounding_error_stars:.8f}%"], + 'Note': ['Before bonus', 'Per star loss', 'Goes to bonus pool', 'Of total supply'] + }) + + galaxy_adjusted_df = pd.DataFrame({ + 'Metric': ['Adjusted Max per Galaxy', 'Rounding Error per Galaxy', 'Total Rounding Error', 'Rounding Error %'], + 'Value': [f"{adjusted_max_allocation_per_galaxy:,.6f} $Z", f"{rounding_error_per_galaxy:.9f} $Z", + f"{total_rounding_error_galaxies:,.6f} $Z", f"{percentage_rounding_error_galaxies:.8f}%"], + 'Note': ['Before bonus', 'Per galaxy loss', 'Goes to bonus pool', 'Of total supply'] + }) + + print("\nšŸŽÆ QUANTA ADJUSTED PARTICIPANT ALLOCATIONS") + print_table_with_borders(star_adjusted_df, "⭐ Star Adjustments") + print_table_with_borders(galaxy_adjusted_df, "🌌 Galaxy Adjustments") + print("\n" + "="*80) + + +def print_penalty_analysis(allocation_data): + """Print penalty system analysis using values before bonus calculations.""" + print("=" * 80) + print("āš–ļø PENALTY SYSTEM ANALYSIS") + print("=" * 80) + + # Use adjusted allocations (after penalties, before bonus distribution) + adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] + adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] + + penalty_analysis_df = pd.DataFrame({ + 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], + 'Penalty Rate': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], + 'Star Allocation ($Z)': [f"{adjusted_max_allocation_per_star * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]], + 'Galaxy Allocation ($Z)': [f"{adjusted_max_allocation_per_galaxy * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]], + 'vs Max Allocation': ['100%', '80%', '60%', '40%', '20%'] + }) + + print_table_with_borders(penalty_analysis_df, "šŸ“Š PENALTY ADJUSTED ALLOCATIONS (Before Bonus Distribution)") + print("\n" + "="*80) + + +def print_participation_summary(allocation_data): + """Print participation distribution summary.""" + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + total_stars_locked = allocation_data['total_stars_locked'] + total_galaxies_locked = allocation_data['total_galaxies_locked'] + + # Consolidated participation and lock period distribution + participation_df = pd.DataFrame({ + 'Lock Period': ['1 Year', '2 Years', '3 Years', '4 Years', '5 Years', 'Total'], + 'Stars': [f"{stars_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked:,}"], + '% of Total Stars': [f"{stars_counts[year]/NUM_STARS:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked/NUM_STARS:.1%}"], + 'Galaxies': [f"{galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked:,}"], + '% of Total Galaxies': [f"{galaxies_counts[year]/NUM_GALAXIES:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked/NUM_GALAXIES:.1%}"], + 'Total': [f"{stars_counts[year] + galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked + total_galaxies_locked:,}"], + }) + + print("=" * 80) + print_table_with_borders(participation_df, "šŸŽÆ PARTICIPANTS LOCK PERIOD DISTRIBUTION") + print("\n" + "="*80) + + +def print_bonus_pool_calculations(allocation_data, bonus_data): + """Print detailed bonus pool calculations.""" + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] + adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] + total_rounding_error_stars = allocation_data['total_rounding_error_stars'] + total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] + + print("=" * 80) + print("šŸŽ BONUS POOL CALCULATIONS") + print("=" * 80) + + # Star Bonus Pool Analysis + star_bonus_df = pd.DataFrame({ + 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Star Bonus Pool', + 'Recipients (5Y Stars)', 'Bonus per 5Y Star', 'Final 5Y Star Allocation'], + 'Value': [f"{bonus_data['star_penalty_pool']:,.6f} $Z", f"{total_rounding_error_stars:,.6f} $Z", + f"{bonus_data['star_bonus_pool_total']:,.6f} $Z", f"{stars_counts[5]:,}", + f"{bonus_data['bonus_per_star_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_star + bonus_data['bonus_per_star_5_years']:,.6f} $Z"] + }) + print_table_with_borders(star_bonus_df, "⭐ STAR BONUS POOL ANALYSIS") + + # Galaxy Bonus Pool Analysis + galaxy_bonus_df = pd.DataFrame({ + 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Galaxy Bonus Pool', + 'Recipients (5Y Galaxies)', 'Bonus per 5Y Galaxy', 'Final 5Y Galaxy Allocation'], + 'Value': [f"{bonus_data['galaxy_penalty_pool']:,.6f} $Z", f"{total_rounding_error_galaxies:,.6f} $Z", + f"{bonus_data['galaxy_bonus_pool_total']:,.6f} $Z", f"{galaxies_counts[5]:,}", + f"{bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_galaxy + bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z"] + }) + print_table_with_borders(galaxy_bonus_df, "🌌 GALAXY BONUS POOL ANALYSIS") + + print("\n" + "="*80) + + +def print_final_allocations_and_verification(allocation_data, final_data): + """Print final allocations and verification tables.""" + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + final_star_allocations = final_data['final_star_allocations'] + final_galaxy_allocations = final_data['final_galaxy_allocations'] + star_z_per_block = final_data['star_z_per_block'] + galaxy_z_per_block = final_data['galaxy_z_per_block'] + total_stars_allocation = final_data['total_stars_allocation'] + total_galaxies_allocation = final_data['total_galaxies_allocation'] + + print("=" * 80) + print("āœ… FINAL ALLOCATIONS & VERIFICATION") + print("=" * 80) + + # Star Final Allocations Table + star_final_df = pd.DataFrame({ + 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], + 'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], + 'Final Allocation ($Z)': [f"{final_star_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]], + 'Z per Block': [f"{star_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]], + 'Participants': [f"{stars_counts[year]:,}" for year in [5, 4, 3, 2, 1]] + }) + print_table_with_borders(star_final_df, "⭐ FINAL STAR ALLOCATIONS") + + # Galaxy Final Allocations Table + galaxy_final_df = pd.DataFrame({ + 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], + 'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], + 'Final Allocation ($Z)': [f"{final_galaxy_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]], + 'Z per Block': [f"{galaxy_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]], + 'Participants': [f"{galaxies_counts[year]:,}" for year in [5, 4, 3, 2, 1]] + }) + print_table_with_borders(galaxy_final_df, "🌌 FINAL GALAXY ALLOCATIONS") + + # Verification and Rounding Error Analysis + lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT + lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT + final_rounding_error_stars = lockdrop_allocation_stars - total_stars_allocation + final_rounding_error_galaxies = lockdrop_allocation_galaxies - total_galaxies_allocation + final_rounding_error = final_rounding_error_stars + final_rounding_error_galaxies + + verification_df = pd.DataFrame({ + 'Category': ['Star Allocations', 'Galaxy Allocations', 'Combined'], + 'Calculated Total': [f"{total_stars_allocation:.8f} $Z", f"{total_galaxies_allocation:.8f} $Z", + f"{total_stars_allocation + total_galaxies_allocation:.8f} $Z"], + 'Expected Total': [f"{lockdrop_allocation_stars:.8f} $Z", f"{lockdrop_allocation_galaxies:.8f} $Z", + f"{LOCKDROP_ALLOCATION:.8f} $Z"], + 'Rounding Error': [f"{final_rounding_error_stars:.8f} $Z", f"{final_rounding_error_galaxies:.8f} $Z", + f"{final_rounding_error:.8f} $Z"] + }) + print_table_with_borders(verification_df, "šŸ” ALLOCATION VERIFICATION") + print("\nšŸ“ NOTE: Final rounding errors go to Zenith Foundation") + print("\n" + "="*80) + + +def print_analysis_tables(allocation_data, bonus_data, final_data): + """Print comprehensive analysis tables - calls all the detailed sections.""" + print_participation_summary(allocation_data) + print_allocation_calculations(allocation_data) + print_penalty_analysis(allocation_data) + print_bonus_pool_calculations(allocation_data, bonus_data) + print_final_allocations_and_verification(allocation_data, final_data) diff --git a/lockdrop/simulation.py b/lockdrop/simulation.py new file mode 100644 index 0000000..dab4fd5 --- /dev/null +++ b/lockdrop/simulation.py @@ -0,0 +1,129 @@ +""" +Simulation-specific functions for processing zenith-stack generated data. + +This module contains functions specifically for processing simulation data +generated by the zenith-stack project, including watcher events and participant data. +""" + +import json +import os +import urbitob +from decimal import Decimal +from collections import defaultdict + +from .constants import NUM_GALAXIES +from .calculations import ( + calculate_dynamic_allocations, calculate_bonus_pools, + calculate_final_allocations, generate_test_output +) +from .display import print_analysis_tables + + +def simulation_load_watcher_events(file_path): + """Load and parse PointLockedEvent events from JSON file (simulation-specific).""" + with open(file_path, 'r') as f: + data = json.load(f) + + # Filter for only PointLockedEvent events during loading + point_locked_events = [ + event_data for event_data in data['data']['eventsInRange'] + if event_data['event']['__typename'] == 'PointLockedEvent' + ] + + return point_locked_events + + +def simulation_analyze_lockdrop_events(events): + """Analyze PointLockedEvent events and return participation statistics (simulation-specific).""" + # Initialize counters + lock_duration_counts = { + 'star': defaultdict(int), + 'galaxy': defaultdict(int) + } + + # Process events (already filtered to PointLockedEvent only) + for event_data in events: + point = event_data['event']['point'] + lock_period = event_data['event']['lock_period'] + + # Determine if it's a galaxy or star + point_num = urbitob.patp_to_num(point) + point_type = "galaxy" if point_num < NUM_GALAXIES else "star" + + # Count by lock period + lock_duration_counts[point_type][lock_period] += 1 + + # Extract counts for each year and point type + result = {} + for years in [1, 2, 3, 4, 5]: + result[f'stars_{years}_years'] = lock_duration_counts['star'][years] + result[f'galaxies_{years}_years'] = lock_duration_counts['galaxy'][years] + + return result + + +def run_simulation_analysis(generated_dir=None): + """Complete simulation analysis pipeline (simulation-specific).""" + # Determine generated directory + if generated_dir is None: + generated_dir = os.getenv('GENERATED_DIR', 'generated') + + # Load and analyze watcher events + watcher_events_path = os.path.join(generated_dir, 'watcher-events.json') + + print("=" * 80) + print("šŸ“ WATCHER EVENTS DATA SUMMARY") + print("=" * 80) + + try: + point_locked_events = simulation_load_watcher_events(watcher_events_path) + + if point_locked_events: + total_events = len(point_locked_events) + print(f"āœ… Successfully loaded {total_events} PointLockedEvent records from watcher file") + else: + print("āš ļø No PointLockedEvent events found") + return None + + # Analyze events and convert to Decimal + lock_stats = simulation_analyze_lockdrop_events(point_locked_events) + participation_counts = {k: Decimal(v) for k, v in lock_stats.items()} + + # Run calculations + allocation_data = calculate_dynamic_allocations(participation_counts) + bonus_data = calculate_bonus_pools(allocation_data) + final_data = calculate_final_allocations(allocation_data, bonus_data) + + # Print comprehensive analysis + print_analysis_tables(allocation_data, bonus_data, final_data) + + # Generate and save test output + test_output = generate_test_output(final_data) + allocations_output_file = 'lockdrop_allocations_notebook.json' + + export_data = { + 'metadata': { + 'source': 'lockdrop-calculations-simulated.ipynb', + 'participation_counts': lock_stats + }, + 'allocations': test_output + } + + with open(allocations_output_file, 'w') as f: + json.dump(export_data, f, indent=2) + + print("=" * 80) + print("šŸ’¾ JSON OUTPUT GENERATED") + print(f"āœ… Final allocations saved to: {allocations_output_file}") + print("=" * 80) + + # Return data for visualization + return allocation_data, final_data + + except FileNotFoundError: + print(f"āŒ Watcher events file not found: {watcher_events_path}") + print(" Make sure GENERATED_DIR environment variable points to the correct directory") + return None + except Exception as e: + print(f"āŒ Error during simulation analysis: {e}") + return None diff --git a/lockdrop/visualization.py b/lockdrop/visualization.py new file mode 100644 index 0000000..6111f9b --- /dev/null +++ b/lockdrop/visualization.py @@ -0,0 +1,121 @@ +""" +Visualization functions for lockdrop analysis. + +This module contains functions for creating charts and plots to visualize +lockdrop participation and allocation data. +""" + +import matplotlib.pyplot as plt +import seaborn as sns + + +def configure_matplotlib(): + """Configure matplotlib settings for consistent plots.""" + plt.style.use('seaborn-v0_8') + sns.set_palette("husl") + + plt.rcParams['figure.figsize'] = (12, 8) + plt.rcParams['font.size'] = 11 + plt.rcParams['axes.titlesize'] = 14 + plt.rcParams['axes.labelsize'] = 12 + plt.rcParams['xtick.labelsize'] = 10 + plt.rcParams['ytick.labelsize'] = 10 + plt.rcParams['legend.fontsize'] = 10 + plt.rcParams['axes.unicode_minus'] = False + + try: + plt.rcParams['font.family'] = 'DejaVu Sans' + except: + plt.rcParams['font.family'] = 'sans-serif' + plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Liberation Sans', 'Arial', 'Helvetica'] + + +def create_visualization(allocation_data, final_data): + """Create comprehensive visualization plots.""" + stars_counts = allocation_data['stars_counts'] + galaxies_counts = allocation_data['galaxies_counts'] + final_star_allocations = final_data['final_star_allocations'] + final_galaxy_allocations = final_data['final_galaxy_allocations'] + + fig = plt.figure(figsize=(20, 10)) + gs = fig.add_gridspec(2, 3, hspace=0.35, wspace=0.35) + + fig.suptitle('Lockdrop Analysis', fontsize=18, fontweight='bold', y=0.98) + + # 1. Star Participation Distribution + ax1 = fig.add_subplot(gs[0, 0]) + star_years = [1, 2, 3, 4, 5] + star_counts = [stars_counts[year] for year in star_years] + + bars1 = ax1.bar(range(len(star_years)), star_counts, + color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8) + ax1.set_xlabel('Lock Period (Years)') + ax1.set_ylabel('Number of Stars') + ax1.set_title('Star Participation by Lock Period', fontweight='bold') + ax1.set_xticks(range(len(star_years))) + ax1.set_xticklabels(star_years) + + for i, count in enumerate(star_counts): + height = bars1[i].get_height() + ax1.text(bars1[i].get_x() + bars1[i].get_width()/2., height + height*0.01, + f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9) + + # 2. Galaxy Participation Distribution + ax2 = fig.add_subplot(gs[0, 1]) + galaxy_counts = [galaxies_counts[year] for year in star_years] + + bars2 = ax2.bar(range(len(star_years)), galaxy_counts, + color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8) + ax2.set_xlabel('Lock Period (Years)') + ax2.set_ylabel('Number of Galaxies') + ax2.set_title('Galaxy Participation by Lock Period', fontweight='bold') + ax2.set_xticks(range(len(star_years))) + ax2.set_xticklabels(star_years) + + for i, count in enumerate(galaxy_counts): + height = bars2[i].get_height() + ax2.text(bars2[i].get_x() + bars2[i].get_width()/2., height + height*0.01, + f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9) + + # 3. Star Allocations + ax3 = fig.add_subplot(gs[0, 2]) + star_alloc_values = [float(final_star_allocations[year]) for year in star_years] + + bars3 = ax3.bar(range(len(star_years)), star_alloc_values, + color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8) + ax3.set_xlabel('Lock Period (Years)') + ax3.set_ylabel('Allocation per Star ($Z)') + ax3.set_title('Star Allocations by Lock Period', fontweight='bold') + ax3.set_xticks(range(len(star_years))) + ax3.set_xticklabels(star_years) + + for i, allocation in enumerate(star_alloc_values): + height = bars3[i].get_height() + ax3.text(bars3[i].get_x() + bars3[i].get_width()/2., height + height*0.01, + f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9) + + # 4. Galaxy Allocations + ax4 = fig.add_subplot(gs[1, 0]) + galaxy_alloc_values = [float(final_galaxy_allocations[year]) for year in star_years] + + bars4 = ax4.bar(range(len(star_years)), galaxy_alloc_values, + color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8) + ax4.set_xlabel('Lock Period (Years)') + ax4.set_ylabel('Allocation per Galaxy ($Z)') + ax4.set_title('Galaxy Allocations by Lock Period', fontweight='bold') + ax4.set_xticks(range(len(star_years))) + ax4.set_xticklabels(star_years) + + for i, allocation in enumerate(galaxy_alloc_values): + height = bars4[i].get_height() + ax4.text(bars4[i].get_x() + bars4[i].get_width()/2., height + height*0.01, + f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9) + + # Hide remaining subplots + ax5 = fig.add_subplot(gs[1, 1]) + ax5.axis('off') + ax6 = fig.add_subplot(gs[1, 2]) + ax6.axis('off') + + plt.subplots_adjust(bottom=0.08, top=0.88, left=0.05, right=0.98) + plt.show() diff --git a/lockdrop/widgets.py b/lockdrop/widgets.py new file mode 100644 index 0000000..d68628c --- /dev/null +++ b/lockdrop/widgets.py @@ -0,0 +1,304 @@ +""" +Jupyter widget functions for interactive lockdrop experimentation. + +This module contains functions for creating and managing interactive widgets +for the experimental lockdrop notebook. +""" + +import json +import traceback +import ipywidgets as widgets +from datetime import datetime +from IPython.display import clear_output + +from .constants import NUM_STARS, NUM_GALAXIES, SCENARIOS +from .calculations import ( + calculate_dynamic_allocations, calculate_bonus_pools, + calculate_final_allocations, generate_test_output +) +from .display import print_analysis_tables, print_table_with_borders +from .visualization import create_visualization + + +def create_experiment_widgets(): + """Create input widgets for the experimental notebook.""" + # Define reasonable default values + default_participants = { + 'stars_1_years': 8000, + 'stars_2_years': 8000, + 'stars_3_years': 8000, + 'stars_4_years': 8000, + 'stars_5_years': 8000, + 'galaxies_1_years': 40, + 'galaxies_2_years': 40, + 'galaxies_3_years': 40, + 'galaxies_4_years': 40, + 'galaxies_5_years': 40 + } + + # Create input widgets + print("="*50) + + # Star participation widgets with bounds + stars_1_years = widgets.BoundedIntText(value=default_participants['stars_1_years'], min=0, max=NUM_STARS, description='1 Year:', style={'description_width': '80px'}) + stars_2_years = widgets.BoundedIntText(value=default_participants['stars_2_years'], min=0, max=NUM_STARS, description='2 Years:', style={'description_width': '80px'}) + stars_3_years = widgets.BoundedIntText(value=default_participants['stars_3_years'], min=0, max=NUM_STARS, description='3 Years:', style={'description_width': '80px'}) + stars_4_years = widgets.BoundedIntText(value=default_participants['stars_4_years'], min=0, max=NUM_STARS, description='4 Years:', style={'description_width': '80px'}) + stars_5_years = widgets.BoundedIntText(value=default_participants['stars_5_years'], min=0, max=NUM_STARS, description='5 Years:', style={'description_width': '80px'}) + + star_controls = widgets.VBox([stars_1_years, stars_2_years, stars_3_years, stars_4_years, stars_5_years]) + + galaxies_1_years = widgets.BoundedIntText(value=default_participants['galaxies_1_years'], min=0, max=NUM_GALAXIES, description='1 Year:', style={'description_width': '80px'}) + galaxies_2_years = widgets.BoundedIntText(value=default_participants['galaxies_2_years'], min=0, max=NUM_GALAXIES, description='2 Years:', style={'description_width': '80px'}) + galaxies_3_years = widgets.BoundedIntText(value=default_participants['galaxies_3_years'], min=0, max=NUM_GALAXIES, description='3 Years:', style={'description_width': '80px'}) + galaxies_4_years = widgets.BoundedIntText(value=default_participants['galaxies_4_years'], min=0, max=NUM_GALAXIES, description='4 Years:', style={'description_width': '80px'}) + galaxies_5_years = widgets.BoundedIntText(value=default_participants['galaxies_5_years'], min=0, max=NUM_GALAXIES, description='5 Years:', style={'description_width': '80px'}) + + galaxy_controls = widgets.VBox([galaxies_1_years, galaxies_2_years, galaxies_3_years, galaxies_4_years, galaxies_5_years]) + + # Preset scenario dropdown + scenario_dropdown = widgets.Dropdown( + options=[('Select Preset', '')] + [(scenario['description'], k) for k, scenario in SCENARIOS.items()], + description='Preset:', + style={'description_width': '60px'}, + layout={'width': '400px'} + ) + + # Calculate button + calculate_button = widgets.Button(description='šŸ”„ Calculate Allocations', button_style='success', layout={'width': '200px', 'margin': '10px 5px'}) + + # Export button + export_button = widgets.Button(description='šŸ’¾ Export Results', button_style='info', layout={'width': '180px', 'margin': '10px 5px'}) + + # Output area + output_area = widgets.Output() + + # Set up preset selection handler + def on_preset_change(change): + if change['new'] and change['new'] != '': + scenario = SCENARIOS[change['new']] + + # Update widget values + stars_1_years.value = scenario['stars']['1_year'] + stars_2_years.value = scenario['stars']['2_year'] + stars_3_years.value = scenario['stars']['3_year'] + stars_4_years.value = scenario['stars']['4_year'] + stars_5_years.value = scenario['stars']['5_year'] + galaxies_1_years.value = scenario['galaxies']['1_year'] + galaxies_2_years.value = scenario['galaxies']['2_year'] + galaxies_3_years.value = scenario['galaxies']['3_year'] + galaxies_4_years.value = scenario['galaxies']['4_year'] + galaxies_5_years.value = scenario['galaxies']['5_year'] + + scenario_dropdown.observe(on_preset_change, names='value') + + # Set up export button handler + def on_export_click(button=None): + """Handle export button click with timestamp.""" + # Generate timestamp for filename + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f'lockdrop_calculations_result_{timestamp}.json' + + # Get current values from widgets + participation_counts = { + 'stars_1_years': stars_1_years.value, + 'stars_2_years': stars_2_years.value, + 'stars_3_years': stars_3_years.value, + 'stars_4_years': stars_4_years.value, + 'stars_5_years': stars_5_years.value, + 'galaxies_1_years': galaxies_1_years.value, + 'galaxies_2_years': galaxies_2_years.value, + 'galaxies_3_years': galaxies_3_years.value, + 'galaxies_4_years': galaxies_4_years.value, + 'galaxies_5_years': galaxies_5_years.value + } + + try: + # Calculate allocations + allocation_data = calculate_dynamic_allocations(participation_counts) + bonus_data = calculate_bonus_pools(allocation_data) + final_data = calculate_final_allocations(allocation_data, bonus_data) + + # Generate test output + test_output = generate_test_output(final_data) + + # Add metadata + export_data = { + 'metadata': { + 'source': 'lockdrop-calculations.ipynb', + 'timestamp': timestamp, + 'participation_counts': participation_counts + }, + 'allocations': test_output + } + + # Save to file + with open(filename, 'w') as f: + json.dump(export_data, f, indent=2) + + print(f"āœ… Results exported to: {filename}") + + except Exception as e: + print(f"āŒ Export failed: {str(e)}") + + export_button.on_click(on_export_click) + + # Return all widgets as a dictionary + return { + 'star_controls': star_controls, + 'galaxy_controls': galaxy_controls, + 'scenario_dropdown': scenario_dropdown, + 'calculate_button': calculate_button, + 'export_button': export_button, + 'output_area': output_area, + 'widgets': { + 'stars_1_years': stars_1_years, + 'stars_2_years': stars_2_years, + 'stars_3_years': stars_3_years, + 'stars_4_years': stars_4_years, + 'stars_5_years': stars_5_years, + 'galaxies_1_years': galaxies_1_years, + 'galaxies_2_years': galaxies_2_years, + 'galaxies_3_years': galaxies_3_years, + 'galaxies_4_years': galaxies_4_years, + 'galaxies_5_years': galaxies_5_years + } + } + + +def create_calculate_function(widget_dict): + """Create the calculation and display function for the experimental notebook.""" + from decimal import Decimal + import pandas as pd + + def calculate_and_display(button=None): + """Calculate allocations and display results based on input parameters.""" + + widgets_map = widget_dict['widgets'] + output_area = widget_dict['output_area'] + + with output_area: + clear_output(wait=True) + + # Get current values from widgets + participation_counts = { + 'stars_1_years': widgets_map['stars_1_years'].value, + 'stars_2_years': widgets_map['stars_2_years'].value, + 'stars_3_years': widgets_map['stars_3_years'].value, + 'stars_4_years': widgets_map['stars_4_years'].value, + 'stars_5_years': widgets_map['stars_5_years'].value, + 'galaxies_1_years': widgets_map['galaxies_1_years'].value, + 'galaxies_2_years': widgets_map['galaxies_2_years'].value, + 'galaxies_3_years': widgets_map['galaxies_3_years'].value, + 'galaxies_4_years': widgets_map['galaxies_4_years'].value, + 'galaxies_5_years': widgets_map['galaxies_5_years'].value + } + + # Validate inputs + total_stars = sum(participation_counts[key] for key in participation_counts if 'stars' in key) + total_galaxies = sum(participation_counts[key] for key in participation_counts if 'galaxies' in key) + + if total_stars > NUM_STARS: + print(f"āš ļø ERROR: Total stars ({total_stars:,}) exceeds maximum available ({NUM_STARS:,})") + return + + if total_galaxies > NUM_GALAXIES: + print(f"āš ļø ERROR: Total galaxies ({total_galaxies:,}) exceeds maximum available ({NUM_GALAXIES:,})") + return + + if total_stars == 0 and total_galaxies == 0: + print("āš ļø ERROR: Must have at least some participants") + return + + try: + # Perform calculations + print("šŸ”„ Calculating allocations...\n") + + allocation_data = calculate_dynamic_allocations(participation_counts) + bonus_data = calculate_bonus_pools(allocation_data) + final_data = calculate_final_allocations(allocation_data, bonus_data) + + # Display results + print_analysis_tables(allocation_data, bonus_data, final_data) + + # Create visualization + print("\nšŸ“Š Generating visualization...") + create_visualization(allocation_data, final_data) + + # Calculate participation insights + star_participation_rate = Decimal(total_stars) / NUM_STARS + galaxy_participation_rate = Decimal(total_galaxies) / NUM_GALAXIES + + print("\n" + "="*80) + print("šŸŽÆ EXPERIMENT INSIGHTS") + print("=" * 80) + + insights_df = pd.DataFrame({ + 'Metric': [ + 'Total Participants', + 'Star Participation Rate', + 'Galaxy Participation Rate', + '5-Year Star Bonus', + '5-Year Galaxy Bonus', + 'Highest Individual Allocation', + 'Lowest Individual Allocation' + ], + 'Value': [ + f"{total_stars + total_galaxies:,}", + f"{star_participation_rate:.1%}", + f"{galaxy_participation_rate:.1%}", + f"{float(bonus_data['bonus_per_star_5_years']):,.2f} $Z" if participation_counts['stars_5_years'] > 0 else "N/A (no 5Y stars)", + f"{float(bonus_data['bonus_per_galaxy_5_years']):,.2f} $Z" if participation_counts['galaxies_5_years'] > 0 else "N/A (no 5Y galaxies)", + f"{max(float(final_data['final_star_allocations'][5]), float(final_data['final_galaxy_allocations'][5])):,.2f} $Z", + f"{min(float(final_data['final_star_allocations'][1]), float(final_data['final_galaxy_allocations'][1])):,.2f} $Z" + ] + }) + print_table_with_borders(insights_df, "šŸŽÆ EXPERIMENT INSIGHTS") + print("\n" + "="*80) + + except Exception as e: + print(f"āŒ Error during calculation: {str(e)}") + traceback.print_exc() + + return calculate_and_display + + +def create_experimental_interface(): + """Create complete experimental interface with widgets and handlers.""" + from IPython.display import display + + # Create all widgets + widget_dict = create_experiment_widgets() + + # Create calculation function + calculate_function = create_calculate_function(widget_dict) + + # Wire up the calculate button + widget_dict['calculate_button'].on_click(calculate_function) + + # Display the interface - preset dropdown first + preset_label = widgets.HTML('

šŸŽ® Preset Scenarios

') + preset_layout = widgets.VBox([preset_label, widget_dict['scenario_dropdown']]) + + # Then participation controls + star_label = widgets.HTML('

⭐ Star Participation

') + galaxy_label = widgets.HTML('

🌌 Galaxy Participation

') + + input_layout = widgets.HBox([ + widgets.VBox([star_label, widget_dict['star_controls']]), + widgets.VBox([galaxy_label, widget_dict['galaxy_controls']]) + ]) + + # Action buttons + controls_layout = widgets.HBox([ + widget_dict['calculate_button'], + widget_dict['export_button'] + ]) + + # Display in order: preset first, then participation controls, then buttons + display(preset_layout) + display(input_layout) + display(controls_layout) + display(widget_dict['output_area']) + + return None diff --git a/lockdrop_calculations.py b/lockdrop_calculations.py deleted file mode 100644 index 03c610e..0000000 --- a/lockdrop_calculations.py +++ /dev/null @@ -1,1077 +0,0 @@ -""" -Shared lockdrop calculation functions and utilities. - -This module contains the reference calculation logic for lockdrop token distribution, -penalty systems, bonus pools, and final allocations. It can be used by both -the simulation notebook and the experimental notebook. -""" - -import pandas as pd -import matplotlib.pyplot as plt -import seaborn as sns -import json -import os -import urbitob -import ipywidgets as widgets -import traceback - -from decimal import Decimal, ROUND_DOWN, getcontext -from collections import defaultdict -from tabulate import tabulate -from datetime import datetime -from IPython.display import display, clear_output - -# Configure decimal precision -getcontext().prec = 28 -getcontext().rounding = ROUND_DOWN - -# Constants -TOKEN_PRECISION = Decimal('0.00000001') # 8 decimal precision -TOTAL_SUPPLY = 4294967296 # 1 $Z per Urbit ID -LOCKDROP_ALLOCATION_PERCENT = Decimal('0.3') -LOCKDROP_ALLOCATION = TOTAL_SUPPLY * LOCKDROP_ALLOCATION_PERCENT - -# Points -NUM_GALAXIES = pow(2, 8) -NUM_STARS = pow(2, 16) - pow(2, 8) -NUM_PLANETS = pow(2, 32) - pow(2, 16) - -# Allocation distribution -STAR_ALLOCATION_PERCENT = Decimal(NUM_STARS / pow(2, 16)) -GALAXY_ALLOCATION_PERCENT = 1 - STAR_ALLOCATION_PERCENT - -# Lockdrop duration (5 years including 1 leap year) -LOCKDROP_DURATION_SECONDS = 5 * 365.25 * 24 * 60 * 60 -BLOCK_DURATION_SECONDS = 2 -LOCKDROP_DURATION_BLOCKS = int(LOCKDROP_DURATION_SECONDS / BLOCK_DURATION_SECONDS) - -# Penalty rates -PENALTY_RATES = { - 5: Decimal('0'), - 4: Decimal('0.2'), - 3: Decimal('0.4'), - 2: Decimal('0.6'), - 1: Decimal('0.8') -} - - -def print_table_with_borders(df, title=""): - """Print DataFrame with borders using tabulate.""" - if title: - print(f"\n{title}") - print(tabulate(df, headers='keys', tablefmt='grid', showindex=False)) - - -def configure_matplotlib(): - """Configure matplotlib settings for consistent plots.""" - plt.style.use('seaborn-v0_8') - sns.set_palette("husl") - - plt.rcParams['figure.figsize'] = (12, 8) - plt.rcParams['font.size'] = 11 - plt.rcParams['axes.titlesize'] = 14 - plt.rcParams['axes.labelsize'] = 12 - plt.rcParams['xtick.labelsize'] = 10 - plt.rcParams['ytick.labelsize'] = 10 - plt.rcParams['legend.fontsize'] = 10 - plt.rcParams['axes.unicode_minus'] = False - - try: - plt.rcParams['font.family'] = 'DejaVu Sans' - except: - plt.rcParams['font.family'] = 'sans-serif' - plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Liberation Sans', 'Arial', 'Helvetica'] - - -def print_constants_summary(): - """Display core constants as formatted tables.""" - print("=" * 80) - print("šŸ“Š $Z LOCKDROP DISTRIBUTION - CORE CONSTANTS") - print("=" * 80) - - # Lockdrop Allocation Table - lockdrop_df = pd.DataFrame({ - 'Parameter': ['Total Supply (1 $Z per Urbit ID)', 'Lockdrop Allocation %', 'Lockdrop Allocation ($Z)'], - 'Value': [f"{TOTAL_SUPPLY:,}", f"{LOCKDROP_ALLOCATION_PERCENT:.1%}", f"{LOCKDROP_ALLOCATION:,.1f}"] - }) - print_table_with_borders(lockdrop_df, "šŸ”’ LOCKDROP ALLOCATION") - - # Points Distribution Table - points_df = pd.DataFrame({ - 'Point Type': ['Galaxies', 'Stars', 'Planets'], - 'Count': [f"{NUM_GALAXIES:,}", f"{NUM_STARS:,}", f"{NUM_PLANETS:,}"], - 'Allocation %': ['0.39%', '99.61%', '0%'] - }) - print_table_with_borders(points_df, "⭐ URBIT POINTS DISTRIBUTION") - - # Lockdrop Parameters Table - params_df = pd.DataFrame({ - 'Parameter': ['Block Duration', 'Max Point Lock Duration (5 yrs)', 'Total Blocks', 'Star Allocation %', 'Galaxy Allocation %'], - 'Value': [f"{BLOCK_DURATION_SECONDS} seconds", f"{LOCKDROP_DURATION_SECONDS:,.0f} seconds", - f"{LOCKDROP_DURATION_BLOCKS:,}", f"{STAR_ALLOCATION_PERCENT:.6%}", f"{GALAXY_ALLOCATION_PERCENT:.6%}"] - }) - print_table_with_borders(params_df, "ā±ļø LOCKDROP PARAMETERS") - print("\n" + "="*80) - - -def calculate_base_allocations(): - """Calculate base allocation amounts and quanta.""" - lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT - lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT - - assert (lockdrop_allocation_stars + lockdrop_allocation_galaxies) == LOCKDROP_ALLOCATION, "point allocation doesn't add up" - - # Max allocation per point (theoretical) - max_allocation_per_star = lockdrop_allocation_stars / NUM_STARS - max_allocation_per_galaxy = lockdrop_allocation_galaxies / NUM_GALAXIES - - # Quanta calculation - z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS - z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS - - # Round down to 6 decimals - adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) - adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) - - # Adjusted max allocation - adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS - adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS - - return { - 'lockdrop_allocation_stars': lockdrop_allocation_stars, - 'lockdrop_allocation_galaxies': lockdrop_allocation_galaxies, - 'max_allocation_per_star': max_allocation_per_star, - 'max_allocation_per_galaxy': max_allocation_per_galaxy, - 'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star, - 'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy, - 'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block, - 'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block - } - - -def calculate_dynamic_allocations(participation_counts): - """ - Calculate allocations based on actual participation counts. - - Args: - participation_counts: Dict with keys like 'stars_1_years', 'stars_2_years', etc. - """ - # Extract participation data - stars_counts = {year: Decimal(participation_counts[f'stars_{year}_years']) for year in range(1, 6)} - galaxies_counts = {year: Decimal(participation_counts[f'galaxies_{year}_years']) for year in range(1, 6)} - - total_stars_locked = sum(stars_counts.values()) - total_galaxies_locked = sum(galaxies_counts.values()) - - lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT - lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT - - # Dynamic allocations based on actual participation - max_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0') - max_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0') - - # Quanta calculation - z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS - z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS - - # Round down to 6 decimals - adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) - adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) - - # Adjusted max allocation - adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS - adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS - - # Rounding errors - rounding_error_per_star = max_allocation_per_star - adjusted_max_allocation_per_star - rounding_error_per_galaxy = max_allocation_per_galaxy - adjusted_max_allocation_per_galaxy - - total_rounding_error_stars = lockdrop_allocation_stars - (adjusted_max_allocation_per_star * total_stars_locked) - total_rounding_error_galaxies = lockdrop_allocation_galaxies - (adjusted_max_allocation_per_galaxy * total_galaxies_locked) - - return { - 'stars_counts': stars_counts, - 'galaxies_counts': galaxies_counts, - 'total_stars_locked': total_stars_locked, - 'total_galaxies_locked': total_galaxies_locked, - 'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star, - 'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy, - 'rounding_error_per_star': rounding_error_per_star, - 'rounding_error_per_galaxy': rounding_error_per_galaxy, - 'total_rounding_error_stars': total_rounding_error_stars, - 'total_rounding_error_galaxies': total_rounding_error_galaxies, - 'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block, - 'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block - } - - -def calculate_bonus_pools(allocation_data): - """Calculate bonus pools from penalties and rounding errors.""" - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] - total_rounding_error_stars = allocation_data['total_rounding_error_stars'] - total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] - - # Calculate penalty pools - star_penalty_pool = Decimal('0') - for years in [1, 2, 3, 4]: - penalty = PENALTY_RATES[years] - star_penalty_pool += adjusted_max_allocation_per_star * stars_counts[years] * penalty - - galaxy_penalty_pool = Decimal('0') - for years in [1, 2, 3, 4]: - penalty = PENALTY_RATES[years] - galaxy_penalty_pool += adjusted_max_allocation_per_galaxy * galaxies_counts[years] * penalty - - star_penalty_pool = star_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - galaxy_penalty_pool = galaxy_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - - # Add rounding errors to bonus pools - star_bonus_pool_total = star_penalty_pool + total_rounding_error_stars - galaxy_bonus_pool_total = galaxy_penalty_pool + total_rounding_error_galaxies - - # Calculate bonus per 5-year participant - stars_5_years = stars_counts[5] - galaxies_5_years = galaxies_counts[5] - - bonus_per_star_5_years = star_bonus_pool_total / stars_5_years if stars_5_years > 0 else Decimal('0') - bonus_per_galaxy_5_years = galaxy_bonus_pool_total / galaxies_5_years if galaxies_5_years > 0 else Decimal('0') - - return { - 'star_penalty_pool': star_penalty_pool, - 'galaxy_penalty_pool': galaxy_penalty_pool, - 'star_bonus_pool_total': star_bonus_pool_total, - 'galaxy_bonus_pool_total': galaxy_bonus_pool_total, - 'bonus_per_star_5_years': bonus_per_star_5_years, - 'bonus_per_galaxy_5_years': bonus_per_galaxy_5_years - } - - -def calculate_final_allocations(allocation_data, bonus_data): - """Calculate final allocations for all lock periods.""" - adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] - bonus_per_star_5_years = bonus_data['bonus_per_star_5_years'] - bonus_per_galaxy_5_years = bonus_data['bonus_per_galaxy_5_years'] - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - - # Calculate final allocations - final_star_allocations = {} - final_galaxy_allocations = {} - - for years in range(1, 6): - penalty = PENALTY_RATES[years] - - if years == 5: - # 5-year participants get base + bonus - final_star_allocations[years] = (adjusted_max_allocation_per_star + bonus_per_star_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy + bonus_per_galaxy_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - else: - # Other years get penalized amounts - final_star_allocations[years] = (adjusted_max_allocation_per_star * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - - # Calculate Z per block for each lock period - star_z_per_block = {} - galaxy_z_per_block = {} - - for years in range(1, 6): - lock_duration_fraction = Decimal(years) / Decimal('5') # Fraction of full 5-year period - effective_blocks = LOCKDROP_DURATION_BLOCKS * lock_duration_fraction - - star_z_per_block[years] = (final_star_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - galaxy_z_per_block[years] = (final_galaxy_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN) - - # Calculate total allocations for verification - total_stars_allocation = sum(final_star_allocations[year] * stars_counts[year] for year in range(1, 6)) - total_galaxies_allocation = sum(final_galaxy_allocations[year] * galaxies_counts[year] for year in range(1, 6)) - - return { - 'final_star_allocations': final_star_allocations, - 'final_galaxy_allocations': final_galaxy_allocations, - 'star_z_per_block': star_z_per_block, - 'galaxy_z_per_block': galaxy_z_per_block, - 'total_stars_allocation': total_stars_allocation, - 'total_galaxies_allocation': total_galaxies_allocation - } - - -def generate_test_output(final_data): - """Generate JSON output for test validation.""" - final_star_allocations = final_data['final_star_allocations'] - final_galaxy_allocations = final_data['final_galaxy_allocations'] - total_allocation = final_data['total_stars_allocation'] + final_data['total_galaxies_allocation'] - - # Convert to $sZ units (multiply by 10^8) - output = { - "stars": { - f"{year}_years": int(final_star_allocations[year] * Decimal('1e8')) - for year in range(1, 6) - }, - "galaxies": { - f"{year}_years": int(final_galaxy_allocations[year] * Decimal('1e8')) - for year in range(1, 6) - }, - "total": int(total_allocation * Decimal('1e8')) - } - - return output - - -def print_allocation_calculations(allocation_data): - """Print basic allocation calculation tables.""" - lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT - lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT - - total_stars_locked = allocation_data['total_stars_locked'] - total_galaxies_locked = allocation_data['total_galaxies_locked'] - rounding_error_per_star = allocation_data['rounding_error_per_star'] - rounding_error_per_galaxy = allocation_data['rounding_error_per_galaxy'] - adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] - adjusted_z_per_star_per_block = allocation_data['adjusted_z_per_star_per_block'] - adjusted_z_per_galaxy_per_block = allocation_data['adjusted_z_per_galaxy_per_block'] - total_rounding_error_stars = allocation_data['total_rounding_error_stars'] - total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] - - print("=" * 80) - print("šŸ”¢ ALLOCATION CALCULATIONS") - print("=" * 80) - - # Raw Allocations Table - raw_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0') - raw_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0') - - raw_allocations_df = pd.DataFrame({ - 'Point Type': ['Stars', 'Galaxies'], - 'Total Allocation ($Z)': [f"{lockdrop_allocation_stars:,.1f}", f"{lockdrop_allocation_galaxies:,.1f}"], - 'Max Per Point ($Z)': [f"{raw_allocation_per_star:,.6f}", f"{raw_allocation_per_galaxy:,.6f}"] - }) - print_table_with_borders(raw_allocations_df, "šŸ’° RAW PARTICIPANT ALLOCATIONS") - - # Quanta Calculations Table - raw_z_per_star_per_block = raw_allocation_per_star / LOCKDROP_DURATION_BLOCKS if total_stars_locked > 0 else Decimal('0') - raw_z_per_galaxy_per_block = raw_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS if total_galaxies_locked > 0 else Decimal('0') - - quanta_df = pd.DataFrame({ - 'Point Type': ['Stars', 'Galaxies'], - 'Raw Z per Block': [f"{raw_z_per_star_per_block:.15f}", f"{raw_z_per_galaxy_per_block:.15f}"], - 'Adjusted Z per Block (q)': [f"{adjusted_z_per_star_per_block:.6f}", f"{adjusted_z_per_galaxy_per_block:.6f}"] - }) - print_table_with_borders(quanta_df, "āš™ļø QUANTA CALCULATION") - - # Adjusted Allocations Table (after quanta calculation) - percentage_rounding_error_stars = total_rounding_error_stars / TOTAL_SUPPLY * 100 if total_stars_locked > 0 else Decimal('0') - percentage_rounding_error_galaxies = total_rounding_error_galaxies / TOTAL_SUPPLY * 100 if total_galaxies_locked > 0 else Decimal('0') - - # Separate tables for better readability - star_adjusted_df = pd.DataFrame({ - 'Metric': ['Adjusted Max per Star', 'Rounding Error per Star', 'Total Rounding Error', 'Rounding Error %'], - 'Value': [f"{adjusted_max_allocation_per_star:,.6f} $Z", f"{rounding_error_per_star:.9f} $Z", - f"{total_rounding_error_stars:,.6f} $Z", f"{percentage_rounding_error_stars:.8f}%"], - 'Note': ['Before bonus', 'Per star loss', 'Goes to bonus pool', 'Of total supply'] - }) - - galaxy_adjusted_df = pd.DataFrame({ - 'Metric': ['Adjusted Max per Galaxy', 'Rounding Error per Galaxy', 'Total Rounding Error', 'Rounding Error %'], - 'Value': [f"{adjusted_max_allocation_per_galaxy:,.6f} $Z", f"{rounding_error_per_galaxy:.9f} $Z", - f"{total_rounding_error_galaxies:,.6f} $Z", f"{percentage_rounding_error_galaxies:.8f}%"], - 'Note': ['Before bonus', 'Per galaxy loss', 'Goes to bonus pool', 'Of total supply'] - }) - - print("\nšŸŽÆ QUANTA ADJUSTED PARTICIPANT ALLOCATIONS") - print_table_with_borders(star_adjusted_df, "⭐ Star Adjustments") - print_table_with_borders(galaxy_adjusted_df, "🌌 Galaxy Adjustments") - print("\n" + "="*80) - - -def print_penalty_analysis(allocation_data): - """Print penalty system analysis using values before bonus calculations.""" - print("=" * 80) - print("āš–ļø PENALTY SYSTEM ANALYSIS") - print("=" * 80) - - # Use adjusted allocations (after penalties, before bonus distribution) - adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] - - penalty_analysis_df = pd.DataFrame({ - 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], - 'Penalty Rate': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], - 'Star Allocation ($Z)': [f"{adjusted_max_allocation_per_star * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]], - 'Galaxy Allocation ($Z)': [f"{adjusted_max_allocation_per_galaxy * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]], - 'vs Max Allocation': ['100%', '80%', '60%', '40%', '20%'] - }) - - print_table_with_borders(penalty_analysis_df, "šŸ“Š PENALTY ADJUSTED ALLOCATIONS (Before Bonus Distribution)") - print("\n" + "="*80) - - -def print_participation_summary(allocation_data): - """Print participation distribution summary.""" - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - total_stars_locked = allocation_data['total_stars_locked'] - total_galaxies_locked = allocation_data['total_galaxies_locked'] - - # Consolidated participation and lock period distribution - participation_df = pd.DataFrame({ - 'Lock Period': ['1 Year', '2 Years', '3 Years', '4 Years', '5 Years', 'Total'], - 'Stars': [f"{stars_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked:,}"], - '% of Total Stars': [f"{stars_counts[year]/NUM_STARS:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked/NUM_STARS:.1%}"], - 'Galaxies': [f"{galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked:,}"], - '% of Total Galaxies': [f"{galaxies_counts[year]/NUM_GALAXIES:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked/NUM_GALAXIES:.1%}"], - 'Total': [f"{stars_counts[year] + galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked + total_galaxies_locked:,}"], - }) - - print("=" * 80) - print_table_with_borders(participation_df, "šŸŽÆ PARTICIPANTS LOCK PERIOD DISTRIBUTION") - print("\n" + "="*80) - - -def print_bonus_pool_calculations(allocation_data, bonus_data): - """Print detailed bonus pool calculations.""" - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy'] - total_rounding_error_stars = allocation_data['total_rounding_error_stars'] - total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies'] - - print("=" * 80) - print("šŸŽ BONUS POOL CALCULATIONS") - print("=" * 80) - - # Star Bonus Pool Analysis - - star_bonus_df = pd.DataFrame({ - 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Star Bonus Pool', - 'Recipients (5Y Stars)', 'Bonus per 5Y Star', 'Final 5Y Star Allocation'], - 'Value': [f"{bonus_data['star_penalty_pool']:,.6f} $Z", f"{total_rounding_error_stars:,.6f} $Z", - f"{bonus_data['star_bonus_pool_total']:,.6f} $Z", f"{stars_counts[5]:,}", - f"{bonus_data['bonus_per_star_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_star + bonus_data['bonus_per_star_5_years']:,.6f} $Z"] - }) - print_table_with_borders(star_bonus_df, "⭐ STAR BONUS POOL ANALYSIS") - - # Galaxy Bonus Pool Analysis - - galaxy_bonus_df = pd.DataFrame({ - 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Galaxy Bonus Pool', - 'Recipients (5Y Galaxies)', 'Bonus per 5Y Galaxy', 'Final 5Y Galaxy Allocation'], - 'Value': [f"{bonus_data['galaxy_penalty_pool']:,.6f} $Z", f"{total_rounding_error_galaxies:,.6f} $Z", - f"{bonus_data['galaxy_bonus_pool_total']:,.6f} $Z", f"{galaxies_counts[5]:,}", - f"{bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_galaxy + bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z"] - }) - print_table_with_borders(galaxy_bonus_df, "🌌 GALAXY BONUS POOL ANALYSIS") - - print("\n" + "="*80) - - -def print_final_allocations_and_verification(allocation_data, final_data): - """Print final allocations and verification tables.""" - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - final_star_allocations = final_data['final_star_allocations'] - final_galaxy_allocations = final_data['final_galaxy_allocations'] - star_z_per_block = final_data['star_z_per_block'] - galaxy_z_per_block = final_data['galaxy_z_per_block'] - total_stars_allocation = final_data['total_stars_allocation'] - total_galaxies_allocation = final_data['total_galaxies_allocation'] - - print("=" * 80) - print("āœ… FINAL ALLOCATIONS & VERIFICATION") - print("=" * 80) - - # Star Final Allocations Table - star_final_df = pd.DataFrame({ - 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], - 'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], - 'Final Allocation ($Z)': [f"{final_star_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]], - 'Z per Block': [f"{star_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]], - 'Participants': [f"{stars_counts[year]:,}" for year in [5, 4, 3, 2, 1]] - }) - print_table_with_borders(star_final_df, "⭐ FINAL STAR ALLOCATIONS") - - # Galaxy Final Allocations Table - galaxy_final_df = pd.DataFrame({ - 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'], - 'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]], - 'Final Allocation ($Z)': [f"{final_galaxy_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]], - 'Z per Block': [f"{galaxy_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]], - 'Participants': [f"{galaxies_counts[year]:,}" for year in [5, 4, 3, 2, 1]] - }) - print_table_with_borders(galaxy_final_df, "🌌 FINAL GALAXY ALLOCATIONS") - - # Verification and Rounding Error Analysis - lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT - lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT - final_rounding_error_stars = lockdrop_allocation_stars - total_stars_allocation - final_rounding_error_galaxies = lockdrop_allocation_galaxies - total_galaxies_allocation - final_rounding_error = final_rounding_error_stars + final_rounding_error_galaxies - - verification_df = pd.DataFrame({ - 'Category': ['Star Allocations', 'Galaxy Allocations', 'Combined'], - 'Calculated Total': [f"{total_stars_allocation:.8f} $Z", f"{total_galaxies_allocation:.8f} $Z", - f"{total_stars_allocation + total_galaxies_allocation:.8f} $Z"], - 'Expected Total': [f"{lockdrop_allocation_stars:.8f} $Z", f"{lockdrop_allocation_galaxies:.8f} $Z", - f"{LOCKDROP_ALLOCATION:.8f} $Z"], - 'Rounding Error': [f"{final_rounding_error_stars:.8f} $Z", f"{final_rounding_error_galaxies:.8f} $Z", - f"{final_rounding_error:.8f} $Z"] - }) - print_table_with_borders(verification_df, "šŸ” ALLOCATION VERIFICATION") - print("\nšŸ“ NOTE: Final rounding errors go to Zenith Foundation") - print("\n" + "="*80) - - -def print_analysis_tables(allocation_data, bonus_data, final_data): - """Print comprehensive analysis tables - calls all the detailed sections.""" - print_participation_summary(allocation_data) - print_allocation_calculations(allocation_data) - print_penalty_analysis(allocation_data) - print_bonus_pool_calculations(allocation_data, bonus_data) - print_final_allocations_and_verification(allocation_data, final_data) - - -def create_visualization(allocation_data, final_data): - """Create comprehensive visualization plots.""" - stars_counts = allocation_data['stars_counts'] - galaxies_counts = allocation_data['galaxies_counts'] - final_star_allocations = final_data['final_star_allocations'] - final_galaxy_allocations = final_data['final_galaxy_allocations'] - - fig = plt.figure(figsize=(20, 10)) - gs = fig.add_gridspec(2, 3, hspace=0.35, wspace=0.35) - - fig.suptitle('Lockdrop Analysis', fontsize=18, fontweight='bold', y=0.98) - - # 1. Star Participation Distribution - ax1 = fig.add_subplot(gs[0, 0]) - star_years = [1, 2, 3, 4, 5] - star_counts = [stars_counts[year] for year in star_years] - - bars1 = ax1.bar(range(len(star_years)), star_counts, - color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8) - ax1.set_xlabel('Lock Period (Years)') - ax1.set_ylabel('Number of Stars') - ax1.set_title('Star Participation by Lock Period', fontweight='bold') - ax1.set_xticks(range(len(star_years))) - ax1.set_xticklabels(star_years) - - for i, count in enumerate(star_counts): - height = bars1[i].get_height() - ax1.text(bars1[i].get_x() + bars1[i].get_width()/2., height + height*0.01, - f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9) - - # 2. Galaxy Participation Distribution - ax2 = fig.add_subplot(gs[0, 1]) - galaxy_counts = [galaxies_counts[year] for year in star_years] - - bars2 = ax2.bar(range(len(star_years)), galaxy_counts, - color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8) - ax2.set_xlabel('Lock Period (Years)') - ax2.set_ylabel('Number of Galaxies') - ax2.set_title('Galaxy Participation by Lock Period', fontweight='bold') - ax2.set_xticks(range(len(star_years))) - ax2.set_xticklabels(star_years) - - for i, count in enumerate(galaxy_counts): - height = bars2[i].get_height() - ax2.text(bars2[i].get_x() + bars2[i].get_width()/2., height + height*0.01, - f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9) - - # 3. Star Allocations - ax3 = fig.add_subplot(gs[0, 2]) - star_alloc_values = [float(final_star_allocations[year]) for year in star_years] - - bars3 = ax3.bar(range(len(star_years)), star_alloc_values, - color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8) - ax3.set_xlabel('Lock Period (Years)') - ax3.set_ylabel('Allocation per Star ($Z)') - ax3.set_title('Star Allocations by Lock Period', fontweight='bold') - ax3.set_xticks(range(len(star_years))) - ax3.set_xticklabels(star_years) - - for i, allocation in enumerate(star_alloc_values): - height = bars3[i].get_height() - ax3.text(bars3[i].get_x() + bars3[i].get_width()/2., height + height*0.01, - f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9) - - # 4. Galaxy Allocations - ax4 = fig.add_subplot(gs[1, 0]) - galaxy_alloc_values = [float(final_galaxy_allocations[year]) for year in star_years] - - bars4 = ax4.bar(range(len(star_years)), galaxy_alloc_values, - color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8) - ax4.set_xlabel('Lock Period (Years)') - ax4.set_ylabel('Allocation per Galaxy ($Z)') - ax4.set_title('Galaxy Allocations by Lock Period', fontweight='bold') - ax4.set_xticks(range(len(star_years))) - ax4.set_xticklabels(star_years) - - for i, allocation in enumerate(galaxy_alloc_values): - height = bars4[i].get_height() - ax4.text(bars4[i].get_x() + bars4[i].get_width()/2., height + height*0.01, - f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9) - - # Hide remaining subplots - ax5 = fig.add_subplot(gs[1, 1]) - ax5.axis('off') - ax6 = fig.add_subplot(gs[1, 2]) - ax6.axis('off') - - plt.subplots_adjust(bottom=0.08, top=0.88, left=0.05, right=0.98) - plt.show() - -# Experimental notebook widget and interaction functions -def create_experiment_widgets(): - """Create input widgets for the experimental notebook.""" - # Define reasonable default values - default_participants = { - 'stars_1_years': 8000, - 'stars_2_years': 8000, - 'stars_3_years': 8000, - 'stars_4_years': 8000, - 'stars_5_years': 8000, - 'galaxies_1_years': 40, - 'galaxies_2_years': 40, - 'galaxies_3_years': 40, - 'galaxies_4_years': 40, - 'galaxies_5_years': 40 - } - - # Create input widgets - print("="*50) - - # Star participation widgets with bounds - stars_1_years = widgets.BoundedIntText(value=default_participants['stars_1_years'], min=0, max=NUM_STARS, description='1 Year:', style={'description_width': '80px'}) - stars_2_years = widgets.BoundedIntText(value=default_participants['stars_2_years'], min=0, max=NUM_STARS, description='2 Years:', style={'description_width': '80px'}) - stars_3_years = widgets.BoundedIntText(value=default_participants['stars_3_years'], min=0, max=NUM_STARS, description='3 Years:', style={'description_width': '80px'}) - stars_4_years = widgets.BoundedIntText(value=default_participants['stars_4_years'], min=0, max=NUM_STARS, description='4 Years:', style={'description_width': '80px'}) - stars_5_years = widgets.BoundedIntText(value=default_participants['stars_5_years'], min=0, max=NUM_STARS, description='5 Years:', style={'description_width': '80px'}) - - star_controls = widgets.VBox([stars_1_years, stars_2_years, stars_3_years, stars_4_years, stars_5_years]) - - galaxies_1_years = widgets.BoundedIntText(value=default_participants['galaxies_1_years'], min=0, max=NUM_GALAXIES, description='1 Year:', style={'description_width': '80px'}) - galaxies_2_years = widgets.BoundedIntText(value=default_participants['galaxies_2_years'], min=0, max=NUM_GALAXIES, description='2 Years:', style={'description_width': '80px'}) - galaxies_3_years = widgets.BoundedIntText(value=default_participants['galaxies_3_years'], min=0, max=NUM_GALAXIES, description='3 Years:', style={'description_width': '80px'}) - galaxies_4_years = widgets.BoundedIntText(value=default_participants['galaxies_4_years'], min=0, max=NUM_GALAXIES, description='4 Years:', style={'description_width': '80px'}) - galaxies_5_years = widgets.BoundedIntText(value=default_participants['galaxies_5_years'], min=0, max=NUM_GALAXIES, description='5 Years:', style={'description_width': '80px'}) - - galaxy_controls = widgets.VBox([galaxies_1_years, galaxies_2_years, galaxies_3_years, galaxies_4_years, galaxies_5_years]) - - # Preset scenario dropdown - scenario_dropdown = widgets.Dropdown( - options=[('Select Preset', '')] + [(v['description'], k) for k, v in SCENARIOS.items()], - description='Preset:', - style={'description_width': '60px'}, - layout={'width': '400px'} - ) - - # Calculate button - calculate_button = widgets.Button(description='šŸ”„ Calculate Allocations', button_style='success', layout={'width': '200px', 'margin': '10px 5px'}) - - # Export button - export_button = widgets.Button(description='šŸ’¾ Export Results', button_style='info', layout={'width': '180px', 'margin': '10px 5px'}) - - # Output area - output_area = widgets.Output() - - # Set up preset selection handler - def on_preset_change(change): - if change['new'] and change['new'] != '': - scenario = SCENARIOS[change['new']] - params = scenario['params'] - - # Update widget values - stars_1_years.value = params['stars_1_years'] - stars_2_years.value = params['stars_2_years'] - stars_3_years.value = params['stars_3_years'] - stars_4_years.value = params['stars_4_years'] - stars_5_years.value = params['stars_5_years'] - galaxies_1_years.value = params['galaxies_1_years'] - galaxies_2_years.value = params['galaxies_2_years'] - galaxies_3_years.value = params['galaxies_3_years'] - galaxies_4_years.value = params['galaxies_4_years'] - galaxies_5_years.value = params['galaxies_5_years'] - - scenario_dropdown.observe(on_preset_change, names='value') - - # Set up export button handler - def on_export_click(button=None): - """Handle export button click with timestamp.""" - # Generate timestamp for filename - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f'lockdrop_calculations_result_{timestamp}.json' - - # Get current values from widgets - participation_counts = { - 'stars_1_years': stars_1_years.value, - 'stars_2_years': stars_2_years.value, - 'stars_3_years': stars_3_years.value, - 'stars_4_years': stars_4_years.value, - 'stars_5_years': stars_5_years.value, - 'galaxies_1_years': galaxies_1_years.value, - 'galaxies_2_years': galaxies_2_years.value, - 'galaxies_3_years': galaxies_3_years.value, - 'galaxies_4_years': galaxies_4_years.value, - 'galaxies_5_years': galaxies_5_years.value - } - - try: - # Calculate allocations - allocation_data = calculate_dynamic_allocations(participation_counts) - bonus_data = calculate_bonus_pools(allocation_data) - final_data = calculate_final_allocations(allocation_data, bonus_data) - - # Generate test output - test_output = generate_test_output(final_data) - - # Add metadata - export_data = { - 'metadata': { - 'source': 'lockdrop-calculations.ipynb', - 'timestamp': timestamp, - 'participation_counts': participation_counts - }, - 'allocations': test_output - } - - # Save to file - with open(filename, 'w') as f: - json.dump(export_data, f, indent=2) - - print(f"āœ… Results exported to: {filename}") - - except Exception as e: - print(f"āŒ Export failed: {str(e)}") - - export_button.on_click(on_export_click) - - # Return all widgets as a dictionary - return { - 'star_controls': star_controls, - 'galaxy_controls': galaxy_controls, - 'scenario_dropdown': scenario_dropdown, - 'calculate_button': calculate_button, - 'export_button': export_button, - 'output_area': output_area, - 'widgets': { - 'stars_1_years': stars_1_years, - 'stars_2_years': stars_2_years, - 'stars_3_years': stars_3_years, - 'stars_4_years': stars_4_years, - 'stars_5_years': stars_5_years, - 'galaxies_1_years': galaxies_1_years, - 'galaxies_2_years': galaxies_2_years, - 'galaxies_3_years': galaxies_3_years, - 'galaxies_4_years': galaxies_4_years, - 'galaxies_5_years': galaxies_5_years - } - } - - -def create_calculate_function(widget_dict): - """Create the calculation and display function for the experimental notebook.""" - def calculate_and_display(button=None): - """Calculate allocations and display results based on input parameters.""" - - widgets_map = widget_dict['widgets'] - output_area = widget_dict['output_area'] - - with output_area: - clear_output(wait=True) - - # Get current values from widgets - participation_counts = { - 'stars_1_years': widgets_map['stars_1_years'].value, - 'stars_2_years': widgets_map['stars_2_years'].value, - 'stars_3_years': widgets_map['stars_3_years'].value, - 'stars_4_years': widgets_map['stars_4_years'].value, - 'stars_5_years': widgets_map['stars_5_years'].value, - 'galaxies_1_years': widgets_map['galaxies_1_years'].value, - 'galaxies_2_years': widgets_map['galaxies_2_years'].value, - 'galaxies_3_years': widgets_map['galaxies_3_years'].value, - 'galaxies_4_years': widgets_map['galaxies_4_years'].value, - 'galaxies_5_years': widgets_map['galaxies_5_years'].value - } - - # Validate inputs - total_stars = sum(participation_counts[key] for key in participation_counts if 'stars' in key) - total_galaxies = sum(participation_counts[key] for key in participation_counts if 'galaxies' in key) - - if total_stars > NUM_STARS: - print(f"āš ļø ERROR: Total stars ({total_stars:,}) exceeds maximum available ({NUM_STARS:,})") - return - - if total_galaxies > NUM_GALAXIES: - print(f"āš ļø ERROR: Total galaxies ({total_galaxies:,}) exceeds maximum available ({NUM_GALAXIES:,})") - return - - if total_stars == 0 and total_galaxies == 0: - print("āš ļø ERROR: Must have at least some participants") - return - - try: - # Perform calculations - print("šŸ”„ Calculating allocations...\n") - - allocation_data = calculate_dynamic_allocations(participation_counts) - bonus_data = calculate_bonus_pools(allocation_data) - final_data = calculate_final_allocations(allocation_data, bonus_data) - - # Display results - print_analysis_tables(allocation_data, bonus_data, final_data) - - # Create visualization - print("\nšŸ“Š Generating visualization...") - create_visualization(allocation_data, final_data) - - # Calculate participation insights - star_participation_rate = Decimal(total_stars) / NUM_STARS - galaxy_participation_rate = Decimal(total_galaxies) / NUM_GALAXIES - - print("\n" + "="*80) - print("šŸŽÆ EXPERIMENT INSIGHTS") - print("=" * 80) - - insights_df = pd.DataFrame({ - 'Metric': [ - 'Total Participants', - 'Star Participation Rate', - 'Galaxy Participation Rate', - '5-Year Star Bonus', - '5-Year Galaxy Bonus', - 'Highest Individual Allocation', - 'Lowest Individual Allocation' - ], - 'Value': [ - f"{total_stars + total_galaxies:,}", - f"{star_participation_rate:.1%}", - f"{galaxy_participation_rate:.1%}", - f"{float(bonus_data['bonus_per_star_5_years']):,.2f} $Z" if participation_counts['stars_5_years'] > 0 else "N/A (no 5Y stars)", - f"{float(bonus_data['bonus_per_galaxy_5_years']):,.2f} $Z" if participation_counts['galaxies_5_years'] > 0 else "N/A (no 5Y galaxies)", - f"{max(float(final_data['final_star_allocations'][5]), float(final_data['final_galaxy_allocations'][5])):,.2f} $Z", - f"{min(float(final_data['final_star_allocations'][1]), float(final_data['final_galaxy_allocations'][1])):,.2f} $Z" - ] - }) - print_table_with_borders(insights_df, "šŸŽÆ EXPERIMENT INSIGHTS") - print("\n" + "="*80) - - except Exception as e: - print(f"āŒ Error during calculation: {str(e)}") - traceback.print_exc() - - return calculate_and_display - - -# Preset scenario definitions for experimentation -SCENARIOS = { - 'balanced': { - 'description': 'Balanced distribution across all lock periods', - 'params': { - 'stars_1_years': 8000, 'stars_2_years': 8000, 'stars_3_years': 8000, 'stars_4_years': 8000, 'stars_5_years': 8000, - 'galaxies_1_years': 40, 'galaxies_2_years': 40, 'galaxies_3_years': 40, 'galaxies_4_years': 40, 'galaxies_5_years': 40 - } - }, - 'five_year_focused': { - 'description': 'Most participants choose 5-year lock (maximum bonus scenario)', - 'params': { - 'stars_1_years': 2000, 'stars_2_years': 2000, 'stars_3_years': 3000, 'stars_4_years': 5000, 'stars_5_years': 28000, - 'galaxies_1_years': 10, 'galaxies_2_years': 10, 'galaxies_3_years': 20, 'galaxies_4_years': 30, 'galaxies_5_years': 130 - } - }, - 'short_term_focused': { - 'description': 'Most participants choose shorter locks (high penalty scenario)', - 'params': { - 'stars_1_years': 20000, 'stars_2_years': 15000, 'stars_3_years': 4000, 'stars_4_years': 800, 'stars_5_years': 200, - 'galaxies_1_years': 100, 'galaxies_2_years': 80, 'galaxies_3_years': 15, 'galaxies_4_years': 3, 'galaxies_5_years': 2 - } - }, - 'low_participation': { - 'description': 'Low overall participation scenario', - 'params': { - 'stars_1_years': 1000, 'stars_2_years': 800, 'stars_3_years': 600, 'stars_4_years': 400, 'stars_5_years': 200, - 'galaxies_1_years': 8, 'galaxies_2_years': 6, 'galaxies_3_years': 4, 'galaxies_4_years': 2, 'galaxies_5_years': 5 - } - } -} - - -def create_scenario_loader(widget_dict): - """Create scenario loading function for the experimental notebook.""" - def load_scenario(scenario_name): - """Load a preset scenario into the input widgets.""" - if scenario_name not in SCENARIOS: - print(f"āŒ Unknown scenario: {scenario_name}") - return - - scenario = SCENARIOS[scenario_name] - params = scenario['params'] - widgets_map = widget_dict['widgets'] - - # Update widget values - widgets_map['stars_1_years'].value = params['stars_1_years'] - widgets_map['stars_2_years'].value = params['stars_2_years'] - widgets_map['stars_3_years'].value = params['stars_3_years'] - widgets_map['stars_4_years'].value = params['stars_4_years'] - widgets_map['stars_5_years'].value = params['stars_5_years'] - widgets_map['galaxies_1_years'].value = params['galaxies_1_years'] - widgets_map['galaxies_2_years'].value = params['galaxies_2_years'] - widgets_map['galaxies_3_years'].value = params['galaxies_3_years'] - widgets_map['galaxies_4_years'].value = params['galaxies_4_years'] - widgets_map['galaxies_5_years'].value = params['galaxies_5_years'] - - print(f"āœ… Loaded scenario: {scenario_name}") - print(f"šŸ“ Description: {scenario['description']}") - print("\nšŸ”„ Click 'Calculate Allocations' button above to see results!") - - return load_scenario - - -# Simulation-specific helper functions for processing generated data - -def simulation_load_watcher_events(file_path): - """Load and parse PointLockedEvent events from JSON file (simulation-specific).""" - with open(file_path, 'r') as f: - data = json.load(f) - - # Filter for only PointLockedEvent events during loading - point_locked_events = [ - event_data for event_data in data['data']['eventsInRange'] - if event_data['event']['__typename'] == 'PointLockedEvent' - ] - - return point_locked_events - - -def simulation_analyze_lockdrop_events(events): - """Analyze PointLockedEvent events and return participation statistics (simulation-specific).""" - # Initialize counters - lock_duration_counts = { - 'star': defaultdict(int), - 'galaxy': defaultdict(int) - } - - # Process events (already filtered to PointLockedEvent only) - for event_data in events: - point = event_data['event']['point'] - lock_period = event_data['event']['lock_period'] - - # Determine if it's a galaxy or star - point_num = urbitob.patp_to_num(point) - point_type = "galaxy" if point_num < NUM_GALAXIES else "star" - - # Count by lock period - lock_duration_counts[point_type][lock_period] += 1 - - # Extract counts for each year and point type - result = {} - for years in [1, 2, 3, 4, 5]: - result[f'stars_{years}_years'] = lock_duration_counts['star'][years] - result[f'galaxies_{years}_years'] = lock_duration_counts['galaxy'][years] - - return result - - -def run_simulation_analysis(generated_dir=None): - """Complete simulation analysis pipeline (simulation-specific).""" - # Determine generated directory - if generated_dir is None: - generated_dir = os.getenv('GENERATED_DIR', 'generated') - - # Load and analyze watcher events - watcher_events_path = os.path.join(generated_dir, 'watcher-events.json') - - print("=" * 80) - print("šŸ“ WATCHER EVENTS DATA SUMMARY") - print("=" * 80) - - try: - point_locked_events = simulation_load_watcher_events(watcher_events_path) - - if point_locked_events: - total_events = len(point_locked_events) - print(f"āœ… Successfully loaded {total_events} PointLockedEvent records from watcher file") - else: - print("āš ļø No PointLockedEvent events found") - return None - - # Analyze events and convert to Decimal - lock_stats = simulation_analyze_lockdrop_events(point_locked_events) - participation_counts = {k: Decimal(v) for k, v in lock_stats.items()} - - # Run calculations - allocation_data = calculate_dynamic_allocations(participation_counts) - bonus_data = calculate_bonus_pools(allocation_data) - final_data = calculate_final_allocations(allocation_data, bonus_data) - - # Print comprehensive analysis - print_analysis_tables(allocation_data, bonus_data, final_data) - - # Generate and save test output - test_output = generate_test_output(final_data) - allocations_output_file = 'lockdrop_allocations_notebook.json' - - export_data = { - 'metadata': { - 'source': 'lockdrop-calculations-simulated.ipynb', - 'participation_counts': lock_stats - }, - 'allocations': test_output - } - - with open(allocations_output_file, 'w') as f: - json.dump(export_data, f, indent=2) - - print("=" * 80) - print("šŸ’¾ JSON OUTPUT GENERATED") - print(f"āœ… Final allocations saved to: {allocations_output_file}") - print("=" * 80) - - # Return data for visualization - return allocation_data, final_data - - except FileNotFoundError: - print(f"āŒ Watcher events file not found: {watcher_events_path}") - print(" Make sure GENERATED_DIR environment variable points to the correct directory") - return None - except Exception as e: - print(f"āŒ Error during simulation analysis: {e}") - return None - - -def create_experimental_interface(): - """Create complete experimental interface with widgets and handlers.""" - # Create widgets - widget_dict = create_experiment_widgets() - - # Create calculation function - calculate_and_display = create_calculate_function(widget_dict) - - # Connect button to calculation function - widget_dict['calculate_button'].on_click(calculate_and_display) - - # Display interface - print("šŸŽÆ PARTICIPATION INPUT CONTROLS") - print("="*50) - - print("\nšŸŽ® PRESET SCENARIOS") - display(widget_dict['scenario_dropdown']) - - print("\n⭐ STAR PARTICIPATION") - display(widget_dict['star_controls']) - - print("\n🌌 GALAXY PARTICIPATION") - display(widget_dict['galaxy_controls']) - - # Action buttons - buttons_box = widgets.HBox([widget_dict['calculate_button'], widget_dict['export_button']]) - display(buttons_box) - - # Output area - display(widget_dict['output_area']) - - return None