Troubleshooting Guide

This guide helps you resolve common issues when using the Ergodic Insurance Framework. Each issue includes symptoms, causes, and step-by-step solutions.

Table of Contents

  1. Installation Issues

  2. Import Errors

  3. Simulation Problems

  4. Performance Issues

  5. Memory Problems

  6. Numerical Issues

  7. Visualization Problems

  8. Configuration Errors

Installation Issues

Issue: Package won’t install with pip

Symptoms:

ERROR: Could not find a version that satisfies the requirement ergodic_insurance

Solution:

# Install from the local directory
cd ergodic_insurance
pip install -e .

# Or with uv
uv sync

Issue: Dependency conflicts

Symptoms:

ERROR: pip's dependency resolver does not currently take into account all the packages

Solution:

# Create a fresh virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install with updated pip
pip install --upgrade pip
pip install -e .

Issue: NumPy/SciPy installation fails

Symptoms:

ERROR: Failed building wheel for numpy

Solution:

# Install pre-built wheels
pip install --only-binary :all: numpy scipy

# On Mac with M1/M2:
pip install numpy scipy --platform macosx_11_0_arm64

Import Errors

Issue: ModuleNotFoundError

Symptoms:

>>> from ergodic_insurance.manufacturer import Manufacturer
ModuleNotFoundError: No module named 'ergodic_insurance'

Solution:

# Check installation
import sys
print(sys.path)

# Add to path if needed
import sys
sys.path.append('/path/to/ergodic_insurance')

# Or reinstall
pip install -e /path/to/ergodic_insurance

Issue: ImportError for specific modules

Symptoms:

ImportError: cannot import name 'ClaimGenerator' from 'ergodic_insurance.src'

Solution:

# Use correct import path
from ergodic_insurance.claim_generator import ClaimGenerator

# Check available modules
import ergodic_insurance.src
print(dir(ergodic_insurance.src))

Simulation Problems

Issue: All simulations result in bankruptcy

Symptoms:

  • Every simulation path shows ruin

  • Final wealth is always negative

  • Survival rate is 0%

Diagnosis and Solution:

# Check loss severity relative to assets
manufacturer = Manufacturer(initial_assets=10_000_000)
claim_generator = ClaimGenerator(
    frequency=5,
    severity_mu=10.0,
    severity_sigma=1.5
)

# Diagnose the problem
sample_losses = [claim_generator.generate_claims(1) for _ in range(100)]
avg_annual_loss = np.mean([sum(losses) for losses in sample_losses])
loss_ratio = avg_annual_loss / manufacturer.initial_assets

print(f"Average annual loss: ${avg_annual_loss:,.0f}")
print(f"Loss ratio: {loss_ratio:.1%}")

if loss_ratio > 0.1:
    print("⚠️ Losses too high relative to assets!")

    # Solutions:
    # 1. Reduce loss severity
    claim_generator = ClaimGenerator(
        frequency=5,
        severity_mu=9.0,  # Reduced
        severity_sigma=1.0  # Reduced
    )

    # 2. Increase insurance coverage
    retention = 250_000  # Lower retention
    limit = 15_000_000   # Higher limit

    # 3. Increase initial assets
    manufacturer = Manufacturer(initial_assets=20_000_000)

Issue: Results vary wildly between runs

Symptoms:

  • Different results each time

  • Can’t reproduce analysis

  • Inconsistent optimization results

Solution:

# Always set random seed for reproducibility
import numpy as np
np.random.seed(42)

# Or pass seed to functions
results = mc_analyzer.run_simulations(
    n_simulations=1000,
    n_years=20,
    seed=42  # Fixed seed
)

# For multiple runs with different seeds
seeds = [42, 100, 200, 300, 400]
all_results = []
for seed in seeds:
    result = mc_analyzer.run_simulations(seed=seed)
    all_results.append(result)

# Average across seeds for stability
mean_growth = np.mean([r['mean_growth_rate'] for r in all_results])

Issue: Negative growth rates for all strategies

Symptoms:

  • All insurance strategies show negative growth

  • No profitable configurations found

Solution:

# Check base profitability without losses
manufacturer = Manufacturer(
    initial_assets=10_000_000,
    asset_turnover=1.0,
    base_operating_margin=0.08,
    tax_rate=0.25
)

# Calculate base ROE
revenue = manufacturer.initial_assets * manufacturer.asset_turnover
operating_income = revenue * manufacturer.base_operating_margin
net_income = operating_income * (1 - manufacturer.tax_rate)
base_roe = net_income / manufacturer.initial_assets

print(f"Base ROE (no losses): {base_roe:.1%}")

if base_roe < 0.05:
    print("⚠️ Base profitability too low!")

    # Improve profitability
    manufacturer = Manufacturer(
        initial_assets=10_000_000,
        asset_turnover=1.2,      # Increased
        base_operating_margin=0.10,   # Increased
        tax_rate=0.21           # Reduced
    )

