softwarenerdco's picture
create app.py
63ebe15 verified
"""
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()