Initial commit: Markov Economics Simulation App

This commit is contained in:
2025-08-24 19:12:50 +02:00
commit 26c82959a2
28 changed files with 3646 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.venv/
.qoder/

Binary file not shown.

39
app/__init__.py Normal file
View File

@@ -0,0 +1,39 @@
"""
Flask Application Factory
Creates and configures the Flask application with SocketIO support for real-time
updates of the Markov economics simulation.
"""
from flask import Flask
from flask_socketio import SocketIO
from config import config
# Initialize SocketIO
socketio = SocketIO(cors_allowed_origins="*")
def create_app(config_name='development'):
"""
Create and configure the Flask application.
Args:
config_name: Configuration profile to use
Returns:
Configured Flask application instance
"""
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions
socketio.init_app(app, async_mode='threading')
# Register blueprints
from .routes.main import main_bp
from .routes.api import api_bp
app.register_blueprint(main_bp)
app.register_blueprint(api_bp, url_prefix='/api')
return app

Binary file not shown.

25
app/models/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
"""
Economic simulation models package.
This package contains the core Markov chain implementations and simulation engine
for demonstrating how capitalism "eats the world" when r > g.
"""
from .markov_chain import MarkovChain, CapitalistChain, ConsumerChain, EconomicAgent
from .economic_model import (
SimulationParameters,
SimulationSnapshot,
EconomicSimulation,
SimulationManager
)
__all__ = [
'MarkovChain',
'CapitalistChain',
'ConsumerChain',
'EconomicAgent',
'SimulationParameters',
'SimulationSnapshot',
'EconomicSimulation',
'SimulationManager'
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,482 @@
"""
Economic Simulation Engine
This module implements the main simulation engine that orchestrates multiple
economic agents running Markov chains to demonstrate wealth inequality dynamics
when capital return rate (r) exceeds economic growth rate (g).
"""
import numpy as np
import uuid
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass
import time
import json
from .markov_chain import EconomicAgent
@dataclass
class SimulationParameters:
"""Configuration parameters for economic simulation."""
r_rate: float # Capital return rate
g_rate: float # Economic growth rate
initial_capital: float = 1000.0
initial_consumption: float = 1000.0
num_agents: int = 100
iterations: int = 1000
def __post_init__(self):
"""Validate parameters after initialization."""
if self.r_rate < 0 or self.r_rate > 1:
raise ValueError("Capital rate must be between 0 and 1")
if self.g_rate < 0 or self.g_rate > 1:
raise ValueError("Growth rate must be between 0 and 1")
if self.num_agents < 1 or self.num_agents > 10000:
raise ValueError("Number of agents must be between 1 and 10000")
if self.iterations < 1 or self.iterations > 100000:
raise ValueError("Iterations must be between 1 and 100000")
@dataclass
class SimulationSnapshot:
"""Snapshot of simulation state at a specific iteration."""
iteration: int
timestamp: float
wealth_distribution: List[float]
consumption_distribution: List[float]
total_wealth: float
gini_coefficient: float
wealth_concentration_top10: float
capital_share: float
average_wealth: float
median_wealth: float
class EconomicSimulation:
"""
Main simulation engine for demonstrating Piketty's inequality principle.
Manages multiple economic agents and tracks wealth distribution over time
to show how r > g leads to increasing inequality.
"""
def __init__(self, parameters: SimulationParameters):
"""
Initialize the economic simulation.
Args:
parameters: Configuration parameters for the simulation
"""
self.parameters = parameters
self.simulation_id = str(uuid.uuid4())
self.agents: List[EconomicAgent] = []
self.snapshots: List[SimulationSnapshot] = []
self.current_iteration = 0
self.is_running = False
self.start_time = None
self._initialize_agents()
def _initialize_agents(self):
"""Create economic agents with specified parameters."""
self.agents = []
for i in range(self.parameters.num_agents):
agent_id = f"agent_{i:04d}"
# Add some randomness to initial conditions
initial_capital = self.parameters.initial_capital * (0.8 + 0.4 * np.random.random())
initial_consumption = self.parameters.initial_consumption * (0.8 + 0.4 * np.random.random())
agent = EconomicAgent(
agent_id=agent_id,
capital_rate=self.parameters.r_rate,
growth_rate=self.parameters.g_rate,
initial_capital=initial_capital,
initial_consumption=initial_consumption
)
self.agents.append(agent)
def step(self) -> SimulationSnapshot:
"""
Perform one simulation step for all agents.
Returns:
Snapshot of the current simulation state
"""
# Step all agents
for agent in self.agents:
agent.step()
# Create snapshot
snapshot = self._create_snapshot()
self.snapshots.append(snapshot)
self.current_iteration += 1
return snapshot
def run_simulation(self, iterations: Optional[int] = None) -> List[SimulationSnapshot]:
"""
Run the complete simulation for specified iterations.
Args:
iterations: Number of iterations to run (uses parameter default if None)
Returns:
List of snapshots for each iteration
"""
if iterations is None:
iterations = self.parameters.iterations
self.is_running = True
self.start_time = time.time()
try:
for _ in range(iterations):
if not self.is_running:
break
self.step()
finally:
self.is_running = False
return self.snapshots.copy()
def _create_snapshot(self) -> SimulationSnapshot:
"""Create a snapshot of current simulation state."""
# Collect wealth data
wealth_data = []
consumption_data = []
total_wealth_data = []
for agent in self.agents:
if agent.wealth_history and agent.consumption_history:
wealth = agent.wealth_history[-1]
consumption = agent.consumption_history[-1]
else:
wealth = agent.initial_capital
consumption = agent.initial_consumption
wealth_data.append(wealth)
consumption_data.append(consumption)
total_wealth_data.append(wealth + consumption)
# Calculate metrics
total_wealth = sum(total_wealth_data)
gini = self._calculate_gini_coefficient(total_wealth_data)
wealth_conc = self._calculate_wealth_concentration(total_wealth_data, 0.1)
capital_share = sum(wealth_data) / total_wealth if total_wealth > 0 else 0
return SimulationSnapshot(
iteration=self.current_iteration,
timestamp=time.time(),
wealth_distribution=wealth_data,
consumption_distribution=consumption_data,
total_wealth=total_wealth,
gini_coefficient=gini,
wealth_concentration_top10=wealth_conc,
capital_share=capital_share,
average_wealth=np.mean(total_wealth_data),
median_wealth=np.median(total_wealth_data)
)
def _calculate_gini_coefficient(self, wealth_data: List[float]) -> float:
"""
Calculate the Gini coefficient for wealth inequality.
Args:
wealth_data: List of wealth values
Returns:
Gini coefficient between 0 (perfect equality) and 1 (perfect inequality)
"""
if not wealth_data or len(wealth_data) < 2:
return 0.0
# Sort wealth data
sorted_wealth = sorted(wealth_data)
n = len(sorted_wealth)
# Calculate Gini coefficient
cumsum = np.cumsum(sorted_wealth)
total_wealth = cumsum[-1]
if total_wealth == 0:
return 0.0
# Gini coefficient formula
gini = (2 * sum((i + 1) * wealth for i, wealth in enumerate(sorted_wealth))) / (n * total_wealth) - (n + 1) / n
return max(0.0, min(1.0, gini))
def _calculate_wealth_concentration(self, wealth_data: List[float], top_percentage: float) -> float:
"""
Calculate the share of total wealth held by the top percentage of agents.
Args:
wealth_data: List of wealth values
top_percentage: Percentage of top agents (e.g., 0.1 for top 10%)
Returns:
Share of total wealth held by top agents
"""
if not wealth_data:
return 0.0
sorted_wealth = sorted(wealth_data, reverse=True)
total_wealth = sum(sorted_wealth)
if total_wealth == 0:
return 0.0
top_count = max(1, int(len(sorted_wealth) * top_percentage))
top_wealth = sum(sorted_wealth[:top_count])
return top_wealth / total_wealth
def get_latest_snapshot(self) -> Optional[SimulationSnapshot]:
"""Get the most recent simulation snapshot."""
return self.snapshots[-1] if self.snapshots else None
def get_snapshot_at_iteration(self, iteration: int) -> Optional[SimulationSnapshot]:
"""Get snapshot at specific iteration."""
for snapshot in self.snapshots:
if snapshot.iteration == iteration:
return snapshot
return None
def get_wealth_evolution(self) -> Tuple[List[int], List[float], List[float]]:
"""
Get wealth evolution data for plotting.
Returns:
Tuple of (iterations, total_wealth_over_time, gini_over_time)
"""
if not self.snapshots:
return [], [], []
iterations = [s.iteration for s in self.snapshots]
total_wealth = [s.total_wealth for s in self.snapshots]
gini_coefficients = [s.gini_coefficient for s in self.snapshots]
return iterations, total_wealth, gini_coefficients
def get_agent_wealth_distribution(self) -> Dict[str, List[float]]:
"""
Get current wealth distribution across all agents.
Returns:
Dictionary with agent IDs and their current wealth values
"""
distribution = {}
for agent in self.agents:
if agent.wealth_history:
distribution[agent.agent_id] = agent.wealth_history[-1]
else:
distribution[agent.agent_id] = agent.initial_capital
return distribution
def update_parameters(self, new_parameters: SimulationParameters):
"""
Update simulation parameters and restart with new settings.
Args:
new_parameters: New simulation configuration
"""
self.parameters = new_parameters
self.current_iteration = 0
self.snapshots.clear()
self._initialize_agents()
def stop_simulation(self):
"""Stop the running simulation."""
self.is_running = False
def reset_simulation(self):
"""Reset simulation to initial state."""
self.current_iteration = 0
self.snapshots.clear()
self.is_running = False
self._initialize_agents()
def export_data(self, format_type: str = 'json') -> str:
"""
Export simulation data in specified format.
Args:
format_type: Export format ('json' or 'csv')
Returns:
Formatted data string
"""
if format_type.lower() == 'json':
return self._export_json()
elif format_type.lower() == 'csv':
return self._export_csv()
else:
raise ValueError("Format must be 'json' or 'csv'")
def _export_json(self) -> str:
"""Export simulation data as JSON."""
data = {
'simulation_id': self.simulation_id,
'parameters': {
'r_rate': self.parameters.r_rate,
'g_rate': self.parameters.g_rate,
'initial_capital': self.parameters.initial_capital,
'initial_consumption': self.parameters.initial_consumption,
'num_agents': self.parameters.num_agents,
'iterations': self.parameters.iterations
},
'snapshots': []
}
for snapshot in self.snapshots:
snapshot_data = {
'iteration': snapshot.iteration,
'timestamp': snapshot.timestamp,
'total_wealth': snapshot.total_wealth,
'gini_coefficient': snapshot.gini_coefficient,
'wealth_concentration_top10': snapshot.wealth_concentration_top10,
'capital_share': snapshot.capital_share,
'average_wealth': snapshot.average_wealth,
'median_wealth': snapshot.median_wealth
}
data['snapshots'].append(snapshot_data)
return json.dumps(data, indent=2)
def _export_csv(self) -> str:
"""Export simulation data as CSV."""
if not self.snapshots:
return "iteration,total_wealth,gini_coefficient,wealth_concentration_top10,capital_share,average_wealth,median_wealth\n"
lines = ["iteration,total_wealth,gini_coefficient,wealth_concentration_top10,capital_share,average_wealth,median_wealth"]
for snapshot in self.snapshots:
line = f"{snapshot.iteration},{snapshot.total_wealth},{snapshot.gini_coefficient}," \
f"{snapshot.wealth_concentration_top10},{snapshot.capital_share}," \
f"{snapshot.average_wealth},{snapshot.median_wealth}"
lines.append(line)
return "\n".join(lines)
class SimulationManager:
"""
Manages multiple simulation instances for concurrent access.
"""
def __init__(self):
"""Initialize the simulation manager."""
self.simulations: Dict[str, EconomicSimulation] = {}
self.active_simulations: Dict[str, EconomicSimulation] = {}
def create_simulation(self, parameters: SimulationParameters) -> str:
"""
Create a new simulation instance.
Args:
parameters: Simulation configuration
Returns:
Unique simulation ID
"""
simulation = EconomicSimulation(parameters)
simulation_id = simulation.simulation_id
self.simulations[simulation_id] = simulation
return simulation_id
def get_simulation(self, simulation_id: str) -> Optional[EconomicSimulation]:
"""
Get simulation instance by ID.
Args:
simulation_id: Unique simulation identifier
Returns:
Simulation instance or None if not found
"""
return self.simulations.get(simulation_id)
def start_simulation(self, simulation_id: str, iterations: Optional[int] = None) -> bool:
"""
Start running a simulation.
Args:
simulation_id: Unique simulation identifier
iterations: Number of iterations to run
Returns:
True if simulation started successfully
"""
simulation = self.get_simulation(simulation_id)
if not simulation:
return False
self.active_simulations[simulation_id] = simulation
# In a real application, this would run in a separate thread
simulation.run_simulation(iterations)
return True
def stop_simulation(self, simulation_id: str) -> bool:
"""
Stop a running simulation.
Args:
simulation_id: Unique simulation identifier
Returns:
True if simulation stopped successfully
"""
simulation = self.get_simulation(simulation_id)
if simulation:
simulation.stop_simulation()
self.active_simulations.pop(simulation_id, None)
return True
return False
def delete_simulation(self, simulation_id: str) -> bool:
"""
Delete a simulation instance.
Args:
simulation_id: Unique simulation identifier
Returns:
True if simulation deleted successfully
"""
if simulation_id in self.simulations:
self.stop_simulation(simulation_id)
del self.simulations[simulation_id]
return True
return False
def list_simulations(self) -> List[Dict]:
"""
List all simulation instances with their status.
Returns:
List of simulation information dictionaries
"""
simulations_info = []
for sim_id, simulation in self.simulations.items():
info = {
'simulation_id': sim_id,
'is_running': simulation.is_running,
'current_iteration': simulation.current_iteration,
'total_iterations': simulation.parameters.iterations,
'r_rate': simulation.parameters.r_rate,
'g_rate': simulation.parameters.g_rate,
'num_agents': simulation.parameters.num_agents
}
simulations_info.append(info)
return simulations_info

339
app/models/markov_chain.py Normal file
View File

@@ -0,0 +1,339 @@
"""
Markov Chain Models for Economic Simulation
This module implements the core Markov chain models representing:
1. Capitalist Chain (M-C-M'): Money → Commodities → More Money
2. Consumer Chain (C-M-C): Commodities → Money → Commodities
Based on Marx's economic theory and Piketty's inequality principle (r > g).
"""
import numpy as np
from typing import List, Dict, Tuple
import random
class MarkovChain:
"""
Base class for Markov chain implementations.
Provides the foundation for economic state transitions with configurable
transition probabilities and state tracking.
"""
def __init__(self, states: List[str], transition_probability: float, initial_state: str):
"""
Initialize the Markov chain.
Args:
states: List of possible states
transition_probability: Base probability for state transitions
initial_state: Starting state for the chain
"""
self.states = states
self.transition_probability = max(0.0, min(1.0, transition_probability))
self.current_state = initial_state
self.state_history = [initial_state]
self.iteration_count = 0
# Validate initial state
if initial_state not in states:
raise ValueError(f"Initial state '{initial_state}' not in states list")
def get_transition_matrix(self) -> np.ndarray:
"""
Get the transition probability matrix for this chain.
Must be implemented by subclasses.
Returns:
numpy array representing the transition matrix
"""
raise NotImplementedError("Subclasses must implement get_transition_matrix")
def step(self) -> str:
"""
Perform one step in the Markov chain.
Returns:
The new current state after the transition
"""
current_index = self.states.index(self.current_state)
transition_matrix = self.get_transition_matrix()
probabilities = transition_matrix[current_index]
# Choose next state based on probabilities
next_state_index = np.random.choice(len(self.states), p=probabilities)
self.current_state = self.states[next_state_index]
self.state_history.append(self.current_state)
self.iteration_count += 1
return self.current_state
def simulate(self, iterations: int) -> List[str]:
"""
Run the Markov chain for multiple iterations.
Args:
iterations: Number of steps to simulate
Returns:
List of states visited during simulation
"""
for _ in range(iterations):
self.step()
return self.state_history.copy()
def get_state_distribution(self) -> Dict[str, float]:
"""
Calculate the current state distribution from history.
Returns:
Dictionary mapping states to their frequency ratios
"""
if not self.state_history:
return {state: 0.0 for state in self.states}
total_count = len(self.state_history)
distribution = {}
for state in self.states:
count = self.state_history.count(state)
distribution[state] = count / total_count
return distribution
def reset(self, initial_state: str = None):
"""
Reset the chain to initial conditions.
Args:
initial_state: New initial state (optional)
"""
if initial_state and initial_state in self.states:
self.current_state = initial_state
else:
self.current_state = self.state_history[0]
self.state_history = [self.current_state]
self.iteration_count = 0
class CapitalistChain(MarkovChain):
"""
Represents the capitalist economic cycle: M-C-M' (Money → Commodities → More Money)
This chain models capital accumulation where money is invested in commodities
to generate more money, with returns based on the capital rate (r).
"""
def __init__(self, capital_rate: float):
"""
Initialize the capitalist chain.
Args:
capital_rate: The rate of return on capital (r)
"""
states = ['Money', 'Commodities', 'Enhanced_Money']
super().__init__(states, capital_rate, 'Money')
self.capital_rate = capital_rate
self.wealth_multiplier = 1.0
def get_transition_matrix(self) -> np.ndarray:
"""
Create transition matrix for M-C-M' cycle.
The matrix ensures:
- Money transitions to Commodities with probability r
- Commodities always transition to Enhanced_Money (probability 1)
- Enhanced_Money always transitions back to Money (probability 1)
Returns:
3x3 transition probability matrix
"""
r = self.transition_probability
# States: ['Money', 'Commodities', 'Enhanced_Money']
matrix = np.array([
[1-r, r, 0.0], # Money -> stay or go to Commodities
[0.0, 0.0, 1.0], # Commodities -> Enhanced_Money (always)
[1.0, 0.0, 0.0] # Enhanced_Money -> Money (always)
])
return matrix
def step(self) -> str:
"""
Perform one step and update wealth when completing a cycle.
Returns:
The new current state
"""
previous_state = self.current_state
new_state = super().step()
# If we completed a full cycle (Enhanced_Money -> Money), increase wealth
if previous_state == 'Enhanced_Money' and new_state == 'Money':
self.wealth_multiplier *= (1 + self.capital_rate)
return new_state
def get_current_wealth(self) -> float:
"""
Get the current accumulated wealth.
Returns:
Current wealth multiplier representing accumulated capital
"""
return self.wealth_multiplier
class ConsumerChain(MarkovChain):
"""
Represents the consumer economic cycle: C-M-C (Commodities → Money → Commodities)
This chain models consumption patterns where commodities are exchanged for money
to purchase other commodities, growing with the economic growth rate (g).
"""
def __init__(self, growth_rate: float):
"""
Initialize the consumer chain.
Args:
growth_rate: The economic growth rate (g)
"""
states = ['Commodities', 'Money', 'New_Commodities']
super().__init__(states, growth_rate, 'Commodities')
self.growth_rate = growth_rate
self.consumption_value = 1.0
def get_transition_matrix(self) -> np.ndarray:
"""
Create transition matrix for C-M-C cycle.
The matrix ensures:
- Commodities transition to Money with probability g
- Money always transitions to New_Commodities (probability 1)
- New_Commodities always transition back to Commodities (probability 1)
Returns:
3x3 transition probability matrix
"""
g = self.transition_probability
# States: ['Commodities', 'Money', 'New_Commodities']
matrix = np.array([
[1-g, g, 0.0], # Commodities -> stay or go to Money
[0.0, 0.0, 1.0], # Money -> New_Commodities (always)
[1.0, 0.0, 0.0] # New_Commodities -> Commodities (always)
])
return matrix
def step(self) -> str:
"""
Perform one step and update consumption value when completing a cycle.
Returns:
The new current state
"""
previous_state = self.current_state
new_state = super().step()
# If we completed a full cycle (New_Commodities -> Commodities), grow consumption
if previous_state == 'New_Commodities' and new_state == 'Commodities':
self.consumption_value *= (1 + self.growth_rate)
return new_state
def get_current_consumption(self) -> float:
"""
Get the current consumption value.
Returns:
Current consumption value representing accumulated consumption capacity
"""
return self.consumption_value
class EconomicAgent:
"""
Represents an individual economic agent with both capitalist and consumer behaviors.
Each agent operates both chains simultaneously, allowing for mixed economic activity
and wealth accumulation patterns.
"""
def __init__(self, agent_id: str, capital_rate: float, growth_rate: float,
initial_capital: float = 1000.0, initial_consumption: float = 1000.0):
"""
Initialize an economic agent.
Args:
agent_id: Unique identifier for the agent
capital_rate: Capital return rate (r)
growth_rate: Economic growth rate (g)
initial_capital: Starting capital amount
initial_consumption: Starting consumption capacity
"""
self.agent_id = agent_id
self.capitalist_chain = CapitalistChain(capital_rate)
self.consumer_chain = ConsumerChain(growth_rate)
self.initial_capital = initial_capital
self.initial_consumption = initial_consumption
# Track wealth over time
self.wealth_history = []
self.consumption_history = []
def step(self) -> Tuple[float, float]:
"""
Perform one simulation step for both chains.
Returns:
Tuple of (current_wealth, current_consumption)
"""
# Step both chains
self.capitalist_chain.step()
self.consumer_chain.step()
# Calculate current values
current_wealth = self.initial_capital * self.capitalist_chain.get_current_wealth()
current_consumption = self.initial_consumption * self.consumer_chain.get_current_consumption()
# Store history
self.wealth_history.append(current_wealth)
self.consumption_history.append(current_consumption)
return current_wealth, current_consumption
def get_total_wealth(self) -> float:
"""
Get the agent's total economic value.
Returns:
Sum of capital wealth and consumption capacity
"""
if not self.wealth_history or not self.consumption_history:
return self.initial_capital + self.initial_consumption
return self.wealth_history[-1] + self.consumption_history[-1]
def get_wealth_ratio(self) -> float:
"""
Get the ratio of capital wealth to total wealth.
Returns:
Ratio representing the capitalist vs consumer wealth proportion
"""
total = self.get_total_wealth()
if total == 0:
return 0.0
if not self.wealth_history:
return self.initial_capital / (self.initial_capital + self.initial_consumption)
return self.wealth_history[-1] / total

11
app/routes/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Flask routes package.
Contains the main web interface routes and API endpoints for the
Markov economics simulation application.
"""
from .main import main_bp
from .api import api_bp
__all__ = ['main_bp', 'api_bp']

Binary file not shown.

Binary file not shown.

Binary file not shown.

383
app/routes/api.py Normal file
View File

@@ -0,0 +1,383 @@
"""
API Routes for Simulation Control
Provides REST API endpoints for controlling simulations, retrieving data,
and managing simulation parameters with real-time updates via SocketIO.
"""
from flask import Blueprint, request, jsonify, current_app
from flask_socketio import emit, join_room, leave_room
import threading
import time
from typing import Optional
from app import socketio
from app.models import SimulationManager, SimulationParameters
from app.routes.main import simulation_manager
api_bp = Blueprint('api', __name__)
@api_bp.route('/simulation', methods=['POST'])
def create_simulation():
"""
Create a new simulation with specified parameters.
Request JSON:
{
"r_rate": 0.05,
"g_rate": 0.03,
"initial_capital": 1000,
"initial_consumption": 1000,
"num_agents": 100,
"iterations": 1000
}
Returns:
JSON response with simulation ID and status
"""
try:
data = request.get_json()
if not data:
return jsonify({'error': 'No JSON data provided'}), 400
# Validate and create parameters
params = SimulationParameters(
r_rate=float(data.get('r_rate', 0.05)),
g_rate=float(data.get('g_rate', 0.03)),
initial_capital=float(data.get('initial_capital', 1000)),
initial_consumption=float(data.get('initial_consumption', 1000)),
num_agents=int(data.get('num_agents', 100)),
iterations=int(data.get('iterations', 1000))
)
# Create simulation
simulation_id = simulation_manager.create_simulation(params)
return jsonify({
'simulation_id': simulation_id,
'status': 'created',
'parameters': {
'r_rate': params.r_rate,
'g_rate': params.g_rate,
'initial_capital': params.initial_capital,
'initial_consumption': params.initial_consumption,
'num_agents': params.num_agents,
'iterations': params.iterations
}
}), 201
except ValueError as e:
return jsonify({'error': f'Invalid parameter: {str(e)}'}), 400
except Exception as e:
current_app.logger.error(f"Error creating simulation: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulation/<simulation_id>/start', methods=['POST'])
def start_simulation(simulation_id: str):
"""
Start running a simulation.
Args:
simulation_id: Unique simulation identifier
Returns:
JSON response indicating success or failure
"""
try:
simulation = simulation_manager.get_simulation(simulation_id)
if not simulation:
return jsonify({'error': 'Simulation not found'}), 404
if simulation.is_running:
return jsonify({'error': 'Simulation already running'}), 400
# Start simulation in background thread
def run_simulation_background():
try:
# Run simulation with progress updates
total_iterations = simulation.parameters.iterations
for i in range(total_iterations):
if not simulation.is_running:
break
snapshot = simulation.step()
# Emit progress update every 10 iterations or at milestones
if i % 10 == 0 or i == total_iterations - 1:
progress_data = {
'simulation_id': simulation_id,
'iteration': snapshot.iteration,
'total_iterations': total_iterations,
'progress_percentage': (snapshot.iteration / total_iterations) * 100,
'total_wealth': snapshot.total_wealth,
'gini_coefficient': snapshot.gini_coefficient,
'capital_share': snapshot.capital_share,
'wealth_concentration_top10': snapshot.wealth_concentration_top10
}
socketio.emit('simulation_progress', progress_data,
room=f'simulation_{simulation_id}')
# Small delay to allow real-time visualization
time.sleep(0.01)
# Emit completion
socketio.emit('simulation_complete', {
'simulation_id': simulation_id,
'total_snapshots': len(simulation.snapshots)
}, room=f'simulation_{simulation_id}')
except Exception as e:
current_app.logger.error(f"Error in simulation thread: {str(e)}")
socketio.emit('simulation_error', {
'simulation_id': simulation_id,
'error': str(e)
}, room=f'simulation_{simulation_id}')
# Start background thread
thread = threading.Thread(target=run_simulation_background)
thread.daemon = True
thread.start()
return jsonify({
'simulation_id': simulation_id,
'status': 'started',
'message': 'Simulation started successfully'
})
except Exception as e:
current_app.logger.error(f"Error starting simulation: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulation/<simulation_id>/stop', methods=['POST'])
def stop_simulation(simulation_id: str):
"""
Stop a running simulation.
Args:
simulation_id: Unique simulation identifier
Returns:
JSON response indicating success or failure
"""
try:
success = simulation_manager.stop_simulation(simulation_id)
if not success:
return jsonify({'error': 'Simulation not found or not running'}), 404
# Emit stop event
socketio.emit('simulation_stopped', {
'simulation_id': simulation_id
}, room=f'simulation_{simulation_id}')
return jsonify({
'simulation_id': simulation_id,
'status': 'stopped',
'message': 'Simulation stopped successfully'
})
except Exception as e:
current_app.logger.error(f"Error stopping simulation: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulation/<simulation_id>/data', methods=['GET'])
def get_simulation_data(simulation_id: str):
"""
Get current simulation data and snapshots.
Args:
simulation_id: Unique simulation identifier
Returns:
JSON response with simulation data
"""
try:
simulation = simulation_manager.get_simulation(simulation_id)
if not simulation:
return jsonify({'error': 'Simulation not found'}), 404
# Get specific iteration if requested
iteration = request.args.get('iteration', type=int)
if iteration is not None:
snapshot = simulation.get_snapshot_at_iteration(iteration)
if not snapshot:
return jsonify({'error': f'No data for iteration {iteration}'}), 404
return jsonify({
'simulation_id': simulation_id,
'snapshot': {
'iteration': snapshot.iteration,
'total_wealth': snapshot.total_wealth,
'gini_coefficient': snapshot.gini_coefficient,
'wealth_concentration_top10': snapshot.wealth_concentration_top10,
'capital_share': snapshot.capital_share,
'average_wealth': snapshot.average_wealth,
'median_wealth': snapshot.median_wealth
}
})
# Get latest snapshot and evolution data
latest_snapshot = simulation.get_latest_snapshot()
iterations, total_wealth, gini_coefficients = simulation.get_wealth_evolution()
response_data = {
'simulation_id': simulation_id,
'is_running': simulation.is_running,
'current_iteration': simulation.current_iteration,
'total_snapshots': len(simulation.snapshots),
'parameters': {
'r_rate': simulation.parameters.r_rate,
'g_rate': simulation.parameters.g_rate,
'num_agents': simulation.parameters.num_agents,
'iterations': simulation.parameters.iterations
}
}
if latest_snapshot:
response_data['latest_snapshot'] = {
'iteration': latest_snapshot.iteration,
'total_wealth': latest_snapshot.total_wealth,
'gini_coefficient': latest_snapshot.gini_coefficient,
'wealth_concentration_top10': latest_snapshot.wealth_concentration_top10,
'capital_share': latest_snapshot.capital_share,
'average_wealth': latest_snapshot.average_wealth,
'median_wealth': latest_snapshot.median_wealth
}
# Include evolution data if requested
if request.args.get('include_evolution', '').lower() == 'true':
response_data['evolution'] = {
'iterations': iterations,
'total_wealth': total_wealth,
'gini_coefficients': gini_coefficients
}
return jsonify(response_data)
except Exception as e:
current_app.logger.error(f"Error getting simulation data: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulation/<simulation_id>/export/<format_type>', methods=['GET'])
def export_simulation_data(simulation_id: str, format_type: str):
"""
Export simulation data in specified format.
Args:
simulation_id: Unique simulation identifier
format_type: Export format ('json' or 'csv')
Returns:
Data in requested format
"""
try:
simulation = simulation_manager.get_simulation(simulation_id)
if not simulation:
return jsonify({'error': 'Simulation not found'}), 404
if format_type.lower() not in ['json', 'csv']:
return jsonify({'error': 'Format must be json or csv'}), 400
exported_data = simulation.export_data(format_type)
if format_type.lower() == 'json':
return jsonify({'data': exported_data})
else: # CSV
return exported_data, 200, {
'Content-Type': 'text/csv',
'Content-Disposition': f'attachment; filename=simulation_{simulation_id[:8]}.csv'
}
except Exception as e:
current_app.logger.error(f"Error exporting simulation data: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulation/<simulation_id>', methods=['DELETE'])
def delete_simulation(simulation_id: str):
"""
Delete a simulation.
Args:
simulation_id: Unique simulation identifier
Returns:
JSON response indicating success or failure
"""
try:
success = simulation_manager.delete_simulation(simulation_id)
if not success:
return jsonify({'error': 'Simulation not found'}), 404
return jsonify({
'simulation_id': simulation_id,
'status': 'deleted',
'message': 'Simulation deleted successfully'
})
except Exception as e:
current_app.logger.error(f"Error deleting simulation: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
@api_bp.route('/simulations', methods=['GET'])
def list_simulations():
"""
List all simulations with their status.
Returns:
JSON response with list of simulation information
"""
try:
simulations_info = simulation_manager.list_simulations()
return jsonify({
'simulations': simulations_info,
'total_count': len(simulations_info)
})
except Exception as e:
current_app.logger.error(f"Error listing simulations: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
# SocketIO event handlers
@socketio.on('join_simulation')
def on_join_simulation(data):
"""Handle client joining simulation room for real-time updates."""
simulation_id = data.get('simulation_id')
if simulation_id:
join_room(f'simulation_{simulation_id}')
emit('joined_simulation', {'simulation_id': simulation_id})
@socketio.on('leave_simulation')
def on_leave_simulation(data):
"""Handle client leaving simulation room."""
simulation_id = data.get('simulation_id')
if simulation_id:
leave_room(f'simulation_{simulation_id}')
emit('left_simulation', {'simulation_id': simulation_id})
@socketio.on('connect')
def on_connect():
"""Handle client connection."""
emit('connected', {'status': 'connected'})
@socketio.on('disconnect')
def on_disconnect():
"""Handle client disconnection."""
print('Client disconnected')

149
app/routes/main.py Normal file
View File

@@ -0,0 +1,149 @@
"""
Main Flask Routes
Handles the primary web interface for the Markov economics simulation,
including the main simulation page and basic navigation.
"""
from flask import Blueprint, render_template, request, jsonify
from app.models import SimulationManager, SimulationParameters
main_bp = Blueprint('main', __name__)
# Global simulation manager instance
simulation_manager = SimulationManager()
@main_bp.route('/')
def index():
"""
Main simulation interface page.
Returns:
Rendered HTML template with simulation controls
"""
# Default parameters for display
default_params = {
'r_rate': 0.05, # 5% capital return rate
'g_rate': 0.03, # 3% economic growth rate
'initial_capital': 1000.0,
'initial_consumption': 1000.0,
'num_agents': 100,
'iterations': 1000
}
return render_template('simulation.html',
default_params=default_params,
title="Markov Economics - Capitalism Eats the World")
@main_bp.route('/simulation')
def simulation():
"""
Alternative route to the main simulation page.
Returns:
Rendered HTML template with simulation controls
"""
return index()
@main_bp.route('/about')
def about():
"""
Information page about the economic theory behind the simulation.
Returns:
Rendered HTML template with theoretical background
"""
return render_template('about.html',
title="About - Markov Economics Theory")
@main_bp.route('/results/<simulation_id>')
def results(simulation_id):
"""
Display results for a specific simulation.
Args:
simulation_id: Unique identifier for the simulation
Returns:
Rendered results template or 404 if simulation not found
"""
simulation = simulation_manager.get_simulation(simulation_id)
if not simulation:
return render_template('error.html',
error_message=f"Simulation {simulation_id} not found",
title="Simulation Not Found"), 404
# Get simulation summary data
latest_snapshot = simulation.get_latest_snapshot()
iterations, total_wealth, gini_coefficients = simulation.get_wealth_evolution()
summary_data = {
'simulation_id': simulation_id,
'parameters': {
'r_rate': simulation.parameters.r_rate,
'g_rate': simulation.parameters.g_rate,
'num_agents': simulation.parameters.num_agents,
'iterations': simulation.parameters.iterations
},
'current_iteration': simulation.current_iteration,
'total_iterations': len(simulation.snapshots),
'latest_snapshot': latest_snapshot,
'has_data': len(simulation.snapshots) > 0
}
return render_template('results.html',
simulation_data=summary_data,
title=f"Results - Simulation {simulation_id[:8]}")
@main_bp.route('/health')
def health_check():
"""
Simple health check endpoint.
Returns:
JSON response indicating service status
"""
return jsonify({
'status': 'healthy',
'service': 'markov-economics',
'active_simulations': len(simulation_manager.active_simulations),
'total_simulations': len(simulation_manager.simulations)
})
@main_bp.errorhandler(404)
def not_found_error(error):
"""
Handle 404 errors with custom template.
Args:
error: The error object
Returns:
Rendered error template
"""
return render_template('error.html',
error_message="Page not found",
title="Page Not Found"), 404
@main_bp.errorhandler(500)
def internal_error(error):
"""
Handle 500 errors with custom template.
Args:
error: The error object
Returns:
Rendered error template
"""
return render_template('error.html',
error_message="Internal server error occurred",
title="Server Error"), 500

269
app/static/css/style.css Normal file
View File

@@ -0,0 +1,269 @@
/* Custom CSS for Markov Economics Application */
/* Global Styles */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Navigation */
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
}
/* Cards and Components */
.card {
border: none;
border-radius: 10px;
}
.card-header {
border-radius: 10px 10px 0 0 !important;
font-weight: 600;
}
.parameter-card {
border-left: 4px solid #007bff;
transition: all 0.3s ease;
}
.parameter-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important;
}
/* Form Controls */
.form-range {
height: 8px;
border-radius: 5px;
}
.form-range::-webkit-slider-thumb {
height: 20px;
width: 20px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
.form-range::-moz-range-thumb {
height: 20px;
width: 20px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
}
/* Buttons */
.btn-simulation {
border-radius: 25px;
font-weight: 600;
padding: 10px 20px;
transition: all 0.3s ease;
}
.btn-simulation:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* Charts */
.chart-container {
position: relative;
height: 350px;
width: 100%;
}
.chart-container canvas {
border-radius: 8px;
}
/* Metrics Cards */
.metrics-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
transition: all 0.3s ease;
}
.metrics-card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
}
.inequality-warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border: none;
transition: all 0.3s ease;
}
.inequality-warning:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(240, 147, 251, 0.3);
}
/* Progress Bar */
.progress-custom {
height: 25px;
border-radius: 15px;
background-color: #e9ecef;
overflow: hidden;
}
.progress-custom .progress-bar {
border-radius: 15px;
font-weight: 600;
font-size: 0.9rem;
line-height: 25px;
}
/* Simulation Status */
.simulation-status {
border-radius: 20px;
padding: 8px 16px;
font-weight: 600;
transition: all 0.3s ease;
}
.status-ready {
background-color: #6c757d !important;
}
.status-running {
background: linear-gradient(90deg, #28a745, #20c997) !important;
animation: pulse 2s infinite;
}
.status-complete {
background-color: #007bff !important;
}
.status-error {
background-color: #dc3545 !important;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); }
100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}
/* Alerts */
.alert {
border: none;
border-radius: 10px;
}
/* Tooltips */
.tooltip {
font-size: 0.85rem;
}
/* Loading Spinner */
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Responsive adjustments */
@media (max-width: 768px) {
.chart-container {
height: 250px;
}
.parameter-card {
margin-bottom: 20px;
}
.metrics-card .card-body {
padding: 1rem;
}
.btn-simulation {
padding: 8px 16px;
font-size: 0.9rem;
}
}
@media (max-width: 576px) {
.chart-container {
height: 200px;
}
.display-4 {
font-size: 2rem;
}
.card-body {
padding: 1rem;
}
}
/* Animation classes */
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease-in forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.slide-up {
transform: translateY(20px);
opacity: 0;
animation: slideUp 0.5s ease-out forwards;
}
@keyframes slideUp {
to {
transform: translateY(0);
opacity: 1;
}
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Print styles */
@media print {
.navbar, .card-header, .btn, footer {
display: none !important;
}
.card {
border: 1px solid #ddd !important;
}
.chart-container {
height: auto !important;
}
}

281
app/static/js/app.js Normal file
View File

@@ -0,0 +1,281 @@
/**
* Main JavaScript Application for Markov Economics
*
* Provides utility functions and global application behavior
*/
// Global application state
window.MarkovEconomics = {
socket: null,
currentSimulationId: null,
isConnected: false,
// Configuration
config: {
maxRetries: 3,
retryDelay: 1000,
updateInterval: 100
}
};
/**
* Initialize Socket.IO connection
*/
function initializeSocket() {
if (typeof io !== 'undefined') {
window.MarkovEconomics.socket = io();
window.MarkovEconomics.socket.on('connect', function() {
console.log('Connected to server');
window.MarkovEconomics.isConnected = true;
updateConnectionStatus(true);
});
window.MarkovEconomics.socket.on('disconnect', function() {
console.log('Disconnected from server');
window.MarkovEconomics.isConnected = false;
updateConnectionStatus(false);
});
window.MarkovEconomics.socket.on('connect_error', function(error) {
console.error('Connection error:', error);
updateConnectionStatus(false);
});
}
}
/**
* Update connection status indicator
*/
function updateConnectionStatus(connected) {
// You can add a connection status indicator here if needed
if (connected) {
console.log('✅ Real-time connection established');
} else {
console.log('❌ Real-time connection lost');
}
}
/**
* Format numbers for display
*/
function formatNumber(num, decimals = 2) {
if (num === null || num === undefined || isNaN(num)) {
return '0';
}
if (Math.abs(num) >= 1e9) {
return (num / 1e9).toFixed(decimals) + 'B';
} else if (Math.abs(num) >= 1e6) {
return (num / 1e6).toFixed(decimals) + 'M';
} else if (Math.abs(num) >= 1e3) {
return (num / 1e3).toFixed(decimals) + 'K';
} else {
return num.toLocaleString(undefined, {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
});
}
}
/**
* Format currency values
*/
function formatCurrency(amount, decimals = 0) {
if (amount === null || amount === undefined || isNaN(amount)) {
return '$0';
}
return '$' + formatNumber(amount, decimals);
}
/**
* Format percentage values
*/
function formatPercentage(value, decimals = 1) {
if (value === null || value === undefined || isNaN(value)) {
return '0%';
}
return (value * 100).toFixed(decimals) + '%';
}
/**
* Show notification message
*/
function showNotification(message, type = 'info', duration = 3000) {
// Create notification element
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
notification.style.cssText = `
top: 20px;
right: 20px;
z-index: 9999;
min-width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto-remove after duration
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, duration);
return notification;
}
/**
* Show loading spinner
*/
function showLoading(element, text = 'Loading...') {
if (typeof element === 'string') {
element = document.getElementById(element);
}
if (element) {
element.innerHTML = `
<div class="d-flex align-items-center justify-content-center">
<div class="spinner me-2"></div>
<span>${text}</span>
</div>
`;
}
}
/**
* Hide loading spinner
*/
function hideLoading(element, originalContent = '') {
if (typeof element === 'string') {
element = document.getElementById(element);
}
if (element) {
element.innerHTML = originalContent;
}
}
/**
* Make API request with error handling
*/
async function apiRequest(url, options = {}) {
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
},
};
const finalOptions = { ...defaultOptions, ...options };
try {
const response = await fetch(url, finalOptions);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('API Request failed:', error);
showNotification(`Error: ${error.message}`, 'danger');
throw error;
}
}
/**
* Validate parameter ranges
*/
function validateParameters(params) {
const errors = [];
if (params.r_rate < 0 || params.r_rate > 0.25) {
errors.push('Capital rate must be between 0% and 25%');
}
if (params.g_rate < 0 || params.g_rate > 0.20) {
errors.push('Growth rate must be between 0% and 20%');
}
if (params.num_agents < 10 || params.num_agents > 10000) {
errors.push('Number of agents must be between 10 and 10,000');
}
if (params.iterations < 100 || params.iterations > 100000) {
errors.push('Iterations must be between 100 and 100,000');
}
return errors;
}
/**
* Debounce function for parameter changes
*/
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Initialize application
*/
document.addEventListener('DOMContentLoaded', function() {
// Initialize Socket.IO if available
initializeSocket();
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Add fade-in animation to cards
const cards = document.querySelectorAll('.card');
cards.forEach((card, index) => {
card.style.animationDelay = `${index * 0.1}s`;
card.classList.add('fade-in');
});
console.log('🚀 Markov Economics application initialized');
});
/**
* Handle page visibility changes
*/
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
console.log('Page hidden - pausing updates');
} else {
console.log('Page visible - resuming updates');
}
});
/**
* Export functions to global scope
*/
window.MarkovEconomics.utils = {
formatNumber,
formatCurrency,
formatPercentage,
showNotification,
showLoading,
hideLoading,
apiRequest,
validateParameters,
debounce
};

743
app/static/js/simulation.js Normal file
View File

@@ -0,0 +1,743 @@
/**
* Simulation Control and Visualization
*
* Handles the main simulation interface, real-time charting,
* and parameter control for the Markov Economics application.
*/
// Simulation state
let currentSimulation = {
id: null,
isRunning: false,
parameters: {},
data: {
iterations: [],
totalWealth: [],
giniCoefficients: [],
capitalShare: [],
top10Share: []
}
};
// Chart instances
let charts = {
wealthEvolution: null,
inequality: null,
distribution: null
};
/**
* Initialize the simulation interface
*/
function initializeSimulation() {
console.log('Initializing simulation interface...');
// Initialize parameter controls
initializeParameterControls();
// Initialize charts
initializeCharts();
// Initialize event listeners
initializeEventListeners();
// Initialize real-time updates
initializeRealtimeUpdates();
console.log('✅ Simulation interface ready');
}
/**
* Initialize parameter controls with sliders
*/
function initializeParameterControls() {
const controls = [
{ id: 'capitalRate', valueId: 'capitalRateValue', suffix: '%', scale: 100 },
{ id: 'growthRate', valueId: 'growthRateValue', suffix: '%', scale: 100 },
{ id: 'numAgents', valueId: 'numAgentsValue', suffix: '', scale: 1 },
{ id: 'iterations', valueId: 'iterationsValue', suffix: '', scale: 1 }
];
controls.forEach(control => {
const slider = document.getElementById(control.id);
const valueDisplay = document.getElementById(control.valueId);
if (slider && valueDisplay) {
slider.addEventListener('input', function() {
const value = parseFloat(this.value);
const displayValue = (value / control.scale).toFixed(control.scale === 100 ? 1 : 0);
valueDisplay.textContent = displayValue + control.suffix;
// Update inequality warning
updateInequalityWarning();
});
}
});
}
/**
* Update inequality warning based on r vs g
*/
function updateInequalityWarning() {
const capitalRate = parseFloat(document.getElementById('capitalRate').value) / 100;
const growthRate = parseFloat(document.getElementById('growthRate').value) / 100;
const warning = document.getElementById('inequalityAlert');
if (warning) {
if (capitalRate > growthRate) {
warning.style.display = 'block';
warning.className = 'alert alert-danger';
warning.innerHTML = `
<strong>⚠️ r > g Detected!</strong><br>
<small>Capital rate (${(capitalRate * 100).toFixed(1)}%) > Growth rate (${(growthRate * 100).toFixed(1)}%)</small><br>
<small>Wealth inequality will increase over time</small>
`;
} else if (capitalRate === growthRate) {
warning.style.display = 'block';
warning.className = 'alert alert-warning';
warning.innerHTML = `
<strong>⚖️ r = g</strong><br>
<small>Balanced scenario - moderate inequality growth expected</small>
`;
} else {
warning.style.display = 'block';
warning.className = 'alert alert-success';
warning.innerHTML = `
<strong>✅ r < g</strong><br>
<small>Growth rate exceeds capital rate - inequality may decrease</small>
`;
}
}
}
/**
* Initialize Chart.js instances
*/
function initializeCharts() {
// Wealth Evolution Chart
const wealthCtx = document.getElementById('wealthEvolutionChart');
if (wealthCtx) {
charts.wealthEvolution = new Chart(wealthCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Total Wealth',
data: [],
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Wealth Accumulation Over Time'
},
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: 'Iteration'
}
},
y: {
title: {
display: true,
text: 'Total Wealth ($)'
},
ticks: {
callback: function(value) {
return window.MarkovEconomics.utils.formatCurrency(value);
}
}
}
},
animation: {
duration: 300
}
}
});
}
// Inequality Metrics Chart
const inequalityCtx = document.getElementById('inequalityChart');
if (inequalityCtx) {
charts.inequality = new Chart(inequalityCtx, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Gini Coefficient',
data: [],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
yAxisID: 'y',
tension: 0.4
},
{
label: 'Top 10% Share',
data: [],
borderColor: '#fd7e14',
backgroundColor: 'rgba(253, 126, 20, 0.1)',
yAxisID: 'y1',
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Inequality Metrics'
}
},
scales: {
x: {
title: {
display: true,
text: 'Iteration'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Gini Coefficient'
},
min: 0,
max: 1
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Top 10% Share (%)'
},
grid: {
drawOnChartArea: false,
},
ticks: {
callback: function(value) {
return (value * 100).toFixed(0) + '%';
}
}
}
},
animation: {
duration: 300
}
}
});
}
// Wealth Distribution Chart
const distributionCtx = document.getElementById('distributionChart');
if (distributionCtx) {
charts.distribution = new Chart(distributionCtx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Number of Agents',
data: [],
backgroundColor: 'rgba(40, 167, 69, 0.7)',
borderColor: '#28a745',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Wealth Distribution'
},
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: 'Wealth Range'
}
},
y: {
title: {
display: true,
text: 'Number of Agents'
}
}
}
}
});
}
}
/**
* Initialize event listeners
*/
function initializeEventListeners() {
// Start simulation button
const startBtn = document.getElementById('startBtn');
if (startBtn) {
startBtn.addEventListener('click', startSimulation);
}
// Stop simulation button
const stopBtn = document.getElementById('stopBtn');
if (stopBtn) {
stopBtn.addEventListener('click', stopSimulation);
}
// Reset button
const resetBtn = document.getElementById('resetBtn');
if (resetBtn) {
resetBtn.addEventListener('click', resetSimulation);
}
// Export buttons
const exportJsonBtn = document.getElementById('exportJsonBtn');
if (exportJsonBtn) {
exportJsonBtn.addEventListener('click', () => exportData('json'));
}
const exportCsvBtn = document.getElementById('exportCsvBtn');
if (exportCsvBtn) {
exportCsvBtn.addEventListener('click', () => exportData('csv'));
}
}
/**
* Initialize real-time updates via Socket.IO
*/
function initializeRealtimeUpdates() {
if (!window.MarkovEconomics.socket) {
console.warn('Socket.IO not available - real-time updates disabled');
return;
}
const socket = window.MarkovEconomics.socket;
// Simulation progress updates
socket.on('simulation_progress', function(data) {
updateSimulationProgress(data);
});
// Simulation completion
socket.on('simulation_complete', function(data) {
onSimulationComplete(data);
});
// Simulation stopped
socket.on('simulation_stopped', function(data) {
onSimulationStopped(data);
});
// Simulation error
socket.on('simulation_error', function(data) {
onSimulationError(data);
});
}
/**
* Start a new simulation
*/
async function startSimulation() {
try {
// Get parameters from UI
const parameters = getSimulationParameters();
// Validate parameters
const errors = window.MarkovEconomics.utils.validateParameters(parameters);
if (errors.length > 0) {
window.MarkovEconomics.utils.showNotification(
'Parameter validation failed:<br>' + errors.join('<br>'),
'danger'
);
return;
}
// Update UI state
updateUIState('starting');
// Create simulation
const createResponse = await window.MarkovEconomics.utils.apiRequest('/api/simulation', {
method: 'POST',
body: JSON.stringify(parameters)
});
currentSimulation.id = createResponse.simulation_id;
currentSimulation.parameters = parameters;
// Join simulation room for real-time updates
if (window.MarkovEconomics.socket) {
window.MarkovEconomics.socket.emit('join_simulation', {
simulation_id: currentSimulation.id
});
}
// Start simulation
await window.MarkovEconomics.utils.apiRequest(`/api/simulation/${currentSimulation.id}/start`, {
method: 'POST'
});
currentSimulation.isRunning = true;
updateUIState('running');
window.MarkovEconomics.utils.showNotification('Simulation started successfully!', 'success');
} catch (error) {
console.error('Failed to start simulation:', error);
updateUIState('error');
}
}
/**
* Stop the current simulation
*/
async function stopSimulation() {
if (!currentSimulation.id) return;
try {
await window.MarkovEconomics.utils.apiRequest(`/api/simulation/${currentSimulation.id}/stop`, {
method: 'POST'
});
currentSimulation.isRunning = false;
updateUIState('stopped');
window.MarkovEconomics.utils.showNotification('Simulation stopped', 'info');
} catch (error) {
console.error('Failed to stop simulation:', error);
}
}
/**
* Reset simulation state
*/
function resetSimulation() {
currentSimulation = {
id: null,
isRunning: false,
parameters: {},
data: {
iterations: [],
totalWealth: [],
giniCoefficients: [],
capitalShare: [],
top10Share: []
}
};
// Clear charts
Object.values(charts).forEach(chart => {
if (chart) {
chart.data.labels = [];
chart.data.datasets.forEach(dataset => {
dataset.data = [];
});
chart.update();
}
});
// Reset metrics
updateMetricsDisplay({
total_wealth: 0,
gini_coefficient: 0,
wealth_concentration_top10: 0,
capital_share: 0
});
updateUIState('ready');
window.MarkovEconomics.utils.showNotification('Simulation reset', 'info');
}
/**
* Get current simulation parameters from UI
*/
function getSimulationParameters() {
return {
r_rate: parseFloat(document.getElementById('capitalRate').value) / 100,
g_rate: parseFloat(document.getElementById('growthRate').value) / 100,
num_agents: parseInt(document.getElementById('numAgents').value),
iterations: parseInt(document.getElementById('iterations').value),
initial_capital: 1000,
initial_consumption: 1000
};
}
/**
* Update UI state based on simulation status
*/
function updateUIState(state) {
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const resetBtn = document.getElementById('resetBtn');
const status = document.getElementById('simulationStatus');
const progress = document.getElementById('progressContainer');
const exportBtns = [
document.getElementById('exportJsonBtn'),
document.getElementById('exportCsvBtn')
];
switch (state) {
case 'starting':
if (startBtn) startBtn.disabled = true;
if (stopBtn) stopBtn.disabled = true;
if (resetBtn) resetBtn.disabled = true;
if (status) {
status.textContent = 'Starting...';
status.className = 'simulation-status bg-warning text-dark';
}
break;
case 'running':
if (startBtn) startBtn.disabled = true;
if (stopBtn) stopBtn.disabled = false;
if (resetBtn) resetBtn.disabled = true;
if (status) {
status.textContent = 'Running';
status.className = 'simulation-status status-running';
}
if (progress) progress.style.display = 'block';
break;
case 'complete':
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
if (resetBtn) resetBtn.disabled = false;
if (status) {
status.textContent = 'Complete';
status.className = 'simulation-status status-complete';
}
if (progress) progress.style.display = 'none';
exportBtns.forEach(btn => {
if (btn) btn.disabled = false;
});
break;
case 'stopped':
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
if (resetBtn) resetBtn.disabled = false;
if (status) {
status.textContent = 'Stopped';
status.className = 'simulation-status bg-warning text-dark';
}
if (progress) progress.style.display = 'none';
break;
case 'error':
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
if (resetBtn) resetBtn.disabled = false;
if (status) {
status.textContent = 'Error';
status.className = 'simulation-status status-error';
}
if (progress) progress.style.display = 'none';
break;
default: // 'ready'
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
if (resetBtn) resetBtn.disabled = false;
if (status) {
status.textContent = 'Ready to start';
status.className = 'simulation-status status-ready';
}
if (progress) progress.style.display = 'none';
exportBtns.forEach(btn => {
if (btn) btn.disabled = true;
});
}
}
/**
* Update simulation progress
*/
function updateSimulationProgress(data) {
// Update progress bar
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
if (progressBar && progressText) {
const percentage = data.progress_percentage || 0;
progressBar.style.width = percentage + '%';
progressText.textContent = percentage.toFixed(1) + '%';
}
// Update charts and metrics
if (data.iteration !== undefined) {
currentSimulation.data.iterations.push(data.iteration);
currentSimulation.data.totalWealth.push(data.total_wealth || 0);
currentSimulation.data.giniCoefficients.push(data.gini_coefficient || 0);
currentSimulation.data.capitalShare.push(data.capital_share || 0);
currentSimulation.data.top10Share.push(data.wealth_concentration_top10 || 0);
updateCharts();
updateMetricsDisplay(data);
}
}
/**
* Update charts with new data
*/
function updateCharts() {
// Wealth Evolution Chart
if (charts.wealthEvolution) {
charts.wealthEvolution.data.labels = currentSimulation.data.iterations;
charts.wealthEvolution.data.datasets[0].data = currentSimulation.data.totalWealth;
charts.wealthEvolution.update('none');
}
// Inequality Chart
if (charts.inequality) {
charts.inequality.data.labels = currentSimulation.data.iterations;
charts.inequality.data.datasets[0].data = currentSimulation.data.giniCoefficients;
charts.inequality.data.datasets[1].data = currentSimulation.data.top10Share;
charts.inequality.update('none');
}
}
/**
* Update metrics display
*/
function updateMetricsDisplay(data) {
const formatters = window.MarkovEconomics.utils;
const totalWealthEl = document.getElementById('totalWealthMetric');
if (totalWealthEl) {
totalWealthEl.textContent = formatters.formatCurrency(data.total_wealth || 0);
}
const giniEl = document.getElementById('giniMetric');
if (giniEl) {
giniEl.textContent = (data.gini_coefficient || 0).toFixed(3);
}
const top10El = document.getElementById('top10Metric');
if (top10El) {
top10El.textContent = formatters.formatPercentage(data.wealth_concentration_top10 || 0);
}
const capitalShareEl = document.getElementById('capitalShareMetric');
if (capitalShareEl) {
capitalShareEl.textContent = formatters.formatPercentage(data.capital_share || 0);
}
}
/**
* Handle simulation completion
*/
function onSimulationComplete(data) {
currentSimulation.isRunning = false;
updateUIState('complete');
window.MarkovEconomics.utils.showNotification(
`Simulation completed! ${data.total_snapshots} data points collected.`,
'success'
);
}
/**
* Handle simulation stopped
*/
function onSimulationStopped(data) {
currentSimulation.isRunning = false;
updateUIState('stopped');
}
/**
* Handle simulation error
*/
function onSimulationError(data) {
currentSimulation.isRunning = false;
updateUIState('error');
window.MarkovEconomics.utils.showNotification(
`Simulation error: ${data.error}`,
'danger'
);
}
/**
* Export simulation data
*/
async function exportData(format) {
if (!currentSimulation.id) {
window.MarkovEconomics.utils.showNotification('No simulation data to export', 'warning');
return;
}
try {
const response = await fetch(`/api/simulation/${currentSimulation.id}/export/${format}`);
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
}
if (format === 'csv') {
const csvData = await response.text();
downloadFile(csvData, `simulation_${currentSimulation.id.slice(0, 8)}.csv`, 'text/csv');
} else {
const jsonData = await response.json();
downloadFile(
JSON.stringify(jsonData.data, null, 2),
`simulation_${currentSimulation.id.slice(0, 8)}.json`,
'application/json'
);
}
window.MarkovEconomics.utils.showNotification(`Data exported as ${format.toUpperCase()}`, 'success');
} catch (error) {
console.error('Export failed:', error);
window.MarkovEconomics.utils.showNotification(`Export failed: ${error.message}`, 'danger');
}
}
/**
* Download file helper
*/
function downloadFile(content, filename, contentType) {
const blob = new Blob([content], { type: contentType });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Initial inequality warning update
updateInequalityWarning();
});
// Export to global scope
window.initializeSimulation = initializeSimulation;

286
app/templates/about.html Normal file
View File

@@ -0,0 +1,286 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-10">
<!-- Header -->
<div class="text-center mb-5">
<h1 class="display-4">🎯 About Markov Economics</h1>
<p class="lead">Understanding how capitalism "eats the world" through Markov chain analysis</p>
</div>
<!-- Theoretical Foundation -->
<div class="card shadow mb-4">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">📚 Theoretical Foundation</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5>Marx's Economic Cycles</h5>
<p>Karl Marx identified two fundamental economic circulation patterns:</p>
<div class="alert alert-info">
<h6><strong>M-C-M' (Capitalist Circuit)</strong></h6>
<p class="mb-2">Money → Commodities → More Money</p>
<small>
Capital is invested in production to generate surplus value.
The goal is accumulation - turning M into M' where M' > M.
</small>
</div>
<div class="alert alert-success">
<h6><strong>C-M-C (Consumer Circuit)</strong></h6>
<p class="mb-2">Commodities → Money → Commodities</p>
<small>
Goods are sold to acquire money to purchase other goods.
The goal is consumption and use-value satisfaction.
</small>
</div>
</div>
<div class="col-md-6">
<h5>Piketty's Inequality Principle</h5>
<p>Thomas Piketty demonstrated that wealth inequality increases when:</p>
<div class="alert alert-warning">
<h6><strong>r > g</strong></h6>
<p class="mb-2">Capital Return Rate > Economic Growth Rate</p>
<small>
When capital generates returns faster than the overall economy grows,
wealth concentrates among capital owners, leading to increasing inequality.
</small>
</div>
<h6>Key Parameters:</h6>
<ul>
<li><strong>r:</strong> Rate of return on capital (stocks, real estate, business)</li>
<li><strong>g:</strong> Economic growth rate (GDP growth)</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Markov Chain Implementation -->
<div class="card shadow mb-4">
<div class="card-header bg-success text-white">
<h4 class="mb-0">⚙️ Markov Chain Implementation</h4>
</div>
<div class="card-body">
<p>This simulation models economic behavior as Markov chains with state-dependent transition probabilities:</p>
<div class="row">
<div class="col-md-6">
<h5>Capitalist Chain States</h5>
<div class="list-group">
<div class="list-group-item">
<strong>Money (M)</strong>
<br><small>Initial capital seeking investment opportunities</small>
</div>
<div class="list-group-item">
<strong>Commodities (C)</strong>
<br><small>Capital invested in production/goods</small>
</div>
<div class="list-group-item">
<strong>Enhanced Money (M')</strong>
<br><small>Capital with accumulated returns</small>
</div>
</div>
<div class="mt-3">
<h6>Transition Probability Matrix:</h6>
<pre class="bg-light p-2">
M → C: r (capital rate)
C → M': 1 (always)
M' → M: 1 (reinvestment)
</pre>
</div>
</div>
<div class="col-md-6">
<h5>Consumer Chain States</h5>
<div class="list-group">
<div class="list-group-item">
<strong>Commodities (C)</strong>
<br><small>Goods available for consumption</small>
</div>
<div class="list-group-item">
<strong>Money (M)</strong>
<br><small>Liquid currency from sales</small>
</div>
<div class="list-group-item">
<strong>New Commodities (C')</strong>
<br><small>Purchased goods for consumption</small>
</div>
</div>
<div class="mt-3">
<h6>Transition Probability Matrix:</h6>
<pre class="bg-light p-2">
C → M: g (growth rate)
M → C': 1 (always)
C' → C: 1 (consumption cycle)
</pre>
</div>
</div>
</div>
</div>
</div>
<!-- Simulation Mechanics -->
<div class="card shadow mb-4">
<div class="card-header bg-warning text-dark">
<h4 class="mb-0">🔬 Simulation Mechanics</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h5>How the Simulation Works</h5>
<ol>
<li><strong>Agent Initialization:</strong> Each economic agent starts with equal capital and consumption capacity</li>
<li><strong>Dual Chain Operation:</strong> Every agent runs both capitalist (M-C-M') and consumer (C-M-C) chains simultaneously</li>
<li><strong>Wealth Accumulation:</strong> Capitalist chains multiply wealth by (1 + r) on each complete cycle</li>
<li><strong>Consumption Growth:</strong> Consumer chains grow by (1 + g) on each complete cycle</li>
<li><strong>Inequality Emergence:</strong> When r > g, capital wealth grows faster than consumption, concentrating among fewer agents</li>
</ol>
</div>
<div class="col-md-4">
<h5>Key Metrics</h5>
<div class="list-group list-group-flush">
<div class="list-group-item px-0">
<strong>Gini Coefficient</strong>
<br><small>Measures inequality (0 = perfect equality, 1 = perfect inequality)</small>
</div>
<div class="list-group-item px-0">
<strong>Top 10% Share</strong>
<br><small>Percentage of total wealth held by richest 10%</small>
</div>
<div class="list-group-item px-0">
<strong>Capital Share</strong>
<br><small>Proportion of wealth from capital vs consumption</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Experimental Parameters -->
<div class="card shadow mb-4">
<div class="card-header bg-danger text-white">
<h4 class="mb-0">🧪 Experimental Parameters</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5>Try These Scenarios</h5>
<div class="alert alert-success">
<h6>Scenario 1: Stable Economy (r ≈ g)</h6>
<ul class="mb-0">
<li>Capital Rate: 3%</li>
<li>Growth Rate: 3%</li>
<li>Expected: Moderate inequality growth</li>
</ul>
</div>
<div class="alert alert-warning">
<h6>Scenario 2: Modern Capitalism (r > g)</h6>
<ul class="mb-0">
<li>Capital Rate: 5%</li>
<li>Growth Rate: 2%</li>
<li>Expected: Increasing inequality</li>
</ul>
</div>
<div class="alert alert-danger">
<h6>Scenario 3: Extreme Inequality (r >> g)</h6>
<ul class="mb-0">
<li>Capital Rate: 8%</li>
<li>Growth Rate: 1%</li>
<li>Expected: Rapid wealth concentration</li>
</ul>
</div>
</div>
<div class="col-md-6">
<h5>Historical Context</h5>
<p>Real-world examples of r vs g:</p>
<table class="table table-sm">
<thead>
<tr>
<th>Period</th>
<th>r (Capital)</th>
<th>g (Growth)</th>
<th>Inequality</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gilded Age (1870-1914)</td>
<td>4-5%</td>
<td>1-1.5%</td>
<td>Very High</td>
</tr>
<tr>
<td>Post-War Era (1950-1980)</td>
<td>2-3%</td>
<td>3-4%</td>
<td>Decreasing</td>
</tr>
<tr>
<td>Modern Era (1980-2020)</td>
<td>4-6%</td>
<td>1-2%</td>
<td>Increasing</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Technical Implementation -->
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white">
<h4 class="mb-0">⚡ Technical Implementation</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5>Technology Stack</h5>
<ul>
<li><strong>Backend:</strong> Python Flask with SocketIO</li>
<li><strong>Modeling:</strong> NumPy for matrix operations</li>
<li><strong>Visualization:</strong> Chart.js for real-time charts</li>
<li><strong>Frontend:</strong> Bootstrap 5 with responsive design</li>
<li><strong>Real-time:</strong> WebSocket connections for live updates</li>
</ul>
</div>
<div class="col-md-6">
<h5>Performance Characteristics</h5>
<ul>
<li><strong>Scalability:</strong> Up to 10,000 agents</li>
<li><strong>Speed:</strong> Real-time visualization of state transitions</li>
<li><strong>Accuracy:</strong> Precise Markov chain calculations</li>
<li><strong>Export:</strong> JSON and CSV data export</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Call to Action -->
<div class="text-center">
<a href="{{ url_for('main.index') }}" class="btn btn-primary btn-lg">
🚀 Start Your Simulation
</a>
</div>
</div>
</div>
{% endblock %}

90
app/templates/base.html Normal file
View File

@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ title }}{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Socket.IO -->
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block extra_head %}{% endblock %}
</head>
<body class="bg-light">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('main.index') }}">
<strong>📈 Markov Economics</strong>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">Simulation</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.about') }}">About</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<span class="navbar-text">
<small>When r > g, capitalism eats the world</small>
</span>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="container-fluid mt-4">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-8">
<h6>Markov Economics Simulation</h6>
<p class="mb-0">
<small>
Demonstrating Marx's M-C-M' model using Markov chains and Piketty's inequality principle (r > g).
Built with Flask, SocketIO, and Chart.js.
</small>
</p>
</div>
<div class="col-md-4 text-md-end">
<p class="mb-0">
<small>
<strong>M-C-M':</strong> Money → Commodities → More Money<br>
<strong>C-M-C:</strong> Commodities → Money → Commodities
</small>
</p>
</div>
</div>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JavaScript -->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
{% block extra_scripts %}{% endblock %}
</body>
</html>

35
app/templates/error.html Normal file
View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="text-center">
<div class="card shadow">
<div class="card-body p-5">
<div class="mb-4">
<i class="text-danger" style="font-size: 4rem;"></i>
</div>
<h2 class="card-title text-danger">Oops!</h2>
<p class="card-text lead">{{ error_message }}</p>
<div class="mt-4">
<a href="{{ url_for('main.index') }}" class="btn btn-primary btn-lg me-2">
🏠 Go Home
</a>
<button onclick="history.back()" class="btn btn-outline-secondary">
← Go Back
</button>
</div>
<div class="mt-4">
<small class="text-muted">
If this problem persists, the simulation may have encountered an unexpected error.
</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,295 @@
{% extends "base.html" %}
{% block extra_head %}
<style>
.parameter-card {
border-left: 4px solid #007bff;
}
.chart-container {
position: relative;
height: 400px;
margin-bottom: 20px;
}
.metrics-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.inequality-warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.progress-custom {
height: 25px;
}
.simulation-status {
font-size: 0.9rem;
padding: 8px 16px;
border-radius: 20px;
}
.btn-simulation {
min-width: 120px;
}
</style>
{% endblock %}
{% block content %}
<div class="row">
<!-- Parameter Controls Panel -->
<div class="col-lg-3 col-md-4">
<div class="card parameter-card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">📊 Simulation Parameters</h5>
</div>
<div class="card-body">
<!-- Capital Return Rate (r) -->
<div class="mb-4">
<label for="capitalRate" class="form-label">
<strong>Capital Rate (r)</strong>
<span class="badge bg-info ms-1" data-bs-toggle="tooltip"
title="Rate of return on capital investment">?</span>
</label>
<div class="input-group">
<input type="range" class="form-range" id="capitalRate"
min="0" max="25" step="0.1" value="{{ default_params.r_rate * 100 }}">
<span class="input-group-text" id="capitalRateValue">{{ "%.1f"|format(default_params.r_rate * 100) }}%</span>
</div>
<small class="text-muted">Higher values increase wealth accumulation</small>
</div>
<!-- Economic Growth Rate (g) -->
<div class="mb-4">
<label for="growthRate" class="form-label">
<strong>Growth Rate (g)</strong>
<span class="badge bg-info ms-1" data-bs-toggle="tooltip"
title="Overall economic growth rate">?</span>
</label>
<div class="input-group">
<input type="range" class="form-range" id="growthRate"
min="0" max="20" step="0.1" value="{{ default_params.g_rate * 100 }}">
<span class="input-group-text" id="growthRateValue">{{ "%.1f"|format(default_params.g_rate * 100) }}%</span>
</div>
<small class="text-muted">Affects consumption growth</small>
</div>
<!-- Number of Agents -->
<div class="mb-4">
<label for="numAgents" class="form-label">
<strong>Population Size</strong>
</label>
<div class="input-group">
<input type="range" class="form-range" id="numAgents"
min="10" max="1000" step="10" value="{{ default_params.num_agents }}">
<span class="input-group-text" id="numAgentsValue">{{ default_params.num_agents }}</span>
</div>
<small class="text-muted">Number of economic agents</small>
</div>
<!-- Simulation Length -->
<div class="mb-4">
<label for="iterations" class="form-label">
<strong>Time Steps</strong>
</label>
<div class="input-group">
<input type="range" class="form-range" id="iterations"
min="100" max="5000" step="100" value="{{ default_params.iterations }}">
<span class="input-group-text" id="iterationsValue">{{ default_params.iterations }}</span>
</div>
<small class="text-muted">Simulation duration</small>
</div>
<!-- Inequality Indicator -->
<div class="alert alert-warning" id="inequalityAlert" style="display: none;">
<strong>⚠️ r > g Detected!</strong><br>
<small>Wealth inequality will increase over time</small>
</div>
<!-- Control Buttons -->
<div class="d-grid gap-2">
<button type="button" class="btn btn-success btn-simulation" id="startBtn">
▶️ Start Simulation
</button>
<button type="button" class="btn btn-warning btn-simulation" id="stopBtn" disabled>
⏸️ Stop
</button>
<button type="button" class="btn btn-secondary btn-simulation" id="resetBtn">
🔄 Reset
</button>
</div>
<!-- Simulation Status -->
<div class="mt-3">
<div class="simulation-status bg-secondary text-white text-center" id="simulationStatus">
Ready to start
</div>
</div>
<!-- Progress Bar -->
<div class="mt-3" id="progressContainer" style="display: none;">
<div class="progress progress-custom">
<div class="progress-bar progress-bar-striped progress-bar-animated"
id="progressBar" role="progressbar" style="width: 0%">
<span id="progressText">0%</span>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Info Card -->
<div class="card mt-3 shadow-sm">
<div class="card-header bg-dark text-white">
<h6 class="mb-0">💡 Theory</h6>
</div>
<div class="card-body">
<small>
<strong>M-C-M':</strong> Capitalist cycle where money (M) is invested in commodities (C) to generate more money (M').<br><br>
<strong>C-M-C:</strong> Consumer cycle where commodities (C) are exchanged for money (M) to buy other commodities (C).<br><br>
<strong>When r > g:</strong> Capital returns exceed economic growth, leading to increasing wealth inequality.
</small>
</div>
</div>
</div>
<!-- Visualization Area -->
<div class="col-lg-9 col-md-8">
<!-- Key Metrics Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card metrics-card text-white shadow">
<div class="card-body text-center">
<h6 class="card-title">Total Wealth</h6>
<h4 id="totalWealthMetric">$0</h4>
<small>System-wide</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card metrics-card text-white shadow">
<div class="card-body text-center">
<h6 class="card-title">Gini Coefficient</h6>
<h4 id="giniMetric">0.00</h4>
<small>Inequality measure</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card inequality-warning text-white shadow">
<div class="card-body text-center">
<h6 class="card-title">Top 10% Share</h6>
<h4 id="top10Metric">0%</h4>
<small>Wealth concentration</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card inequality-warning text-white shadow">
<div class="card-body text-center">
<h6 class="card-title">Capital Share</h6>
<h4 id="capitalShareMetric">0%</h4>
<small>vs Consumption</small>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="row">
<!-- Wealth Evolution Chart -->
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header">
<h6 class="mb-0">📈 Wealth Evolution Over Time</h6>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="wealthEvolutionChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Inequality Metrics Chart -->
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header">
<h6 class="mb-0">📊 Inequality Metrics</h6>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="inequalityChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Wealth Distribution Chart -->
<div class="row mt-4">
<div class="col-12">
<div class="card shadow">
<div class="card-header">
<h6 class="mb-0">🏛️ Wealth Distribution Histogram</h6>
</div>
<div class="card-body">
<div class="chart-container">
<canvas id="distributionChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Export and Actions -->
<div class="row mt-4">
<div class="col-12">
<div class="card shadow">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6>📥 Export Simulation Data</h6>
<small class="text-muted">Download results for further analysis</small>
</div>
<div>
<button type="button" class="btn btn-outline-primary me-2" id="exportJsonBtn" disabled>
📄 JSON
</button>
<button type="button" class="btn btn-outline-success" id="exportCsvBtn" disabled>
📊 CSV
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Hidden fields for data storage -->
<input type="hidden" id="currentSimulationId">
<input type="hidden" id="defaultParams" value="{{ default_params | tojson }}">
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', filename='js/simulation.js') }}"></script>
<script>
// Initialize tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize simulation interface
document.addEventListener('DOMContentLoaded', function() {
initializeSimulation();
});
</script>
{% endblock %}

38
config.py Normal file
View File

@@ -0,0 +1,38 @@
import os
from datetime import timedelta
class Config:
"""Base configuration class"""
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-for-markov-economics')
MAX_SIMULATION_AGENTS = int(os.environ.get('MAX_AGENTS', 10000))
SIMULATION_TIMEOUT = int(os.environ.get('TIMEOUT', 300))
# Parameter constraints
MIN_CAPITAL_RATE = 0.0
MAX_CAPITAL_RATE = 0.25
MIN_GROWTH_RATE = 0.0
MAX_GROWTH_RATE = 0.20
MIN_AGENTS = 10
MAX_AGENTS = 10000
MIN_ITERATIONS = 100
MAX_ITERATIONS = 100000
class DevelopmentConfig(Config):
"""Development configuration"""
DEBUG = True
DEVELOPMENT = True
class ProductionConfig(Config):
"""Production configuration"""
DEBUG = False
DEVELOPMENT = False
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask>=2.0.0
Flask-SocketIO>=5.0.0
numpy>=1.21.0
matplotlib>=3.5.0
plotly>=5.0.0

14
run.py Normal file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""
Main entry point for the Markov Economics Flask application.
Demonstrates how capitalism "eats the world" using Markov chains.
"""
import os
from app import create_app, socketio
config_name = os.getenv('FLASK_CONFIG', 'development')
app = create_app(config_name)
if __name__ == '__main__':
socketio.run(app, debug=True, host='0.0.0.0', port=5000)

154
test_core.py Normal file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""
Test script for Markov Economics Application
Simple tests to verify the core functionality works correctly.
"""
import sys
import os
# Add the app directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
from models.markov_chain import CapitalistChain, ConsumerChain, EconomicAgent
from models.economic_model import SimulationParameters, EconomicSimulation
def test_markov_chains():
"""Test basic Markov chain functionality."""
print("🧪 Testing Markov Chains...")
# Test CapitalistChain
cap_chain = CapitalistChain(capital_rate=0.05) # 5% return
print(f" Initial capitalist wealth: {cap_chain.get_current_wealth()}")
# Simulate a few steps
for i in range(10):
state = cap_chain.step()
if i % 3 == 2: # Every complete cycle
print(f" After cycle {(i+1)//3}: wealth = {cap_chain.get_current_wealth():.3f}")
# Test ConsumerChain
cons_chain = ConsumerChain(growth_rate=0.03) # 3% growth
print(f" Initial consumer value: {cons_chain.get_current_consumption()}")
for i in range(10):
state = cons_chain.step()
if i % 3 == 2: # Every complete cycle
print(f" After cycle {(i+1)//3}: consumption = {cons_chain.get_current_consumption():.3f}")
print("✅ Markov chains working correctly")
def test_economic_agent():
"""Test economic agent functionality."""
print("\n🧪 Testing Economic Agent...")
agent = EconomicAgent(
agent_id="test_agent",
capital_rate=0.05,
growth_rate=0.03,
initial_capital=1000,
initial_consumption=1000
)
print(f" Initial total wealth: {agent.get_total_wealth()}")
print(f" Initial wealth ratio: {agent.get_wealth_ratio():.3f}")
# Simulate several steps
for i in range(30):
wealth, consumption = agent.step()
if i % 10 == 9:
print(f" Step {i+1}: wealth={wealth:.2f}, consumption={consumption:.2f}, total={agent.get_total_wealth():.2f}")
print("✅ Economic agent working correctly")
def test_simulation():
"""Test full simulation functionality."""
print("\n🧪 Testing Economic Simulation...")
params = SimulationParameters(
r_rate=0.05, # 5% capital return
g_rate=0.03, # 3% economic growth
num_agents=20,
iterations=50,
initial_capital=1000,
initial_consumption=1000
)
simulation = EconomicSimulation(params)
print(f" Created simulation with {len(simulation.agents)} agents")
# Run simulation
print(" Running simulation...")
snapshots = simulation.run_simulation()
print(f" Completed {len(snapshots)} iterations")
# Analyze results
if snapshots:
final_snapshot = snapshots[-1]
print(f" Final total wealth: ${final_snapshot.total_wealth:,.2f}")
print(f" Final Gini coefficient: {final_snapshot.gini_coefficient:.3f}")
print(f" Final top 10% share: {final_snapshot.wealth_concentration_top10:.1%}")
print(f" Final capital share: {final_snapshot.capital_share:.1%}")
# Check if r > g caused inequality
if params.r_rate > params.g_rate:
print(f" ⚠️ r ({params.r_rate:.1%}) > g ({params.g_rate:.1%}) - inequality should increase")
if final_snapshot.gini_coefficient > 0.1:
print(" ✅ Inequality increased as expected")
else:
print(" ⚠️ Expected more inequality")
print("✅ Simulation working correctly")
def test_inequality_principle():
"""Test Piketty's r > g inequality principle."""
print("\n🧪 Testing Piketty's r > g Principle...")
# Scenario 1: r > g (should increase inequality)
print(" Scenario 1: r > g")
params1 = SimulationParameters(r_rate=0.07, g_rate=0.02, num_agents=50, iterations=100)
sim1 = EconomicSimulation(params1)
snapshots1 = sim1.run_simulation()
if snapshots1:
initial_gini = snapshots1[0].gini_coefficient if snapshots1 else 0
final_gini = snapshots1[-1].gini_coefficient
print(f" Initial Gini: {initial_gini:.3f}, Final Gini: {final_gini:.3f}")
print(f" Inequality change: {final_gini - initial_gini:+.3f}")
# Scenario 2: r ≈ g (should have moderate inequality)
print(" Scenario 2: r ≈ g")
params2 = SimulationParameters(r_rate=0.03, g_rate=0.03, num_agents=50, iterations=100)
sim2 = EconomicSimulation(params2)
snapshots2 = sim2.run_simulation()
if snapshots2:
initial_gini = snapshots2[0].gini_coefficient if snapshots2 else 0
final_gini = snapshots2[-1].gini_coefficient
print(f" Initial Gini: {initial_gini:.3f}, Final Gini: {final_gini:.3f}")
print(f" Inequality change: {final_gini - initial_gini:+.3f}")
print("✅ Inequality principle demonstrated")
if __name__ == "__main__":
print("🚀 Markov Economics - Core Functionality Test")
print("=" * 50)
try:
test_markov_chains()
test_economic_agent()
test_simulation()
test_inequality_principle()
print("\n" + "=" * 50)
print("🎉 All tests passed! The application is working correctly.")
print("\nThe web interface is available at: http://127.0.0.1:5000")
print("The simulation demonstrates Marx's M-C-M' model and Piketty's r > g principle.")
except Exception as e:
print(f"\n❌ Test failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

6
test_simulation.json Normal file
View File

@@ -0,0 +1,6 @@
{
"r_rate": 0.05,
"g_rate": 0.03,
"num_agents": 50,
"iterations": 100
}