Performance Issues

Issue: Simulations run too slowly

Symptoms:

  • Single simulation takes minutes

  • Monte Carlo takes hours

  • Optimization never completes

Solution:

# 1. Reduce simulation parameters for testing
quick_test = mc_analyzer.run_simulations(
    n_simulations=100,   # Reduced from 1000
    n_years=10,         # Reduced from 20
    seed=42
)

# 2. Use parallel processing
from ergodic_insurance.parallel_executor import ParallelExecutor

executor = ParallelExecutor(n_workers=4)
results = executor.run_parallel_monte_carlo(
    manufacturer=manufacturer,
    claim_generator=claim_generator,
    n_simulations=10000,
    n_years=20
)

# 3. Profile code to find bottlenecks
import cProfile
import pstats

profiler = cProfile.Profile()
profiler.enable()

# Run simulation
results = mc_analyzer.run_simulations(n_simulations=100)

profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10)  # Top 10 time consumers

Issue: Optimization takes too long

Symptoms:

  • Grid search never finishes

  • Optimization runs for hours

Solution:

# 1. Use coarser grid for initial search
# Instead of:
retentions = np.linspace(100_000, 3_000_000, 30)  # 30 points
limits = np.linspace(1_000_000, 20_000_000, 20)   # 20 points

# Use:
retentions = np.linspace(100_000, 3_000_000, 8)   # 8 points
limits = np.linspace(1_000_000, 20_000_000, 5)    # 5 points

# 2. Use adaptive refinement
# Start coarse, then refine around optimum
coarse_optimum = optimize_coarse_grid()
fine_optimum = optimize_fine_grid_around(coarse_optimum)

# 3. Use gradient-based optimization for continuous parameters
from scipy.optimize import minimize

result = minimize(
    objective_function,
    x0=initial_guess,
    method='L-BFGS-B',  # Fast for smooth problems
    bounds=bounds
)

Memory Problems

Issue: Out of memory errors

Symptoms:

MemoryError: Unable to allocate array

Solution:

# 1. Process in batches
def run_large_simulation(n_total, batch_size=1000):
    all_results = []
    n_batches = n_total // batch_size

    for i in range(n_batches):
        batch_results = mc_analyzer.run_simulations(
            n_simulations=batch_size,
            seed=i
        )
        # Extract only needed metrics
        all_results.append({
            'mean_growth': np.mean(batch_results['growth_rates']),
            'survival': batch_results['survival_rate']
        })
        # Clear batch data
        del batch_results

    return all_results

# 2. Use memory-efficient storage
from ergodic_insurance.trajectory_storage import TrajectoryStorage

storage = TrajectoryStorage(
    storage_type='disk',  # Use disk instead of memory
    chunk_size=100
)

# 3. Reduce trajectory storage
# Instead of storing full trajectories
results = mc_analyzer.run_simulations(
    store_trajectories=False,  # Only store summary statistics
    n_simulations=10000
)

Issue: Jupyter notebook kernel crashes

Symptoms:

  • Kernel dies during large simulations

  • “Kernel appears to have died” message

Solution:

# 1. Increase Jupyter memory limits
# In jupyter_notebook_config.py:
# c.NotebookApp.max_buffer_size = 10000000000

# 2. Clear variables periodically
import gc

# After processing
del large_array
gc.collect()

# 3. Use generators instead of lists
def generate_simulations(n):
    for i in range(n):
        yield run_single_simulation(seed=i)

# Process one at a time
for sim_result in generate_simulations(10000):
    process(sim_result)

Numerical Issues

Issue: NaN or Inf values in results

Symptoms:

>>> print(results['growth_rate'])
nan

Solution:

# 1. Check for division by zero
def safe_growth_rate(final_wealth, initial_wealth, n_years):
    if initial_wealth <= 0 or final_wealth <= 0:
        return np.nan

    return (final_wealth / initial_wealth) ** (1/n_years) - 1

# 2. Check for extreme values
def validate_inputs(manufacturer, claim_generator):
    assert manufacturer.initial_assets > 0
    assert 0 < manufacturer.base_operating_margin < 1
    assert 0 < manufacturer.tax_rate < 1
    assert claim_generator.frequency >= 0
    assert claim_generator.severity_sigma > 0

# 3. Add numerical stability
def stable_log_return(final, initial):
    ratio = final / initial
    # Clip to prevent extreme values
    ratio = np.clip(ratio, 1e-10, 1e10)
    return np.log(ratio)

