{ "cells": [ { "cell_type": "markdown", "id": "6440d53f", "metadata": {}, "source": [ "## Computing Remnant Properties from SXS Numerical Relativity Simulations\n", "\n", "This notebook loads quasi-circular SXS simulations, computes remnant properties with `gw_remnant`, and compares against the remnant properties in the SXS simulation metadata.\n", "\n", "Two approaches are demonstrated:\n", "- **Section 2**: Supply `E_initial` and `L_initial` from the NR metadata (exact initial conditions).\n", "- **Section 3**: Let `gw_remnant` compute `E_initial` and `L_initial` from its built-in PN expressions.\n", "\n", "Requires `pip install gw_remnant[nr]`.\n", "\n", "Contact: Tousif Islam [tousifislam24@gmail.com]" ] }, { "cell_type": "markdown", "id": "033c7e87", "metadata": {}, "source": [ "### 1. Setup" ] }, { "cell_type": "code", "execution_count": 1, "id": "3e9e7de0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "lal.MSUN_SI != Msun\n" ] } ], "source": [ "import warnings\n", "warnings.filterwarnings(\"ignore\", \"Wswiglal-redir-stdio\")\n", "\n", "import numpy as np\n", "import sxs\n", "from gw_remnant.gw_remnant_calculator import GWRemnantCalculator" ] }, { "cell_type": "code", "execution_count": 2, "id": "a5ba61c0", "metadata": {}, "outputs": [], "source": [ "LMAX = 8\n", "\n", "def load_sxs_simulation(name):\n", " \"\"\"Load an SXS simulation and return (time, h_dict, metadata_info).\"\"\"\n", " sim = sxs.load(name)\n", " md = sim.metadata\n", " h = sim.h\n", "\n", " # discard junk radiation before reference time\n", " reference_time = md.get(\"reference_time\", h.t[0])\n", " reference_index = h.index_closest_to(reference_time)\n", " h = h[reference_index:]\n", "\n", " t = np.array(h.t, float)\n", "\n", " h_dict = {}\n", " for l in range(2, LMAX + 1):\n", " for m in range(-l, l + 1):\n", " h_dict[(l, m)] = np.array(h.data[:, h.index(l, m)], complex)\n", "\n", " m1 = md.get(\"reference_mass1\") or md.get(\"initial_mass1\")\n", " m2 = md.get(\"reference_mass2\") or md.get(\"initial_mass2\")\n", " q = m1 / m2 if (m1 and m2) else md.get(\"reference_mass_ratio\")\n", " if q is not None and q < 1:\n", " q = 1.0 / q\n", "\n", " info = dict(\n", " q=float(q),\n", " M_ADM=float(md[\"initial_ADM_energy\"]),\n", " J_ADM=np.array(md[\"initial_ADM_angular_momentum\"], float),\n", " chi1=np.array(md[\"initial_dimensionless_spin1\"], float),\n", " chi2=np.array(md[\"initial_dimensionless_spin2\"], float),\n", " rem_mass=float(md[\"remnant_mass\"]),\n", " rem_chi=np.array(md[\"remnant_dimensionless_spin\"], float),\n", " kick=np.array(md.get(\"remnant_velocity\", [0, 0, 0]), float),\n", " )\n", " return t, h_dict, info\n", "\n", "\n", "def nr_initial_conditions(info):\n", " \"\"\"Compute E_initial and L_initial from NR metadata.\"\"\"\n", " q = info[\"q\"]\n", " X1, X2 = q / (1 + q), 1 / (1 + q)\n", " S1z = info[\"chi1\"][2] * X1**2\n", " S2z = info[\"chi2\"][2] * X2**2\n", " E_initial = 1.0 - info[\"M_ADM\"]\n", " L_initial = info[\"J_ADM\"][2] - S1z - S2z\n", " return E_initial, L_initial\n", "\n", "\n", "def compare_with_nr(calc, info):\n", " \"\"\"Print a side-by-side comparison of gw_remnant vs SXS metadata.\"\"\"\n", " nr_chi = info[\"rem_chi\"]\n", " nr_kick = np.linalg.norm(info[\"kick\"])\n", "\n", " print(f\"{'Property':<25} {'gw_remnant':>15} {'SXS metadata':>15}\")\n", " print(\"-\" * 57)\n", " print(f\"{'Remnant mass [M]':<25} {calc.remnant_mass:>15.6f} {info['rem_mass']:>15.6f}\")\n", " print(f\"{'Remnant |chi|':<25} {np.linalg.norm(calc.remnant_spin_vector):>15.6f} {np.linalg.norm(nr_chi):>15.6f}\")\n", " print(f\"{'Remnant spin (z)':<25} {calc.remnant_spin:>15.6f} {nr_chi[2]:>15.6f}\")\n", " print(f\"{'Kick velocity [c]':<25} {calc.remnant_kick:>15.6f} {nr_kick:>15.6f}\")\n", " print(f\"{'Spin vector':<25} {np.array2string(calc.remnant_spin_vector, precision=4)}\")\n", " print(f\"{'':25} {np.array2string(nr_chi, precision=4)} (SXS)\")" ] }, { "cell_type": "code", "execution_count": 3, "id": "7af251e9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading SXS:BBH:2265 (non-spinning)...\n", "Loading SXS simulations using latest tag 'v3.0.0', published at 2025-05-14T18:17:30Z.\n", " q = 3.000, chi1 = [ 0. -0. -0.], chi2 = [ 0. -0. -0.]\n", " Time range: [1077.2, 30739.8] M\n", "\n", "Loading SXS:BBH:2014 (non-precessing)...\n", " q = 4.000, chi1 = [ 0. -0. 0.8], chi2 = [-0. 0. 0.4]\n", " Time range: [820.8, 5231.0] M\n", "\n", "Loading SXS:BBH:2000 (precessing)...\n", " q = 3.999, chi1 = [-0.163 0.782 0.05 ], chi2 = [ 0.782 -0.107 -0.132]\n", " Time range: [1218.8, 5097.4] M\n", "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Loading SXS simulations using latest tag 'v3.0.0', published at 2025-05-14T18:17:30Z.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " q = 3.000, chi1 = [ 0. -0. -0.], chi2 = [ 0. -0. -0.]\n", " Time range: [1077.2, 30739.8] M\n", "\n", "Loading SXS:BBH:2014 (non-precessing)...\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " q = 4.000, chi1 = [ 0. -0. 0.8], chi2 = [-0. 0. 0.4]\n", " Time range: [820.8, 5231.0] M\n", "\n", "Loading SXS:BBH:2000 (precessing)...\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " q = 3.999, chi1 = [-0.163 0.782 0.05 ], chi2 = [ 0.782 -0.107 -0.132]\n", " Time range: [1218.8, 5097.4] M\n", "\n" ] } ], "source": [ "sims = {\n", " \"non-spinning\": \"SXS:BBH:2265\",\n", " \"non-precessing\": \"SXS:BBH:2014\",\n", " \"precessing\": \"SXS:BBH:2000\",\n", "}\n", "\n", "data = {}\n", "for label, name in sims.items():\n", " print(f\"Loading {name} ({label})...\")\n", " t, h_dict, info = load_sxs_simulation(name)\n", " data[label] = (t, h_dict, info)\n", " print(f\" q = {info['q']:.3f}, chi1 = {np.round(info['chi1'], 3)}, chi2 = {np.round(info['chi2'], 3)}\")\n", " print(f\" Time range: [{t[0]:.1f}, {t[-1]:.1f}] M\")\n", " print()" ] }, { "cell_type": "markdown", "id": "e456773f", "metadata": {}, "source": [ "---\n", "### 2. Using E_initial and L_initial from NR metadata\n", "\n", "Here we supply the initial binding energy and orbital angular momentum directly from the SXS metadata:\n", "- `E_initial = 1 - M_ADM`\n", "- `L_initial = J_ADM_z - S1_z - S2_z`\n", "\n", "This gives the most accurate remnant mass since it uses the exact ADM energy." ] }, { "cell_type": "markdown", "id": "0be5ff41", "metadata": {}, "source": [ "#### 2a. Non-spinning quasi-circular" ] }, { "cell_type": "code", "execution_count": 4, "id": "7ce2404b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2265: q = 3.000, E_initial = 0.003840, L_initial = 0.972388\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 3.000\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.02482047 M\n", "Peak luminosity : 0.00052717\n", "Remnant mass : 0.97133946 M\n", "Remnant spin (dimensionless) : 0.54414793\n", "Remnant spin vector (x,y,z) : (0.00000004, 0.00000016, 0.54414793)\n", "Remnant kick velocity : 0.00056293 c\n", "Remnant kick velocity : 168.76 km/s\n", "Remnant kick vector (x,y,z) : (0.00031969, -0.00046335, 0.00000006) c\n", "Remnant displacement (x,y,z) : (0.04215534, -0.08285644, 0.00002058) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.971339 0.971102\n", "Remnant |chi| 0.544148 0.540609\n", "Remnant spin (z) 0.544148 0.540609\n", "Kick velocity [c] 0.000563 0.000582\n", "Spin vector [3.8320e-08 1.5622e-07 5.4415e-01]\n", " [ 5.3327e-08 -7.7351e-08 5.4061e-01] (SXS)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/953974870.py:5: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] } ], "source": [ "t, h_dict, info = data[\"non-spinning\"]\n", "E_init, L_init = nr_initial_conditions(info)\n", "print(f\"{sims['non-spinning']}: q = {info['q']:.3f}, E_initial = {E_init:.6f}, L_initial = {L_init:.6f}\")\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]),\n", " E_initial=E_init, L_initial=L_init)\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "markdown", "id": "8caf7aa6", "metadata": {}, "source": [ "#### 2b. Non-precessing quasi-circular" ] }, { "cell_type": "code", "execution_count": 5, "id": "757aaab4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2014: q = 4.000, E_initial = 0.005468, L_initial = 0.646792\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 4.000\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.03939614 M\n", "Peak luminosity : 0.00073047\n", "Remnant mass : 0.95513612 M\n", "Remnant spin (dimensionless) : 0.89469251\n", "Remnant spin vector (x,y,z) : (-0.00000004, -0.00000033, 0.89469251)\n", "Remnant kick velocity : 0.00006424 c\n", "Remnant kick velocity : 19.26 km/s\n", "Remnant kick vector (x,y,z) : (0.00003287, -0.00005519, 0.00000004) c\n", "Remnant displacement (x,y,z) : (-0.00345658, -0.01176818, 0.00000834) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.955136 0.954579\n", "Remnant |chi| 0.894693 0.881767\n", "Remnant spin (z) 0.894693 0.881767\n", "Kick velocity [c] 0.000064 0.000040\n", "Spin vector [-3.9407e-08 -3.2589e-07 8.9469e-01]\n", " [1.6820e-07 6.9549e-07 8.8177e-01] (SXS)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/1814872700.py:5: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] } ], "source": [ "t, h_dict, info = data[\"non-precessing\"]\n", "E_init, L_init = nr_initial_conditions(info)\n", "print(f\"{sims['non-precessing']}: q = {info['q']:.3f}, E_initial = {E_init:.6f}, L_initial = {L_init:.6f}\")\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]),\n", " E_initial=E_init, L_initial=L_init)\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "markdown", "id": "82c447d8", "metadata": {}, "source": [ "#### 2c. Precessing quasi-circular" ] }, { "cell_type": "code", "execution_count": 6, "id": "b5308565", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2000: q = 3.999, E_initial = 0.004981, L_initial = 0.687246\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 3.999\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.01918366 M\n", "Peak luminosity : 0.00041586\n", "Remnant mass : 0.97583504 M\n", "Remnant spin (dimensionless) : 0.57633725\n", "Remnant spin vector (x,y,z) : (-0.08356523, 0.42361271, 0.57633725)\n", "Remnant kick velocity : 0.00090260 c\n", "Remnant kick velocity : 270.59 km/s\n", "Remnant kick vector (x,y,z) : (-0.00013259, 0.00010672, -0.00088641) c\n", "Remnant displacement (x,y,z) : (-0.02164086, -0.00085242, -0.12443661) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.975835 0.975265\n", "Remnant |chi| 0.720136 0.691529\n", "Remnant spin (z) 0.576337 0.559751\n", "Kick velocity [c] 0.000903 0.000970\n", "Spin vector [-0.0836 0.4236 0.5763]\n", " [-0.0868 0.3967 0.5598] (SXS)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/4040783995.py:5: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] } ], "source": [ "t, h_dict, info = data[\"precessing\"]\n", "E_init, L_init = nr_initial_conditions(info)\n", "print(f\"{sims['precessing']}: q = {info['q']:.3f}, E_initial = {E_init:.6f}, L_initial = {L_init:.6f}\")\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]),\n", " E_initial=E_init, L_initial=L_init)\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "markdown", "id": "196f51d7", "metadata": {}, "source": [ "---\n", "### 3. Using native PN E_initial and L_initial\n", "\n", "Here we let `gw_remnant` compute the initial energy and angular momentum from its built-in post-Newtonian expressions (SEOBNRv5EHM-based, valid for aligned spins to 3PN + non-spinning circular to 5PN). No `E_initial` or `L_initial` is passed." ] }, { "cell_type": "markdown", "id": "ab7efca7", "metadata": {}, "source": [ "#### 3a. Non-spinning quasi-circular" ] }, { "cell_type": "code", "execution_count": 7, "id": "c8a539fe", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/2826678238.py:3: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2265: PN E_initial = 0.003887, PN L_initial = 0.967556\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 3.000\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.02482047 M\n", "Peak luminosity : 0.00052717\n", "Remnant mass : 0.97129257 M\n", "Remnant spin (dimensionless) : 0.53907838\n", "Remnant spin vector (x,y,z) : (0.00000004, 0.00000016, 0.53907838)\n", "Remnant kick velocity : 0.00056296 c\n", "Remnant kick velocity : 168.77 km/s\n", "Remnant kick vector (x,y,z) : (0.00031970, -0.00046337, 0.00000006) c\n", "Remnant displacement (x,y,z) : (0.04215739, -0.08286044, 0.00002058) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.971293 0.971102\n", "Remnant |chi| 0.539078 0.540609\n", "Remnant spin (z) 0.539078 0.540609\n", "Kick velocity [c] 0.000563 0.000582\n", "Spin vector [3.8323e-08 1.5624e-07 5.3908e-01]\n", " [ 5.3327e-08 -7.7351e-08 5.4061e-01] (SXS)\n" ] } ], "source": [ "t, h_dict, info = data[\"non-spinning\"]\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]))\n", "print(f\"{sims['non-spinning']}: PN E_initial = {calc.E_initial:.6f}, PN L_initial = {calc.L_initial:.6f}\")\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "markdown", "id": "acacfb77", "metadata": {}, "source": [ "#### 3b. Non-precessing quasi-circular" ] }, { "cell_type": "code", "execution_count": 8, "id": "0a677771", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2014: PN E_initial = 0.005803, PN L_initial = 0.632742\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 4.000\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.03939614 M\n", "Peak luminosity : 0.00073047\n", "Remnant mass : 0.95480117 M\n", "Remnant spin (dimensionless) : 0.87990845\n", "Remnant spin vector (x,y,z) : (-0.00000004, -0.00000033, 0.87990845)\n", "Remnant kick velocity : 0.00006426 c\n", "Remnant kick velocity : 19.26 km/s\n", "Remnant kick vector (x,y,z) : (0.00003288, -0.00005521, 0.00000004) c\n", "Remnant displacement (x,y,z) : (-0.00345770, -0.01177226, 0.00000834) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.954801 0.954579\n", "Remnant |chi| 0.879908 0.881767\n", "Remnant spin (z) 0.879908 0.881767\n", "Kick velocity [c] 0.000064 0.000040\n", "Spin vector [-3.9435e-08 -3.2612e-07 8.7991e-01]\n", " [1.6820e-07 6.9549e-07 8.8177e-01] (SXS)\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/3571571940.py:3: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] } ], "source": [ "t, h_dict, info = data[\"non-precessing\"]\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]))\n", "print(f\"{sims['non-precessing']}: PN E_initial = {calc.E_initial:.6f}, PN L_initial = {calc.L_initial:.6f}\")\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "markdown", "id": "aef23265", "metadata": {}, "source": [ "#### 3c. Precessing quasi-circular\n", "\n", "**Note:** The PN expressions assume the initial orbital angular momentum is aligned with the z-axis. For precessing binaries this assumption breaks down, so the remnant spin is slightly off even though the remnant mass and kick velocity remain accurate. When directly supplying NR initial conditions (Section 2), this issue does not arise." ] }, { "cell_type": "code", "execution_count": 9, "id": "2ce238e9", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/var/folders/9k/xlxjfz4d2cz1063spty94p0r0000gq/T/ipykernel_23613/2529988132.py:3: UserWarning: Tips: If you are using NR waveforms, ensure that they do not contain junk radiation, as that is known to corrupt the remnant property estimation.\n", " calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "SXS:BBH:2000: PN E_initial = 0.005430, PN L_initial = 0.667319\n", "==================================================\n", "Remnant Properties Summary\n", "==================================================\n", "Mass ratio : 3.999\n", "Initial mass : 1.00000000 M\n", "Total energy radiated : 0.01918366 M\n", "Peak luminosity : 0.00041586\n", "Remnant mass : 0.97538668 M\n", "Remnant spin (dimensionless) : 0.55592193\n", "Remnant spin vector (x,y,z) : (-0.08364207, 0.42400225, 0.55592193)\n", "Remnant kick velocity : 0.00090302 c\n", "Remnant kick velocity : 270.72 km/s\n", "Remnant kick vector (x,y,z) : (-0.00013265, 0.00010677, -0.00088682) c\n", "Remnant displacement (x,y,z) : (-0.02165076, -0.00085276, -0.12449380) M\n", "==================================================\n", "Property gw_remnant SXS metadata\n", "---------------------------------------------------------\n", "Remnant mass [M] 0.975387 0.975265\n", "Remnant |chi| 0.704147 0.691529\n", "Remnant spin (z) 0.555922 0.559751\n", "Kick velocity [c] 0.000903 0.000970\n", "Spin vector [-0.0836 0.424 0.5559]\n", " [-0.0868 0.3967 0.5598] (SXS)\n" ] } ], "source": [ "t, h_dict, info = data[\"precessing\"]\n", "\n", "calc = GWRemnantCalculator(time=t, h_dict=h_dict, q=info[\"q\"],\n", " chi1=list(info[\"chi1\"]), chi2=list(info[\"chi2\"]))\n", "print(f\"{sims['precessing']}: PN E_initial = {calc.E_initial:.6f}, PN L_initial = {calc.L_initial:.6f}\")\n", "calc.print_remnants()\n", "compare_with_nr(calc, info)" ] }, { "cell_type": "code", "execution_count": null, "id": "051fe55c-1328-4fbd-80c8-2002c7e12f47", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "9cb1b283-1684-4a6a-83d2-d1470e3b3244", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "kitp-py310", "language": "python", "name": "kitp-py310" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.18" } }, "nbformat": 4, "nbformat_minor": 5 }