{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "# NRPyPN Shortcuts\n", "\n", "## Author: Zach Etienne\n", "\n", "## This notebook contains a number of useful shortcuts for constructing post-Newtonian expressions within NRPyPN/SymPy.\n", "\n", "**Notebook Status:** Validated through extensive use \n", "\n", "**Validation Notes:** This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented. (TODO)**\n", "\n", "### Corresponding Python module:\n", "1. [NRPyPN_shortcuts.py](../edit/NRPyPN_shortcuts.py)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Table of Contents\n", "$$\\label{toc}$$\n", "\n", "1. Part 1: [Declare global variables used as input to PN expressions](#globals)\n", "1. Part 2: [Vector & arithmetic operations](#vectorops), including dot products (`dot(a,b)`) and cross products (`cross(a,b)`) (including the [Levi-Civita symbol](https://en.wikipedia.org/wiki/Levi-Civita_symbol)); also the shortcut `div(a,b)` to `sp.Rational(a,b)` for rational number `a/b\n", "1. Part 3: [Numerical evaluation of SymPy expressions](#numeval) (`numeval(expr)`)\n", "1. Part 4: [`eval__P_t__and__P_r()`: Compute $p_t$ (tangential orbital momentum) and $p_r$ (radial orbital momentum](#ptpr)\n", "1. Part 5: [Validate against corresponding Python module](#validate)\n", "1. Part 6: [LaTeX PDF output](#latex_pdf_output): $\\LaTeX$ PDF Output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 1: Declare global variables used as input to PN expressions \\[Back to [top](#toc)\\]\n", "$$\\label{globals}$$ \n", "\n", "Here, after initializing the module, we define \n", "* `m1` and `m2`, the masses of the point particles. We will require that `m1+m2=1`, using $G=c=1$ units, as all results can be rescaled appropriately with total mass.\n", "* `S1U`=$\\mathbf{S}_1$ and `S2U`=$\\mathbf{S}_2$, the dimensionful spin vectors of the point masses\n", "* `pU`=$\\mathbf{p} = \\mathbf{P}_1/\\mu = -\\mathbf{P}_2/\\mu$, where $\\mathbf{P}_i$ is the momentum vector of mass $i$ with respect to the center of mass, and $\\mu=m_1 m_2 / (m_1+m_2)^2$ is the reduced mass\n", "* `nU`=$(\\mathbf{X}_1-\\mathbf{X}_2)/|\\mathbf{X}_1-\\mathbf{X}_2|$, where $\\mathbf{X}_i$ is the position vector of mass $i$ with respect to the center of mass\n", "* `drdt`=$\\frac{dr}{dt}$, the radial infall rate of the binary system\n", "* `Pt`=$p_t$, `Pr`=$p_r$, the tangential and radial components of the momentum vector for the binary, with respect to its center of mass.\n", "* `r`=`q`=$|\\mathbf{X}_1-\\mathbf{X}_2|$\n", "* `chi1U`=$\\mathbf{\\chi}_1=\\mathbf{S}_1/m_2^2$ and `chi2U`=$\\mathbf{\\chi}_2=\\mathbf{S}_2/m_2^2$, the dimensionless spin vectors of the point masses (each with physically permissible values between -1 and +1)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting NRPyPN_shortcuts-validation.py\n" ] } ], "source": [ "%%writefile NRPyPN_shortcuts-validation.py\n", "# As documented in the NRPyPN notebook\n", "# NRPyPN_shortcuts.ipynb, this Python script\n", "# provides useful shortcuts for inputting\n", "# post-Newtonian expressions into SymPy/NRPy+\n", "\n", "# Basic functions:\n", "# dot(a,b): 3-vector dot product\n", "# cross(a,b): 3-vector cross product\n", "# div(a,b): a shortcut for SymPy's sp.Rational(a,b), to declare rational numbers\n", "# num_eval(expr): Numerically evaluates NRPyPN expressions\n", "\n", "# Author: Zach Etienne\n", "# zachetie **at** gmail **dot* com\n", "\n", "# Step 0: Add NRPy's directory to the path\n", "# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory\n", "import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends\n", "import indexedexpNRPyPN as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support\n", "\n", "# Step 1: Declare several global variables used\n", "# throughout NRPyPN\n", "m1,m2 = sp.symbols('m1 m2',real=True)\n", "S1U = ixp.declarerank1(\"S1U\")\n", "S2U = ixp.declarerank1(\"S2U\")\n", "pU = ixp.declarerank1(\"pU\")\n", "nU = ixp.declarerank1(\"nU\")\n", "\n", "drdt = sp.symbols('drdt', real=True)\n", "Pt, Pr = sp.symbols('Pt Pr', real=True)\n", "# Some references use r, others use q to represent the\n", "# distance between the two point masses. This is rather\n", "# confusing since q is also used to represent the\n", "# mass ratio m2/m1. However, q is the canonical position\n", "# variable name in Hamiltonian mechanics, so both are\n", "# well justified. It should be obvious which is which\n", "# throughout NRPyPN.\n", "r, q = sp.symbols('r q', real=True)\n", "chi1U = ixp.declarerank1('chi1U')\n", "chi2U = ixp.declarerank1('chi2U')\n", "\n", "# Euler-Mascheroni gamma constant:\n", "gamma_EulerMascheroni = sp.EulerGamma\n", "\n", "# Derived quantities used in Damour et al papers:\n", "n12U = ixp.zerorank1()\n", "n21U = ixp.zerorank1()\n", "p1U = ixp.zerorank1()\n", "p2U = ixp.zerorank1()\n", "for i in range(3):\n", " n12U[i] = +nU[i]\n", " n21U[i] = -nU[i]\n", " p1U[i] = +pU[i]\n", " p2U[i] = -pU[i]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 2: Vector operations, including dot products (`dot(a,b)`) and cross products (`cross(a,b)`) (including the [Levi-Civita symbol](https://en.wikipedia.org/wiki/Levi-Civita_symbol)); also the shortcut `div(a,b)` to `sp.Rational(a,b)` for rational number `a/b` \\[Back to [top](#toc)\\]\n", "$$\\label{vectorops}$$ \n", "\n", "Here we define \n", "* `dot(a,b)`=$\\mathbf{a}\\cdot\\mathbf{b}$, for 3-vectors $\\mathbf{a}$ and $\\mathbf{b}$\n", "* `cross(a,b)`=$\\mathbf{a}\\times\\mathbf{b}$, for 3-vectors $\\mathbf{a}$ and $\\mathbf{b}$\n", " * ... calling the corresponding `ixp.LeviCivitaSymbol_dim3_rank3()` function from [indexedexp.py](../../edit/indexedexp.py), which constructs the [Levi-Civita symbol](https://en.wikipedia.org/wiki/Levi-Civita_symbol) used for cross products\n", "* `div(a,b)`=$a/b$, for integers $a$ and $b$. This is a convenient shortcut for SymPy's `sp.Rational(a,b)` function" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Appending to NRPyPN_shortcuts-validation.py\n" ] } ], "source": [ "%%writefile -a NRPyPN_shortcuts-validation.py\n", "\n", "# Step 2.a: Define dot and cross product of vectors\n", "def dot(vec1,vec2):\n", " vec1_dot_vec2 = sp.sympify(0)\n", " for i in range(3):\n", " vec1_dot_vec2 += vec1[i]*vec2[i]\n", " return vec1_dot_vec2\n", "\n", "def cross(vec1,vec2):\n", " vec1_cross_vec2 = ixp.zerorank1()\n", " LeviCivitaSymbol = ixp.LeviCivitaSymbol_dim3_rank3()\n", " for i in range(3):\n", " for j in range(3):\n", " for k in range(3):\n", " vec1_cross_vec2[i] += LeviCivitaSymbol[i][j][k]*vec1[j]*vec2[k]\n", " return vec1_cross_vec2\n", "\n", "# Step 2.b: Construct rational numbers a/b via div(a,b)\n", "def div(a,b):\n", " return sp.Rational(a,b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 3: `num_eval(expr)`: numerical evaluation of SymPy expressions \\[Back to [top](#toc)\\]\n", "$$\\label{numeval}$$ \n", "\n", "* `num_eval(expr, qmassratio, nr, nchi1x, nchi1y, nchi1z, nchi2x, nchi2y, nchi2z, nPt=None, ndrdt=None)` numerically evaluates expression `expr` with options to also specify numerical values for the tangential momentum $p_t$ and the radial infall rate $\\frac{dr}{dt}$. Default options evaluate a $q=1$ mass-ratio, nonspinning binary with separation $r=12$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Appending to NRPyPN_shortcuts-validation.py\n" ] } ], "source": [ "%%writefile -a NRPyPN_shortcuts-validation.py\n", "\n", "# Step 3: num_eval(expr), a means to numerically evaluate SymPy/NRPyPN\n", "# expressions\n", "def num_eval(expr,\n", " qmassratio = 1.0, # must be >= 1\n", " nr = 12.0, # Orbital separation\n", " nchi1x = +0.,\n", " nchi1y = +0.,\n", " nchi1z = +0.,\n", " nchi2x = +0.,\n", " nchi2y = +0.,\n", " nchi2z = +0.,\n", " nPt=None, ndrdt=None):\n", "\n", " # DERIVED QUANTITIES BELOW\n", " # We want m1+m2 = 1, so that\n", " # m2/m1 = qmassratio\n", " # We find below:\n", " nm1 = 1/(1+qmassratio)\n", " nm2 = qmassratio/(1+qmassratio)\n", " # This way nm1+nm2 = (qmassratio+1)/(1+qmassratio) = 1 CHECK\n", " # and nm2/nm1 = qmassratio CHECK\n", "\n", " nS1U0 = nchi1x*nm1**2\n", " nS1U1 = nchi1y*nm1**2\n", " nS1U2 = nchi1z*nm1**2\n", " nS2U0 = nchi2x*nm2**2\n", " nS2U1 = nchi2y*nm2**2\n", " nS2U2 = nchi2z*nm2**2\n", "\n", " if nPt != None:\n", " expr2 = expr.subs(Pt,nPt)\n", " expr = expr2\n", " if ndrdt != None:\n", " expr2 = expr.subs(drdt,ndrdt)\n", " expr = expr2\n", " return expr\\\n", ".subs(m1,nm1).subs(m2,nm2)\\\n", ".subs(S1U[0],nS1U0).subs(S1U[1],nS1U1).subs(S1U[2],nS1U2)\\\n", ".subs(S2U[0],nS2U0).subs(S2U[1],nS2U1).subs(S2U[2],nS2U2)\\\n", ".subs(chi1U[0],nchi1x).subs(chi1U[1],nchi1y).subs(chi1U[2],nchi1z)\\\n", ".subs(chi2U[0],nchi2x).subs(chi2U[1],nchi2y).subs(chi2U[2],nchi2z)\\\n", ".subs(r,nr).subs(q,nr).subs(sp.pi,sp.N(sp.pi))\\\n", ".subs(gamma_EulerMascheroni,sp.N(sp.EulerGamma))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 4: `eval__P_t__and__P_r()`: Compute $p_t$ (tangential orbital momentum) and $p_r$ (radial orbital momentum \\[Back to [top](#toc)\\]\n", "$$\\label{ptpr}$$ " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Appending to NRPyPN_shortcuts-validation.py\n" ] } ], "source": [ "%%writefile -a NRPyPN_shortcuts-validation.py\n", "\n", "# Shortcut function to just evaluate P_t and P_r\n", "# -= Inputs =-\n", "# * qmassratio: mass ratio q>=1\n", "# * nr: Orbital separation (total coordinate distance between black holes)\n", "# * nchi1x,nchi1y,nchi1z: dimensionless spin vector for BH 1\n", "# * nchi2x,nchi2y,nchi2z: dimensionless spin vector for BH 2\n", "# -= Outputs =-\n", "# The numerical values for\n", "# * P_t: the tangential momentum\n", "# * P_r: the radial momentum\n", "def eval__P_t__and__P_r(qmassratio, nr,\n", " nchi1x, nchi1y, nchi1z,\n", " nchi2x, nchi2y, nchi2z):\n", "\n", " # Compute p_t, the tangential component of momentum\n", " import PN_p_t as pt\n", " pt.f_p_t(m1,m2, chi1U,chi2U, r)\n", "\n", " # Compute p_r, the radial component of momentum\n", " import PN_p_r as pr\n", " pr.f_p_r(m1,m2, n12U,n21U, chi1U,chi2U, S1U,S2U, p1U,p2U, r)\n", "\n", " nPt = num_eval(pt.p_t,\n", " qmassratio=qmassratio, nr=nr,\n", " nchi1x=nchi1x,nchi1y=nchi1y,nchi1z=nchi1z,\n", " nchi2x=nchi2x,nchi2y=nchi2y,nchi2z=nchi2z)\n", "\n", " nPr = num_eval(pr.p_r,\n", " qmassratio = qmassratio, nr=nr,\n", " nchi1x=nchi1x, nchi1y=nchi1y, nchi1z=nchi1z,\n", " nchi2x=nchi2x, nchi2y=nchi2y, nchi2z=nchi2z, nPt=nPt)\n", " return nPt, nPr\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 5: Validate against corresponding Python module \\[Back to [top](#toc)\\]\n", "$$\\label{validate}$$ " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Printing difference between original NRPyPN_shortcuts.py and this code, NRPyPN_shortcuts-validation.py.\n", "No difference. TEST PASSED!\n" ] } ], "source": [ "import difflib,sys\n", "\n", "print(\"Printing difference between original NRPyPN_shortcuts.py and this code, NRPyPN_shortcuts-validation.py.\")\n", "\n", "# Open the files to compare\n", "with open(\"NRPyPN_shortcuts.py\") as file1, open(\"NRPyPN_shortcuts-validation.py\") as file2:\n", " # Read the lines of each file,\n", " file1_lines = file1.readlines()\n", " file2_lines = file2.readlines()\n", " num_diffs = 0\n", " for line in difflib.unified_diff(file1_lines, file2_lines, fromfile=\"NRPyPN_shortcuts.py\", tofile=\"NRPyPN_shortcuts-validation.py\"):\n", " sys.stdout.writelines(line)\n", " num_diffs = num_diffs + 1\n", " if num_diffs == 0:\n", " print(\"No difference. TEST PASSED!\")\n", " else:\n", " print(\"ERROR: Disagreement found with .py file. See differences above.\")\n", " sys.exit(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "# Part 6: Output this notebook to $\\LaTeX$-formatted PDF file \\[Back to [top](#toc)\\]\n", "$$\\label{latex_pdf_output}$$\n", "\n", "The following code cell converts this Jupyter notebook into a proper, clickable $\\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename\n", "[NRPyPN_shortcuts.pdf](NRPyPN_shortcuts.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created NRPyPN_shortcuts.tex, and compiled LaTeX file to PDF file\n", " NRPyPN_shortcuts.pdf\n" ] } ], "source": [ "import os,sys # Standard Python modules for multiplatform OS-level functions\n", "import cmdline_helperNRPyPN as cmd # NRPy+: Multi-platform Python command-line interface\n", "cmd.output_Jupyter_notebook_to_LaTeXed_PDF(\"NRPyPN_shortcuts\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "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.11" } }, "nbformat": 4, "nbformat_minor": 4 }