# 4. Debug NaN sources
def debug_nan(results):
    for key, value in results.items():
        if isinstance(value, np.ndarray):
            nan_count = np.sum(np.isnan(value))
            if nan_count > 0:
                print(f"NaN found in {key}: {nan_count} values")
                # Find first NaN
                nan_idx = np.where(np.isnan(value))[0][0]
                print(f"  First NaN at index {nan_idx}")

Issue: Overflow in exponential calculations

Symptoms:

RuntimeWarning: overflow encountered in exp

Solution:

# Use log-space calculations
def safe_lognormal_sample(mu, sigma, size):
    # Instead of np.exp(normal)
    # Use built-in lognormal
    return np.random.lognormal(mu, sigma, size)

# Clip extreme values
def clip_losses(losses, max_loss):
    return np.minimum(losses, max_loss)

# Use appropriate data types
large_values = np.array([1e15, 1e16], dtype=np.float64)

Visualization Problems

Issue: Plots don’t display in Jupyter

Symptoms:

  • No plot output

  • <Figure size 640x480 with 1 Axes> message only

Solution:

# 1. Use inline backend
%matplotlib inline
import matplotlib.pyplot as plt

# 2. Explicitly show plots
plt.plot(data)
plt.show()  # Don't forget this!

# 3. Check backend
import matplotlib
print(matplotlib.get_backend())
# Set if needed
matplotlib.use('TkAgg')  # or 'Qt5Agg'

Issue: Overlapping labels or text

Symptoms:

  • X-axis labels overlap

  • Legend covers data

  • Title cut off

Solution:

# 1. Adjust layout
plt.tight_layout()

# 2. Rotate labels
plt.xticks(rotation=45, ha='right')

# 3. Adjust figure size
plt.figure(figsize=(12, 6))  # Wider figure

# 4. Move legend
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')

# 5. Adjust margins
plt.subplots_adjust(bottom=0.15, right=0.85)

Configuration Errors

Issue: YAML configuration won’t load

Symptoms:

yaml.scanner.ScannerError: mapping values are not allowed here

Solution:

# Check YAML syntax
# BAD:
parameter: value
  nested: value  # Wrong indentation

# GOOD:
parameter: value
nested: value    # Correct indentation

# Or use online YAML validator

Issue: Pydantic validation errors

Symptoms:

ValidationError: 1 validation error for ManufacturerConfig

Solution:

# 1. Check required fields
from ergodic_insurance.config_v2 import ManufacturerConfig

# See what's required
print(ManufacturerConfig.schema())

# 2. Provide all required fields
config = ManufacturerConfig(
    initial_assets=10_000_000,  # Required
    asset_turnover=1.0,          # Has default
    base_operating_margin=0.08,       # Has default
    # ... other fields
)

# 3. Check value constraints
# E.g., base_operating_margin must be between 0 and 1

Common Parameter Issues

Issue: Unrealistic parameter combinations

Problem: Setting parameters that don’t make business sense

Guidelines:

# Reasonable parameter ranges
reasonable_ranges = {
    'asset_turnover': (0.5, 3.0),      # Industry dependent
    'base_operating_margin': (0.02, 0.25),   # 2-25%
    'tax_rate': (0.15, 0.35),          # 15-35%
    'retention_ratio': (0.01, 0.20),    # 1-20% of assets
    'base_premium_rate': (0.01, 0.05),       # 1-5% of limit
    'loss_frequency': (0.1, 20),        # Per year
    'loss_severity_mu': (8, 14),        # Log scale
    'loss_severity_sigma': (0.5, 2.5)   # Log scale
}

def validate_parameters(params):
    for param, value in params.items():
        if param in reasonable_ranges:
            min_val, max_val = reasonable_ranges[param]
            if not min_val <= value <= max_val:
                print(f"⚠️ {param}={value} outside typical range [{min_val}, {max_val}]")

Getting Additional Help

If you encounter issues not covered here:

  1. Check the test suite: Look at test files for usage examples

  2. Review notebooks: The example notebooks show working code

  3. Enable debug logging:

    import logging
    logging.basicConfig(level=logging.DEBUG)
    
  4. File an issue: https://github.com/AlexFiliakov/Ergodic-Insurance-Limits/issues

Performance Optimization Tips

For Large Simulations

# Optimal settings for different scales
configs = {
    'quick_test': {
        'n_simulations': 100,
        'n_years': 10,
        'n_workers': 1
    },
    'standard': {
        'n_simulations': 1000,
        'n_years': 20,
        'n_workers': 4
    },
    'comprehensive': {
        'n_simulations': 10000,
        'n_years': 50,
        'n_workers': 8
    }
}

Memory-Efficient Patterns

# Good: Generator pattern
def process_simulations():
    for i in range(10000):
        result = run_simulation(seed=i)
        yield extract_metrics(result)

# Bad: Loading all into memory
results = [run_simulation(seed=i) for i in range(10000)]

Remember: Start small, validate results, then scale up!