Spaces:
Runtime error
Runtime error
| """ | |
| NBA Dynamical Systems Web App | |
| Deploy to HuggingFace Spaces | |
| """ | |
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from scipy.integrate import odeint | |
| import json | |
| # Page config | |
| st.set_page_config( | |
| page_title="NBA Dynamical Systems", | |
| page_icon="🏀", | |
| layout="wide" | |
| ) | |
| # ============================================================ | |
| # DYNAMICAL SYSTEMS FUNCTIONS | |
| # ============================================================ | |
| def driven_pendulum(y, t, gamma, f0, omega): | |
| theta, omega_t = y | |
| dydt = [omega_t, -gamma*omega_t - np.sin(theta) + f0*np.cos(omega*t)] | |
| return dydt | |
| def team_harmonic(m, c, k, F, omega, t): | |
| omega_n = np.sqrt(max(k/m - c**2/(2*m**2), 0.01)) | |
| H = 1 / np.sqrt((k - m*omega**2)**2 + (c*omega)**2) | |
| phi = np.arctan2(c*omega, k - m*omega**2) | |
| return H * F/m * np.sin(omega*t - phi), omega_n | |
| def resonance_factor(m, c, k, omega): | |
| omega_n = np.sqrt(k/m) | |
| r = omega / omega_n | |
| zeta = c / (2*np.sqrt(k*m)) | |
| return 1 / np.sqrt((1 - r**2)**2 + (2*zeta*r)**2) | |
| def lorenz(state, t, sigma, rho, beta): | |
| x, y, z = state | |
| return [sigma*(y-x), x*(rho-z)-y, x*y-beta*z] | |
| def phase_reconstruct(data, dim, tau): | |
| n = len(data) - (dim-1)*tau | |
| if n < 2: | |
| return np.array([]).reshape(0, dim) | |
| return np.array([data[i:i+dim*tau:tau] for i in range(n)]) | |
| def lyapunov_estimate(data): | |
| divergences = [] | |
| for i in range(len(data)-1): | |
| for j in range(i+1, min(i+5, len(data))): | |
| d0 = abs(data[i] - data[j]) | |
| d1 = abs(data[i+1] - data[j+1]) | |
| if d0 > 0 and d1 > 0: | |
| divergences.append(np.log(d1/d0)) | |
| return np.mean(divergences) if divergences else 0 | |
| # ============================================================ | |
| # DATA | |
| # ============================================================ | |
| LUKA_DATA = { | |
| "player": {"name": "Luka Doncic", "team": "Dallas Mavericks"}, | |
| "season_averages": {"points": 28.8, "rebounds": 8.6, "assists": 8.2, "plus_minus": 8.5}, | |
| "game_logs": [ | |
| {"date": "2025-01-20", "opponent": "BOS", "result": "W", "pts": 34, "reb": 10, "ast": 8, "plus_minus": 12}, | |
| {"date": "2025-01-18", "opponent": "DEN", "result": "W", "pts": 38, "reb": 11, "ast": 7, "plus_minus": 8}, | |
| {"date": "2025-01-15", "opponent": "LAL", "result": "L", "pts": 25, "reb": 6, "ast": 12, "plus_minus": -8}, | |
| {"date": "2025-01-12", "opponent": "GSW", "result": "W", "pts": 32, "reb": 9, "ast": 6, "plus_minus": 18}, | |
| {"date": "2025-01-10", "opponent": "PHX", "result": "W", "pts": 29, "reb": 7, "ast": 10, "plus_minus": 10}, | |
| {"date": "2025-01-08", "opponent": "LAC", "result": "W", "pts": 42, "reb": 8, "ast": 9, "plus_minus": 15}, | |
| {"date": "2025-01-05", "opponent": "MEM", "result": "W", "pts": 26, "reb": 5, "ast": 11, "plus_minus": 4}, | |
| {"date": "2025-01-03", "opponent": "HOU", "result": "W", "pts": 31, "reb": 10, "ast": 7, "plus_minus": 6}, | |
| {"date": "2024-12-30", "opponent": "SAS", "result": "W", "pts": 22, "reb": 7, "ast": 13, "plus_minus": 5}, | |
| {"date": "2024-12-28", "opponent": "NYK", "result": "W", "pts": 36, "reb": 9, "ast": 8, "plus_minus": 14}, | |
| ] | |
| } | |
| # ============================================================ | |
| # UI | |
| # ============================================================ | |
| def main(): | |
| st.title("🏀 NBA Dynamical Systems") | |
| st.markdown("### Dallas Mavericks vs LA Lakers") | |
| st.markdown("*Chaos theory applied to basketball*") | |
| data = LUKA_DATA | |
| games = pd.DataFrame(data['game_logs']) | |
| stats = data['season_averages'] | |
| # Header metrics | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.metric("PPG", f"{stats['points']:.1f}") | |
| with col2: | |
| st.metric("RPG", f"{stats['rebounds']:.1f}") | |
| with col3: | |
| st.metric("APG", f"{stats['assists']:.1f}") | |
| with col4: | |
| st.metric("+/-", f"+{stats['plus_minus']:.1f}") | |
| # Tabs | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs(["📈 Timeline", "🔄 Sensitivity", "🌊 Harmonics", "🌀 Phase Space", "🎯 Lorenz"]) | |
| with tab1: | |
| st.subheader("Scoring Timeline") | |
| fig, ax = plt.subplots(figsize=(10, 4)) | |
| ax.plot(range(len(games)), games['pts'], 'b-o', linewidth=2, markersize=8) | |
| ax.axhline(y=games['pts'].mean(), color='r', linestyle='--', label=f"Avg: {games['pts'].mean():.1f}") | |
| ax.set_xlabel("Game") | |
| ax.set_ylabel("Points") | |
| ax.set_title("Luka Doncic - Points per Game") | |
| ax.legend() | |
| ax.grid(True, alpha=0.3) | |
| st.pyplot(fig) | |
| st.dataframe(games[['date', 'opponent', 'result', 'pts', 'reb', 'ast', 'plus_minus']], hide_index=True) | |
| with tab2: | |
| st.subheader("Sensitivity (Driven Pendulum)") | |
| st.latex(r"\theta'' + \gamma\theta' + \sin(\theta) = f_0\cos(\omega t)") | |
| variability = np.std(games['pts']) / np.mean(games['pts']) | |
| damping = 0.5 + (1 - variability) * 2 | |
| forcing = 0.5 + games['pts'].mean() / 30 | |
| col1, col2, col3 = st.columns(3) | |
| with col1: st.metric("Damping (γ)", f"{damping:.2f}") | |
| with col2: st.metric("Forcing (f₀)", f"{forcing:.2f}") | |
| with col3: st.metric("Variability", f"{variability:.2f}") | |
| t = np.linspace(0, 50, 500) | |
| sol = odeint(driven_pendulum, [0.1, 0], t, args=(damping, forcing, 0.5)) | |
| fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) | |
| ax1.plot(t, sol[:, 0], 'b-', label='θ₀=0.10', linewidth=2) | |
| ax1.plot(t, odeint(driven_pendulum, [0.11, 0], t, args=(damping, forcing, 0.5))[:, 0], 'r--', label='θ₀=0.11') | |
| ax1.legend(); ax1.grid(True, alpha=0.3) | |
| div = np.abs(odeint(driven_pendulum, [0.11, 0], t, args=(damping, forcing, 0.5))[:, 0] - sol[:, 0]) | |
| ax2.plot(t, div, 'r-', linewidth=2); ax2.grid(True, alpha=0.3) | |
| st.pyplot(fig) | |
| st.info(f"**Regime:** {'Stable' if np.mean(div) < 2 else 'Chaotic'}") | |
| with tab3: | |
| st.subheader("Team Harmonics (Mass-Spring-Damper)") | |
| st.latex(r"mx'' + cx' + kx = F\sin(\omega t)") | |
| mavs = {'chemistry': 0.72, 'resilience': 0.65, 'roster': 0.88} | |
| lakes = {'chemistry': 0.58, 'resilience': 0.55, 'roster': 0.82} | |
| omega = 0.5 | |
| mavs_rf = resonance_factor(mavs['roster'], mavs['resilience']*2, mavs['chemistry']*5, omega) | |
| lakes_rf = resonance_factor(lakes['roster'], lakes['resilience']*2, lakes['chemistry']*5, omega) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.metric("Mavericks RF", f"{mavs_rf:.2f}") | |
| st.metric("Mavericks Upset Risk", f"{max(0, mavs_rf-1):.2f}") | |
| with col2: | |
| st.metric("Lakers RF", f"{lakes_rf:.2f}") | |
| st.metric("Lakers Upset Risk", f"{max(0, lakes_rf-1):.2f}") | |
| t = np.linspace(0, 30, 300) | |
| team = st.selectbox("Select Team", ["Mavericks", "Lakers"]) | |
| team_data = mavs if team == "Mavericks" else lakes | |
| response, omega_n = team_harmonic(team_data['roster'], team_data['resilience']*2, team_data['chemistry']*5, 0.5, omega, t) | |
| fig, ax = plt.subplots(figsize=(10, 4)) | |
| ax.plot(t, response, 'b-', linewidth=2) | |
| ax.set_xlabel('Time'); ax.set_ylabel('Displacement') | |
| ax.set_title(f'{team} Harmonic Response'); ax.grid(True, alpha=0.3) | |
| st.pyplot(fig) | |
| with tab4: | |
| st.subheader("Phase Space Reconstruction") | |
| points = games['pts'].values | |
| embedded = phase_reconstruct(points, 3, 1) | |
| fig, ax = plt.subplots(figsize=(8, 6)) | |
| ax.plot(embedded[:, 0], embedded[:, 1], 'b-o', markersize=6, alpha=0.7) | |
| ax.set_xlabel('x(t)'); ax.set_ylabel('x(t+τ)') | |
| ax.set_title('2D Phase Space'); ax.grid(True, alpha=0.3) | |
| st.pyplot(fig) | |
| st.info("Reconstructed attractor shows scoring dynamics") | |
| with tab5: | |
| st.subheader("Lorenz System Forecasting") | |
| st.latex(r"\begin{aligned} dx/dt &= \sigma(y-x) \\ dy/dt &= x(\rho-z)-y \end{aligned}") | |
| variability = np.std(games['pts']) / np.mean(games['pts']) | |
| sigma, rho, beta = 10, 14 + stats['plus_minus']/5 + 2*0.5, 8/3 + variability*2 | |
| col1, col2, col3 = st.columns(3) | |
| with col1: st.metric("σ", f"{sigma}") | |
| with col2: st.metric("ρ", f"{rho:.1f}") | |
| with col3: st.metric("β", f"{beta:.1f}") | |
| t = np.linspace(0, 50, 2000) | |
| sol = odeint(lorenz, [1, 1, 1], t, args=(sigma, rho, beta)) | |
| fig = plt.figure(figsize=(10, 6)) | |
| ax = fig.add_subplot(111, projection='3d') | |
| ax.plot(sol[:, 0], sol[:, 1], sol[:, 2], 'b-', linewidth=0.5) | |
| ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('z') | |
| ax.set_title('Lorenz Attractor') | |
| st.pyplot(fig) | |
| lyap = lyapunov_estimate(points) | |
| st.metric("Lyapunov Exponent", f"{lyap:.4f}") | |
| st.success("**Stable**" if lyap < 0 else "**Chaotic**") | |
| # Risk Summary | |
| st.divider() | |
| st.subheader("📊 Combined Risk") | |
| t = np.linspace(0, 50, 500) | |
| sensitivity_risk = np.mean(np.abs(odeint(driven_pendulum, [0.11, 0], t, args=(damping, forcing, 0.5))[:, 0] - | |
| odeint(driven_pendulum, [0.1, 0], t, args=(damping, forcing, 0.5))[:, 0])) / 10 * 0.25 | |
| mav_risk = max(0, mavs_rf-1) * 0.25 | |
| laker_risk = max(0, lakes_rf-1) * 0.25 | |
| chaos_risk = max(0, lyap) * 0.25 | |
| total = sensitivity_risk + mav_risk + laker_risk + chaos_risk | |
| fig, ax = plt.subplots(figsize=(8, 3)) | |
| ax.barh(['Luka', 'Mavericks', 'Lakers', 'Chaos'], [sensitivity_risk, mav_risk, laker_risk, chaos_risk]) | |
| ax.axvline(x=total, color='black', linestyle='--', label=f'Total: {total:.2f}') | |
| ax.legend() | |
| st.pyplot(fig) | |
| if total < 0.25: | |
| st.success(f"**RISK: {total:.2f} - LOW - Favor Mavericks**") | |
| elif total < 0.40: | |
| st.warning(f"**RISK: {total:.2f} - MODERATE**") | |
| else: | |
| st.error(f"**RISK: {total:.2f} - HIGH**") | |
| if __name__ == "__main__": | |
| main() | |