""" 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()