[
  {
    "title": "Solve a quadratic symbolically with SymPy",
    "language": "python",
    "subject": "math",
    "code": "import sympy as sp\n\nx, a, b, c = sp.symbols(\"x a b c\")\nroots = sp.solve(sp.Eq(a * x**2 + b * x + c, 0), x)\nfor r in roots:\n    print(sp.simplify(r))  # the two roots in terms of a, b, c\n\n# numeric case: x^2 - 5x + 6 = 0\nprint(sp.solve(sp.Eq(x**2 - 5 * x + 6, 0), x))  # [2, 3]",
    "tags": [
      "sympy",
      "algebra",
      "roots"
    ],
    "description": "Uses SymPy to solve a quadratic equation exactly, first with symbolic coefficients and then for a numeric example."
  },
  {
    "title": "Differentiate and integrate with SymPy",
    "language": "python",
    "subject": "math",
    "code": "import sympy as sp\n\nx = sp.symbols(\"x\")\nf = sp.sin(x) * sp.exp(x)\n\nprint(sp.diff(f, x))                   # exp(x)*sin(x) + exp(x)*cos(x)\nprint(sp.integrate(f, x))              # antiderivative\nprint(sp.integrate(f, (x, 0, sp.pi)))  # definite integral over [0, pi]",
    "tags": [
      "sympy",
      "calculus",
      "derivative",
      "integral"
    ],
    "description": "One-liner SymPy calls for the derivative, the indefinite integral and a definite integral of the same function."
  },
  {
    "title": "Solve a linear system with NumPy",
    "language": "python",
    "subject": "math",
    "code": "import numpy as np\n\nA = np.array([[2.0, 1.0, -1.0],\n              [-3.0, -1.0, 2.0],\n              [-2.0, 1.0, 2.0]])\nb = np.array([8.0, -11.0, -3.0])\n\nx = np.linalg.solve(A, b)\nprint(x)                       # [ 2.  3. -1.]\nprint(np.allclose(A @ x, b))   # True",
    "tags": [
      "numpy",
      "linear algebra",
      "matrix"
    ],
    "description": "Solves a three-by-three system of linear equations with numpy.linalg.solve and checks the answer by substitution."
  },
  {
    "title": "Projectile trajectory without air resistance",
    "language": "python",
    "subject": "physics",
    "code": "import numpy as np\n\ng, v0, angle_deg = 9.81, 25.0, 40.0     # m/s^2, m/s, degrees\ntheta = np.radians(angle_deg)\n\nt_flight = 2 * v0 * np.sin(theta) / g   # launch and landing at the same height\nt = np.linspace(0.0, t_flight, 200)\nx = v0 * np.cos(theta) * t\ny = v0 * np.sin(theta) * t - 0.5 * g * t**2\n\nprint(f\"flight time = {t_flight:.2f} s\")\nprint(f\"range = {x[-1]:.2f} m, maximum height = {y.max():.2f} m\")",
    "tags": [
      "kinematics",
      "projectile",
      "numpy"
    ],
    "description": "Samples the parabolic path of a projectile launched over level ground and reports flight time, range and apex height."
  },
  {
    "title": "RC discharge curve",
    "language": "python",
    "subject": "physics",
    "code": "import numpy as np\n\nV0, R, C = 5.0, 10e3, 100e-6   # volts, ohms, farads\ntau = R * C                    # time constant in seconds\n\nt = np.linspace(0.0, 5 * tau, 200)\nv = V0 * np.exp(-t / tau)\n\nprint(f\"tau = {tau:.3f} s\")\nprint(f\"V(tau) = {V0 * np.exp(-1):.3f} V  (about 37 percent of V0)\")\nprint(f\"V(5 tau) = {v[-1]:.4f} V\")",
    "tags": [
      "circuits",
      "capacitor",
      "exponential decay"
    ],
    "description": "Computes the exponentially decaying voltage across a capacitor discharging through a resistor and prints the time constant."
  },
  {
    "title": "Work done by a variable force with scipy.quad",
    "language": "python",
    "subject": "physics",
    "code": "from scipy.integrate import quad\n\nk = 120.0                     # spring constant in N/m\nforce = lambda x: k * x       # Hooke's law force in newtons\n\nwork, err = quad(force, 0.0, 0.20)   # stretch the spring from 0 to 0.20 m\nprint(f\"work = {work:.3f} J (estimated error {err:.1e})\")\nprint(f\"closed form = {0.5 * k * 0.20**2:.3f} J\")",
    "tags": [
      "scipy",
      "numeric integration",
      "work"
    ],
    "description": "Integrates a position-dependent force numerically with scipy.integrate.quad and checks it against the closed-form spring energy."
  },
  {
    "title": "Molar mass calculator",
    "language": "python",
    "subject": "chemistry",
    "code": "import re\n\nATOMIC_MASS = {\"H\": 1.008, \"C\": 12.011, \"N\": 14.007, \"O\": 15.999,\n               \"Na\": 22.990, \"S\": 32.06, \"Cl\": 35.45}\n\ndef molar_mass(formula):\n    \"\"\"Molar mass in g/mol for a flat formula such as C6H12O6 or Na2SO4.\"\"\"\n    total = 0.0\n    for element, count in re.findall(r\"([A-Z][a-z]?)(\\d*)\", formula):\n        if element:\n            total += ATOMIC_MASS[element] * int(count or 1)\n    return total\n\nprint(f\"{molar_mass('C6H12O6'):.3f} g/mol\")   # glucose, 180.156\nprint(f\"{molar_mass('Na2SO4'):.3f} g/mol\")    # sodium sulfate, 142.04",
    "tags": [
      "stoichiometry",
      "molar mass",
      "parsing"
    ],
    "description": "Parses a simple chemical formula and sums the atomic masses to give the molar mass in grams per mole."
  },
  {
    "title": "Weak-acid equilibrium solver",
    "language": "python",
    "subject": "chemistry",
    "code": "from math import log10\nfrom scipy.optimize import brentq\n\nKa, C0 = 1.8e-5, 0.10   # acetic acid: Ka, initial concentration in mol/L\n\n# Let x = [H+] at equilibrium, so Ka = x^2 / (C0 - x)\nx = brentq(lambda x: x**2 - Ka * (C0 - x), 1e-12, C0)\n\nprint(f\"[H+] = {x:.3e} mol/L\")\nprint(f\"pH = {-log10(x):.2f}\")        # 2.88 (exact); the small-x approximation gives 2.87\nprint(f\"degree of ionisation = {100 * x / C0:.2f} percent\")",
    "tags": [
      "equilibrium",
      "acids",
      "scipy"
    ],
    "description": "Solves the exact equilibrium expression for a weak monoprotic acid with a root finder instead of the usual small-x approximation."
  },
  {
    "title": "Beer-Lambert calibration fit",
    "language": "python",
    "subject": "chemistry",
    "code": "import numpy as np\n\nconc = np.array([0.00, 0.20, 0.40, 0.60, 0.80])        # standards in mmol/L\nabsorbance = np.array([0.002, 0.101, 0.198, 0.302, 0.399])\n\nslope, intercept = np.polyfit(conc, absorbance, 1)     # A = (eps * l) * c + b\nunknown_a = 0.250\nunknown_c = (unknown_a - intercept) / slope\n\nprint(f\"eps * l = {slope:.4f} L/mmol, intercept = {intercept:.4f}\")\nprint(f\"unknown concentration = {unknown_c:.3f} mmol/L\")",
    "tags": [
      "spectroscopy",
      "calibration",
      "numpy"
    ],
    "description": "Fits a straight calibration line to absorbance measurements and reads an unknown concentration off it using the Beer-Lambert law."
  },
  {
    "title": "Monte Carlo estimate of pi",
    "language": "python",
    "subject": "statistics",
    "code": "import numpy as np\n\nrng = np.random.default_rng(42)\nn = 1_000_000\n\nx, y = rng.random(n), rng.random(n)\ninside = (x**2 + y**2) <= 1.0          # quarter disc inside the unit square\n\nestimate = 4 * inside.mean()\nprint(f\"pi estimate = {estimate:.5f} from {n} samples\")",
    "tags": [
      "monte carlo",
      "simulation",
      "numpy"
    ],
    "description": "Estimates pi by sampling points in the unit square and taking four times the fraction that fall inside the quarter disc."
  },
  {
    "title": "Bootstrap confidence interval for a mean",
    "language": "python",
    "subject": "statistics",
    "code": "import numpy as np\n\nrng = np.random.default_rng(0)\ndata = rng.normal(loc=10.0, scale=2.0, size=50)\n\nmeans = np.array([rng.choice(data, size=data.size, replace=True).mean()\n                  for _ in range(10_000)])\nlo, hi = np.percentile(means, [2.5, 97.5])\n\nprint(f\"sample mean = {data.mean():.3f}\")\nprint(f\"95 percent bootstrap interval = [{lo:.3f}, {hi:.3f}]\")",
    "tags": [
      "bootstrap",
      "confidence interval",
      "resampling"
    ],
    "description": "Resamples the data with replacement to build the sampling distribution of the mean and reads a percentile confidence interval off it."
  },
  {
    "title": "Ordinary least squares regression with statsmodels",
    "language": "python",
    "subject": "statistics",
    "code": "import numpy as np\nimport statsmodels.api as sm\n\nrng = np.random.default_rng(7)\nx = np.linspace(0.0, 10.0, 100)\ny = 2.5 * x + 1.0 + rng.normal(scale=1.5, size=x.size)\n\nmodel = sm.OLS(y, sm.add_constant(x)).fit()\nprint(model.params)      # [intercept, slope], close to [1.0, 2.5]\nprint(f\"R-squared = {model.rsquared:.3f}\")\nprint(model.summary())",
    "tags": [
      "regression",
      "statsmodels",
      "least squares"
    ],
    "description": "Fits a simple linear regression with statsmodels and prints the coefficient estimates, R-squared and the full inference summary."
  }
]
