From 5a5c4508c67a3ef30d9928cb33af3af4584b92f0 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Tue, 12 Aug 2025 18:38:13 +0530 Subject: [PATCH] Add instructions for interactive experimentation --- EXPERIMENT.md | 134 +++++++++++++++++++++++++++++ README.md | 8 ++ lockdrop_calculations.py | 176 +++++++++++++++++++++++++-------------- 3 files changed, 256 insertions(+), 62 deletions(-) create mode 100644 EXPERIMENT.md diff --git a/EXPERIMENT.md b/EXPERIMENT.md new file mode 100644 index 0000000..38dcc41 --- /dev/null +++ b/EXPERIMENT.md @@ -0,0 +1,134 @@ +# Lockdrop Allocation Experimentation + +This guide explains how to use the interactive notebook to experiment with different lockdrop participation scenarios and understand how various distributions affect token allocations. + +## Prerequisites + +- Python 3.x + +## Setup + +1. **Clone and Navigate to Directory** + + ```bash + git clone git@git.vdb.to:LaconicNetwork/lockdrop-simulation.git + cd lockdrop-simulation + ``` + +2. **Create Virtual Environment** + + ```bash + python3 -m venv venv + source venv/bin/activate + ``` + +3. **Install Dependencies** + + ```bash + pip install -r requirements.txt + ``` + +## Running the Experimental Notebook + +1. **Start Jupyter Notebook Server** + + ```bash + jupyter notebook + ``` + + This opens your browser to + +2. **Open the Experimental Notebook** + + Navigate to and open `lockdrop-calculations.ipynb` + +3. **Execute the Notebook** + + Run cells in order by clicking "Run All" from `Run` tab or execute individually with Shift+Enter + +## Using the Interactive Interface + +### Input Controls + +- **Star Participation**: Adjust participation numbers for stars across 1-5 year lock durations +- **Galaxy Participation**: Adjust participation numbers for galaxies across 1-5 year lock durations +- **Preset Scenarios**: Use the dropdown to quickly load common scenarios: + - **Balanced**: Even distribution across all lock periods + - **Five-year focused**: Most participants choose maximum lock duration + - **Short-term focused**: Most participants choose shorter lock durations + - **Low participation**: Reduced overall participation scenario + +### Action Buttons + +- **šŸ”„ Calculate Allocations**: Run calculations and display comprehensive analysis +- **šŸ’¾ Export Results**: Save experimental results to timestamped JSON file + +## Understanding the Analysis Output + +The notebook provides detailed analysis including: + +### 1. Participation Distribution +- Breakdown of participants by lock period +- Participation rates for stars and galaxies +- Total participant counts + +### 2. Allocation Calculations +- Base allocations accounting for actual participation +- Quanta-adjusted allocations per participant +- Calculations on rounding errors resulting from quanta usage + +### 3. Penalty System Analysis +- Impact of lock duration on allocations before bonuses +- Penalty rates for each lock period +- Allocation percentages relative to maximum + +### 4. Bonus Pool Calculations +- How penalties redistribute to 5-year participants +- Bonus amounts per 5-year participant +- Total bonus pool distribution + +### 5. Final Allocations & Verification +- Complete allocation breakdown by lock period +- Z tokens per block calculations +- Allocation verification and rounding error tracking + +### 6. Visualizations +- Participation distribution charts +- Allocation comparison graphs +- Lock period analysis plots + +## Experimenting with Scenarios + +### Quick Start with Presets + +1. Select a preset from the dropdown (e.g., "Five-year focused") +2. Click "šŸ”„ Calculate Allocations" +3. Review the analysis output +4. Try different presets to compare scenarios + +### Custom Scenarios + +1. Manually adjust participation numbers +2. Ensure total participants don't exceed maximum available (65,280 stars, 256 galaxies) +3. Click "šŸ”„ Calculate Allocations" +4. Use "šŸ’¾ Export Results" to save interesting configurations + +## Exported Results + +Export files contain: +- **Metadata**: Timestamp, source notebook, participation parameters +- **Allocations**: Complete calculation results in JSON format +- **File naming**: `lockdrop_calculations_result_YYYYMMDD_HHMMSS.json` + +These files can be used for: +- Further analysis +- Sharing experimental configurations +- Testing different scenarios +- Integration with other tools + +## Troubleshooting + +- **Import errors**: Ensure all dependencies are installed via `pip install -r requirements.txt` +- **Widget display issues**: Restart notebook kernel and re-run cells +- **Calculation errors**: Check that participation numbers don't exceed limits +- **Export failures**: Ensure write permissions in the notebook directory diff --git a/README.md b/README.md index 4e488bd..bfd7a87 100644 --- a/README.md +++ b/README.md @@ -319,3 +319,11 @@ deactivate # Remove virtual environment directory rm -rf venv ``` + +## Interactive Experimentation + +For independent experimentation with lockdrop allocation scenarios without running the full simulation, see [EXPERIMENT.md](./EXPERIMENT.md). This allows you to: + +- Experiment with different participation distributions using an interactive Jupyter notebook +- Test various scenarios (balanced, five-year focused, short-term focused, etc.) +- Export timestamped results for analysis diff --git a/lockdrop_calculations.py b/lockdrop_calculations.py index 16bf9c0..b980028 100644 --- a/lockdrop_calculations.py +++ b/lockdrop_calculations.py @@ -364,32 +364,31 @@ def print_allocation_calculations(allocation_data): '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': ['Final allocation', 'Per star loss', 'Goes to bonus pool', 'Of total supply'] + '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': ['Final allocation', 'Per galaxy loss', 'Goes to bonus pool', 'Of total supply'] + 'Note': ['Before bonus', 'Per galaxy loss', 'Goes to bonus pool', 'Of total supply'] }) - print("\nšŸŽÆ ADJUSTED PARTICIPANT ALLOCATIONS") + 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(): - """Print penalty system analysis.""" +def print_penalty_analysis(allocation_data): + """Print penalty system analysis using values before bonus calculations.""" print("=" * 80) print("āš–ļø PENALTY SYSTEM ANALYSIS") print("=" * 80) - # Theoretical penalty analysis (before bonus distribution) - base_allocations = calculate_base_allocations() - adjusted_max_allocation_per_star = base_allocations['adjusted_max_allocation_per_star'] - adjusted_max_allocation_per_galaxy = base_allocations['adjusted_max_allocation_per_galaxy'] + # 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'], @@ -410,10 +409,6 @@ def print_participation_summary(allocation_data): total_stars_locked = allocation_data['total_stars_locked'] total_galaxies_locked = allocation_data['total_galaxies_locked'] - print("=" * 80) - print("šŸ“ˆ DYNAMIC LOCKDROP ANALYSIS") - print("=" * 80) - # Consolidated participation and lock period distribution participation_df = pd.DataFrame({ 'Lock Period': ['1 Year', '2 Years', '3 Years', '4 Years', '5 Years', 'Total'], @@ -423,6 +418,8 @@ def print_participation_summary(allocation_data): '% 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) @@ -441,13 +438,6 @@ def print_bonus_pool_calculations(allocation_data, bonus_data): print("=" * 80) # Star Bonus Pool Analysis - star_bonus_components = [] - for years in [1, 2, 3, 4]: - star_count = stars_counts[years] - penalty = PENALTY_RATES[years] - if star_count > 0: - component_amount = adjusted_max_allocation_per_star * star_count * penalty - star_bonus_components.append(f"{years}Y Stars: {component_amount:,.2f} $Z") star_bonus_df = pd.DataFrame({ 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Star Bonus Pool', @@ -459,13 +449,6 @@ def print_bonus_pool_calculations(allocation_data, bonus_data): print_table_with_borders(star_bonus_df, "⭐ STAR BONUS POOL ANALYSIS") # Galaxy Bonus Pool Analysis - galaxy_bonus_components = [] - for years in [1, 2, 3, 4]: - galaxy_count = galaxies_counts[years] - penalty = PENALTY_RATES[years] - if galaxy_count > 0: - component_amount = adjusted_max_allocation_per_galaxy * galaxy_count * penalty - galaxy_bonus_components.append(f"{years}Y Galaxies: {component_amount:,.2f} $Z") galaxy_bonus_df = pd.DataFrame({ 'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Galaxy Bonus Pool', @@ -476,17 +459,6 @@ def print_bonus_pool_calculations(allocation_data, bonus_data): }) print_table_with_borders(galaxy_bonus_df, "🌌 GALAXY BONUS POOL ANALYSIS") - # Show bonus pool sources if there are penalties - if star_bonus_components: - print(f"\nšŸ’” Star Bonus Pool Sources:") - for component in star_bonus_components: - print(f" • {component}") - - if galaxy_bonus_components: - print(f"\nšŸ’” Galaxy Bonus Pool Sources:") - for component in galaxy_bonus_components: - print(f" • {component}") - print("\n" + "="*80) @@ -509,8 +481,8 @@ def print_final_allocations_and_verification(allocation_data, final_data): 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]], + '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") @@ -519,8 +491,8 @@ def print_final_allocations_and_verification(allocation_data, final_data): 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]], + '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") @@ -548,9 +520,9 @@ def print_final_allocations_and_verification(allocation_data, final_data): def print_analysis_tables(allocation_data, bonus_data, final_data): """Print comprehensive analysis tables - calls all the detailed sections.""" - print_allocation_calculations(allocation_data) - print_penalty_analysis() 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) @@ -730,17 +702,108 @@ def create_experiment_widgets(): 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': '20px 0'}) + 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.""" + try: + from datetime import datetime + from IPython.display import clear_output + except ImportError: + print("āŒ Required libraries not available") + return + + # 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, @@ -856,9 +919,8 @@ def create_calculate_function(widget_dict): return calculate_and_display -def get_preset_scenarios(): - """Get preset scenario definitions for experimentation.""" - return { +# Preset scenario definitions for experimentation +SCENARIOS = { 'balanced': { 'description': 'Balanced distribution across all lock periods', 'params': { @@ -880,13 +942,6 @@ def get_preset_scenarios(): 'galaxies_1_years': 100, 'galaxies_2_years': 80, 'galaxies_3_years': 15, 'galaxies_4_years': 3, 'galaxies_5_years': 2 } }, - 'polarized': { - 'description': 'Split between 1-year and 5-year locks (maximum bonus per 5Y participant)', - 'params': { - 'stars_1_years': 25000, 'stars_2_years': 0, 'stars_3_years': 0, 'stars_4_years': 0, 'stars_5_years': 15000, - 'galaxies_1_years': 150, 'galaxies_2_years': 0, 'galaxies_3_years': 0, 'galaxies_4_years': 0, 'galaxies_5_years': 50 - } - }, 'low_participation': { 'description': 'Low overall participation scenario', 'params': { @@ -894,20 +949,18 @@ def get_preset_scenarios(): '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.""" - scenarios = get_preset_scenarios() - - if scenario_name not in scenarios: + if scenario_name not in SCENARIOS: print(f"āŒ Unknown scenario: {scenario_name}") return - scenario = scenarios[scenario_name] + scenario = SCENARIOS[scenario_name] params = scenario['params'] widgets_map = widget_dict['widgets'] @@ -962,8 +1015,7 @@ def create_export_function(widget_dict): # Add metadata export_data = { 'metadata': { - 'source': 'lockdrop-experiment.ipynb', - 'scenario': 'custom', + 'source': 'lockdrop-calculations.ipynb', 'participation_counts': participation_counts }, 'allocations': test_output