Add instructions for interactive experimentation

This commit is contained in:
Prathamesh Musale 2025-08-12 18:38:13 +05:30
parent 6b7fca23b1
commit 5a5c4508c6
3 changed files with 256 additions and 62 deletions

134
EXPERIMENT.md Normal file
View File

@ -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 <http://localhost:8888>
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

View File

@ -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

View File

@ -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