# Notebook from pileyan/DPO_python_2022
Path: lect08_Beautiful_soup1/.ipynb_checkpoints/web-scrape-checkpoint.ipynb

# Python для анализа данных

*Алла Тамбовцева, НИУ ВШЭ*_____no_output_____## Web-scraping

Мы уже немного познакомились со структурой html-файлов, теперь попробуем выгрузить информацию из реальной страницы, а точнее, с реального сайта [nplus1.ru](https://nplus1.ru/).

**Наша задача:** выгрузить недавние новости в датафрейм `pandas`, чтобы потом сохранить все в csv-файл.

Сначала сгрузим весь html-код страницы и сохраним его в отдельную переменную. Для этого нам понадобится библиотека `requests`. Импортируем её:_____no_output_____

import requests_____no_output_____

Сохраним ссылку на главную страницу сайта в переменную `url` для удобства и выгрузим страницу. (Разумеется, это будет работать при подключении к интернету. Если соединение будет отключено, Python выдаст `NewConnectionError`)._____no_output_____

url = 'https://nplus1.ru/' # сохраняем
page = requests.get(url) # загружаем страницу по ссылке_____no_output_____

Если мы просто посмотрим на объект, мы ничего особенного не увидим:_____no_output_____

page  # response 200 - страница загружена_____no_output_____

Импортируем функцию `BeautifulSoup` из библиотеки `bs4` (от *beautifulsoup4*) и заберём со страницы `page` код html в виде текста. _____no_output_____

from bs4 import BeautifulSoup  # не спрашивайте, почему BeautifulSoup_____no_output_____soup = BeautifulSoup(page.text, 'lxml')_____no_output_____

Если выведем `soup` на экран, мы увидим то же самое, что в режиме разработчика или в режиме происмотра исходного кода (`view-source` через *Ctrl+U* в Google Chrome)._____no_output_____

soup_____no_output_____

Для просмотра выглядит не очень удобно.  «Причешем» наш `soup` – воспользуемся методом `.prettify()` в сочетании с функцией `print()`._____no_output_____

print(soup.prettify())_____no_output_____

В такой выдаче ориентироваться гораздо удобнее (но при желании, то же можно увидеть в браузере, на большом экране)._____no_output_____Чтобы сгрузить все новости с главной страницы сайта, нужно собрать все ссылки на страницы с этими новостями. Ссылки в html-файле всегда заключены в тэг `<a></a>` и имеют атрибут `href`. Посмотрим на кусочки кода, соответствующие всем ссылкам на главной странице сайта:_____no_output_____

for link in soup.find_all('a'):
    print(link.get('href'))#
/
#
#
/rubric/astronomy
/rubric/physics
/rubric/biology
/rubric/robots-drones
/theme/oops
/theme/economy-literature
/theme/hayabusa
/theme/art-of-integration
/
#
/rubric/astronomy
/rubric/physics
/rubric/biology
/rubric/robots-drones
#
/theme/oops
/theme/economy-literature
/theme/hayabusa
/theme/art-of-integration
https://nplus1.ru/blog/2019/03/27/five-years-deadline
https://nplus1.ru/blog/2019/03/27/five-years-deadline
https://nplus1.ru/blog/2019/03/26/limb-183
https://nplus1.ru/blog/2019/03/25/ben-franklin-effect
https://nplus1.ru/blog/2019/03/22/a-history-of-private-life
https://nplus1.ru/blog/2019/03/22/spanish-princess
https://nplus1.ru/blog/2019/03/21/climate-change-of-the-dead
https://nplus1.ru/blog/2019/03/21/half-fathers
https://nplus1.ru/blog/2019/03/20/coins-manufactering
https://nplus1.ru/blog/2019/03/18/map-of-the-universe
https://nplus1.ru/blog/2019/03/28/what-about-labioldentals
/news/2019/03/31/first
/news/2019/03/30/very-big-boulder-on-Bennu
/news/2019/03/30/PSP-two-flyby
/news/2019/03/30/functional-brain-map
/news/2019/03/30/seizure-odour
/news/2019/03/30/id-capsule
/news/2019/03/29/mars-helicopter-fly-now
/material/2019/03/29/psycho-batman
/rubric/mezzanine
/news/2019/03/29/ABRACADABRA
/news/2019/03/29/nz-apple-picker
/news/2019/03/29/i-know-kung-fu
/news/2019/03/29/warming-leads-to-cruelty
/news/2019/03/29/ghost-faces
/news/2019/03/29/language-storage
/blog/2019/03/29/the-revolutionary-genius-of-plants
/blog/2019/03/28/what-about-labioldentals
/material/2019/03/28/head-transplantation
/rubric/medicine
/material/2019/03/27/tetanus
/rubric/medicine
/news/2019/03/29/island-building
/news/2019/03/29/deep-water-mars
/news/2019/03/29/curaxins
/news/2019/03/29/inflation-falsification
/news/2019/03/29/sooo-tiny
/news/2019/03/29/small-moons-of-Saturn
/blog/2019/03/27/five-years-deadline
/blog/2019/03/26/limb-183
/news/2019/03/26/learning-languages
/rubric/partners
/material/2019/03/25/120-element
/rubric/physics
/theme/phys
/news/2019/03/26/dark-system
/news/2019/03/29/language-storage
/news/2019/03/27/schizo-genes
/news/2019/03/29/curaxins
/news/2019/03/29/inflation-falsification
/news/2019/03/26/efremovka-ancient-solar-flare
/news/2019/03/29/ABRACADABRA
/news/2019/03/27/exoplanet-interferometry
/news/2019/03/26/quantum-radiation-pressure-noise
/news/2019/03/29/ghost-faces
https://nplus1.ru/blog/2019/03/27/five-years-deadline
https://nplus1.ru/blog/2019/03/27/five-years-deadline
https://nplus1.ru/blog/2019/03/26/limb-183
https://nplus1.ru/blog/2019/03/25/ben-franklin-effect
https://nplus1.ru/blog/2019/03/22/a-history-of-private-life
https://nplus1.ru/blog/2019/03/22/spanish-princess
https://nplus1.ru/blog/2019/03/21/climate-change-of-the-dead
https://nplus1.ru/blog/2019/03/21/half-fathers
https://nplus1.ru/blog/2019/03/20/coins-manufactering
https://nplus1.ru/blog/2019/03/18/map-of-the-universe
https://nplus1.ru/blog/2019/03/28/what-about-labioldentals
/
/about
/adv
/rules
/vacancy
/difficult
https://nplus1.ru/personal-data-policy
#
https://t.me/nplusone
http://vk.com/nplusone
https://www.facebook.com/nplusone
https://twitter.com/nplusodin
https://ok.ru/nplus1
https://soundcloud.com/nplus_1
/rss

Ссылок много. Но нам нужны только новости – ссылки, которые начинаются со слова `/news`. Добавим условие: будем выбирать только те ссылки, в которых есть `/news`. Создадим пустой список `urls` и будем добавлять в него только ссылки, которые удовлетворяют этому условию._____no_output_____

urls = []

for link in soup.find_all('a'):
    if '/news' in link.get('href'):
        urls.append(link.get('href'))
urls_____no_output_____

Ссылки, которые у нас есть в списке `urls`, относительные: они неполные, начало ссылки (название сайта) отсутствует. Давайте превратим их в абсолютные ‒ склеим с ссылкой https://nplus1.ru._____no_output_____

full_urls = []

for u in urls:
    res = 'https://nplus1.ru' + u
    full_urls.append(res)

full_urls_____no_output_____

Теперь наша задача сводится к следующему: изучить одну страницу с новостью, научиться из нее вытаскивать текст и всю необходимую информацию, а потом применить весь набор действий к каждой ссылке из `full_urls` в цикле. Посмотрим на новость с индексом 1, у вас может быть другая, новости обновляются._____no_output_____

url0 = full_urls[1]

page0 = requests.get(url0)
soup0 = BeautifulSoup(page0.text, 'lxml')_____no_output_____

В коде каждой страницы с новостью есть часть с мета-информацией: датой, именем автора и проч. Такая информация окружена тэгом `<meta></meta>`. Посмотрим:_____no_output_____

soup0.find_all('meta')_____no_output_____

Из этого списка нам нужны части с именем автора, датой, заголовком и кратким описанием. Воспользуемся поиском по атрибуту `name`. Передадим функции `find_all()` в качестве аргумента словарь с названием и значением атрибута: _____no_output_____

soup0.find_all('meta', {'name' : 'author'}) # например, автор_____no_output_____

Теперь выберем единственный элемент полученного списка (с индексом 0):_____no_output_____

soup0.find_all('meta', {'name' : 'author'})[0]_____no_output_____

Нам нужно вытащить из этого объекта `content` – имя автора. Посмотрим на атрибуты:_____no_output_____

soup0.find_all('meta', {'name' : 'author'})[0].attrs_____no_output_____

Как получить отсюда `content`? Очень просто, ведь это словарь! А доставать из словаря значение по ключу мы умеем._____no_output_____

author = soup0.find_all('meta', {'name' : 'author'})[0].attrs['content']
author_____no_output_____

Аналогичным образом извлечем дату, заголовок и описание._____no_output_____

date = soup0.find_all('meta', {'itemprop' : 'datePublished'})[0].attrs['content']
title = soup0.find_all('meta', {'property' : 'og:title'})[0].attrs['content']
description = soup0.find_all('meta', {'name' : 'description'})[0].attrs['content']_____no_output_____

Осталось вытащить рубрики и сложность текста. Если мы посмотрим на исходный код страницы, мы увидим, что нужная нам информация находится в тэгах `<p></p>`:_____no_output_____

soup0.find_all('p')_____no_output_____

Выберем из полученного списка первый элемент и найдем в нем все тэги `<a>`:_____no_output_____

soup0.find_all('p')[0].find_all('a')_____no_output_____

Получился список из одного элемента. Применим списковые включения – вытащим из каждого элемента (их могло бы быть больше) текст и поместим его в новый список `rubrics`._____no_output_____

rubrics = [r.text for r in soup0.find_all('p')[0].find_all('a')]
rubrics_____no_output_____

Осталась только сложность. Возьмем соответствующий кусок кода:_____no_output_____

soup0.find_all('span', {'class' : 'difficult-value'})_____no_output_____

И выберем оттуда текст._____no_output_____

diff = soup0.find_all('span', {'class' : 'difficult-value'})[0].text
diff_____no_output_____

Теперь перейдем к тексту самой новости. Как можно заметить, текст сохранен в абзацах `<p></p>`, причем безо всяких атрибутов. Сообщим Python, что нас интересуют куски с пустым атрибутом `class`:_____no_output_____

text_list = soup0.find_all('p', {'class' : None})_____no_output_____

«Выцепим» все тексты (без тэгов) из полученного списка:_____no_output_____

text = [t.text for t in text_list]_____no_output_____

Склеим все элементы списка `text` через пробел:_____no_output_____

final_text = ' '.join(text)
final_text_____no_output_____

Все здорово, только мешают отступы-переходы на новую строку `\n`. Заменим их на пробелы с помощью метода `.replace`:_____no_output_____

final_text = final_text.replace('\n', ' ')_____no_output_____

Не прошло и двух пар, как мы разобрались со всем :) Теперь осталось совсем чуть-чуть. Написать готовую функцию для всех проделанных нами действий и применить ее в цикле для всех ссылок в списке `full_urls`. Напишем! Аргументом функции будет ссылка на новость, а возвращать она будет текст новости и всю необходимую информацию (дата, автор, сложность и проч.). Скопируем все строки кода выше._____no_output_____

def GetNews(url0):
    """
    Returns a tuple with url0, date, author, description, title, final_text, rubrics, diff.
    Parameters:

    url0 is a link to the news (string)
    """
    page0 = requests.get(url0)
    soup0 = BeautifulSoup(page0.text, 'lxml')
    author = soup0.find_all('meta', {'name' : 'author'})[0].attrs['content']
    date = soup0.find_all('meta', {'itemprop' : 'datePublished'})[0].attrs['content']
    title = soup0.find_all('meta', {'property' : 'og:title'})[0].attrs['content']
    description = soup0.find_all('meta', {'name' : 'description'})[0].attrs['content']
    rubrics = [r.text for r in soup0.find_all('p')[0].find_all('a')]
    diff = soup0.find_all('span', {'class' : 'difficult-value'})[0].text
    text_list = soup0.find_all('p', {'class' : None})
    text = [t.text for t in text_list]
    final_text = ' '.join(text)
    final_text = final_text.replace('\n', ' ')

    return url0, date, author, description, title, final_text, rubrics, diff_____no_output_____

Уфф. Осталось применить ее в цикле. Но давайте не будем спешить: импортируем функцию `sleep` для задержки, чтобы на каждой итерации цикла, прежде чем перейти к следующей новости, Python ждал несколько секунд. Во-первых, это нужно, чтобы сайт «не понял», чтобы мы его грабим, да еще автоматически. Во-вторых, с небольшой задержкой всегда есть гарантия, что страница прогрузится (сейчас это пока не очень важно, но особенно актуально будет, когда будем обсуждать встраивание в браузер с Selenium). Приступим._____no_output_____

from time import sleep_____no_output_____news = [] # это будет список из кортежей, в которых будут храниться данные по каждой новости

for link in full_urls:
    res = GetNews(link)
    news.append(res)
    sleep(3) # задержка в 3 секунды_____no_output_____

Так теперь выглядит первый элемент списка:_____no_output_____

news[0]_____no_output_____

Импортируем `pandas` и создадим датафрейм из списка кортежей: _____no_output_____

import pandas as pd_____no_output_____df = pd.DataFrame(news)_____no_output_____df.head(2)_____no_output_____

Переименуем столбцы в базе._____no_output_____

df.columns = ['link', 'date', 'author', 'desc', 'title', 'text', 'rubric', 'diffc']_____no_output_____df.head(2)_____no_output_____

Теперь внесем изменения: сделаем столбец `diffc` числовым – типа *float*._____no_output_____

df['diffc'] = [float(i) for i in df.diffc]_____no_output_____

Теперь сложность представлена в базе как количественный показатель, и описывать ее можно соответствующим образом:_____no_output_____

df.diffc.describe()_____no_output_____

Теперь столбец со сложностью точно числовой. Можем даже построить для него гистограмму._____no_output_____

%matplotlib inline
df.diffc.plot.hist()_____no_output_____

Объединим рубрики в *text* в одну строку через запятую:_____no_output_____

df['rubric'] = [','.join(r) for r in df.rubric]_____no_output_____

Давайте почистим текст новостей – уберем оттуда текст, не относящийся к новостям. Найдем лишнее:_____no_output_____

df.text[0]_____no_output_____

Лишний текст находится после фразы 'Нашли опечатку?'. Так давайте будем разбивать строки по этой фразе с помощью метода `.split()` и брать все, что до нее (элемент с индексом 0)._____no_output_____

df['clean_text'] = [t.split('Нашли опечатку?')[0] for t in df.text]_____no_output_____

Осталось только заменить непонятные символы `\xa0` на пробелы:_____no_output_____

df['clean_text'] = [t.replace("\xa0", " ") for t in df.clean_text]_____no_output_____df.clean_text[0]_____no_output_____

Всё! Сохраняем датафрейм в файл. Для разнообразия сохраним в Excel:_____no_output_____

df.to_excel('nplus-news.xlsx')_____no_output_____


# Notebook from weichen-yan/nrpytutorial
Path: Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb

# Start-to-Finish Example: Setting up Polytropic [TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) Initial Data, in Curvilinear Coordinates

## Authors: Zach Etienne & Phil Chang
### Formatting improvements courtesy Brandon Clark

## This module sets up initial data for a TOV star in *spherical, isotropic coordinates*, using the *Numerical* ADM Spherical to BSSN Curvilinear initial data module (numerical = BSSN $\lambda^i$'s are computed using finite-difference derivatives instead of exact expressions).

**Notebook Status:**  Validated

**Validation Notes:** This module has been validated to exhibit convergence to zero of the Hamiltonian constraint violation at the expected order to the exact solution (see [plots](#convergence) at bottom). Note that convergence at the surface of the star will be lower order due to the sharp drop to zero in $T^{\mu\nu}$.

### NRPy+ Source Code for this module:

* [TOV/TOV_Solver.py](../edit/TOV/TOV_Solver.py); ([**NRPy+ Tutorial module reviewing mathematical formulation and equations solved**](Tutorial-ADM_Initial_Data-TOV.ipynb)); ([**start-to-finish NRPy+ Tutorial module demonstrating that initial data satisfy Hamiltonian constraint**](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb)): Tolman-Oppenheimer-Volkoff (TOV) initial data; defines all ADM variables and nonzero $T^{\mu\nu}$ components in Spherical basis.
* [BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py); [\[**tutorial**\]](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb): *Numerical* Spherical ADM$\to$Curvilinear BSSN converter function
* [BSSN/BSSN_constraints.py](../edit/BSSN/BSSN_constraints.py); [\[**tutorial**\]](Tutorial-BSSN_constraints.ipynb): Hamiltonian constraint in BSSN curvilinear basis/coordinates

## Introduction:
Here we use NRPy+ to set up initial data for a [simple polytrope TOV star](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation).

The entire algorithm is outlined as follows, with links to the relevant NRPy+ tutorial notebooks listed at each step:

1. Allocate memory for gridfunctions, including temporary storage for the Method of Lines time integration [(**NRPy+ tutorial on NRPy+ Method of Lines algorithm**)](Tutorial-Method_of_Lines-C_Code_Generation.ipynb).
1. Set gridfunction values to initial data
    * [**NRPy+ tutorial on TOV initial data**](Tutorial-ADM_Initial_Data-TOV.ipynb)
    * [**NRPy+ tutorial on validating TOV initial data**](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb).
1. Evaluate the Hamiltonian constraint violation
    * [**NRPy+ tutorial on BSSN constraints**](Tutorial-BSSN_constraints.ipynb)
1. Repeat above steps at two numerical resolutions to confirm convergence of Hamiltonian constraint violation to zero._____no_output_____

# Table of Contents
$$\label{toc}$$

This notebook is organized as follows

1. [Step 1](#initializenrpy): Set core NRPy+ parameters for numerical grids and reference metric
1. [Step 2](#adm_id_tov): Set up ADM initial data for polytropic TOV Star
    1. [Step 2.a](#tov_interp): Interpolating the TOV data file as needed
    1. [Step 2.b](#source): Compute source terms $S_{ij}$, $S_{i}$, $S$, and $\rho$
    1. [Step 2.c](#jacobian): Jacobian transformation on the ADM/BSSN source terms
    1. [Step 2.d](#tensor): Rescale tensorial quantities
1. [Step 3](#adm_id_spacetime): Convert ADM spacetime quantity initial data from Spherical to BSSN Curvilinear coordinates
1. [Step 4](#validate): Validating that the TOV initial data satisfy the Hamiltonian constraint
    1. [Step 4.a](#ham_const_output): Output the Hamiltonian Constraint
    1. [Step 4.b](#apply_bcs): Apply singular, curvilinear coordinate boundary conditions
    1. [Step 4.c](#enforce3metric): Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$
1. [Step 5](#mainc): `TOV_Playground.c`: The Main C Code
1. [Step 6](#plot): Plotting the single-neutron-star initial data
1. [Step 7](#convergence): Validation: Convergence of numerical errors (Hamiltonian constraint violation) to zero
1. [Step 8](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file_____no_output_____

# Step 1: Set core NRPy+ parameters for numerical grids and reference metric \[Back to [top](#toc)\]
$$\label{initializenrpy}$$
_____no_output_____

# Step P1: Import needed NRPy+ core modules:
from outputC import *            # NRPy+: Core C code output module
import finite_difference as fin  # NRPy+: Finite difference C code generation module
import NRPy_param_funcs as par   # NRPy+: Parameter interface
import grid as gri               # NRPy+: Functions having to do with numerical grids
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import reference_metric as rfm   # NRPy+: Reference metric support
import cmdline_helper as cmd     # NRPy+: Multi-platform Python command-line interface
import shutil, os, sys           # Standard Python modules for multiplatform OS-level functions

# Step P2: Create C code output directory:
Ccodesdir = os.path.join("TOVID_Ccodes/")
# First remove C code output directory if it exists
# Courtesy https://stackoverflow.com/questions/303200/how-do-i-remove-delete-a-folder-that-is-not-empty
# !rm -r ScalarWaveCurvilinear_Playground_Ccodes
shutil.rmtree(Ccodesdir, ignore_errors=True)
# Then create a fresh directory
cmd.mkdir(Ccodesdir)

# Step P3: Create executable output directory:
outdir = os.path.join(Ccodesdir,"output/")
cmd.mkdir(outdir)

# Step 1: Set the spatial dimension parameter
#         to three this time, and then read
#         the parameter as DIM.
par.set_parval_from_str("grid::DIM",3)
DIM = par.parval_from_str("grid::DIM")

# Step 2: Set some core parameters, including CoordSystem MoL timestepping algorithm,
#                                 FD order, floating point precision, and CFL factor:
# Choices are: Spherical, SinhSpherical, SinhSphericalv2, Cylindrical, SinhCylindrical,
#              SymTP, SinhSymTP
CoordSystem     = "Spherical"

# Step 2.a: Set defaults for Coordinate system parameters.
#           These are perhaps the most commonly adjusted parameters,
#           so we enable modifications at this high level.

# domain_size     = 7.5 # SET BELOW BASED ON TOV STELLAR RADIUS

# sinh_width sets the default value for:
#   * SinhSpherical's params.SINHW
#   * SinhCylindrical's params.SINHW{RHO,Z}
#   * SinhSymTP's params.SINHWAA
sinh_width      = 0.4 # If Sinh* coordinates chosen

# sinhv2_const_dr sets the default value for:
#   * SinhSphericalv2's params.const_dr
#   * SinhCylindricalv2's params.const_d{rho,z}
sinhv2_const_dr = 0.05# If Sinh*v2 coordinates chosen

# SymTP_bScale sets the default value for:
#   * SinhSymTP's params.bScale
SymTP_bScale    = 0.5 # If SymTP chosen

# Step 2.b: Set the order of spatial finite difference derivatives;
#           and the core data type.
FD_order  = 4        # Finite difference order: even numbers only, starting with 2. 12 is generally unstable
REAL      = "double" # Best to use double here.

# Step 3: Set the coordinate system for the numerical grid
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

# Step 4: Set the finite differencing order to FD_order (set above).
par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order)

# Step 5: Set the direction=2 (phi) axis to be the symmetry axis; i.e.,
#         axis "2", corresponding to the i2 direction.
#         This sets all spatial derivatives in the phi direction to zero.
par.set_parval_from_str("indexedexp::symmetry_axes","2")

# Step 6: The MoLtimestepping interface is only used for memory allocation/deallocation
import MoLtimestepping.C_Code_Generation as MoL
from MoLtimestepping.RK_Butcher_Table_Dictionary import Butcher_dict
RK_method = "Euler" # DOES NOT MATTER; Again MoL interface is only used for memory alloc/dealloc.
RK_order  = Butcher_dict[RK_method][1]
cmd.mkdir(os.path.join(Ccodesdir,"MoLtimestepping/"))
MoL.MoL_C_Code_Generation(RK_method, RHS_string      = "", post_RHS_string = "",
    outdir = os.path.join(Ccodesdir,"MoLtimestepping/"))_____no_output_____

# Step 2: Set up ADM initial data for polytropic TOV Star \[Back to [top](#toc)\]
$$\label{adm_id_tov}$$

As documented [in the TOV Initial Data NRPy+ Tutorial Module](Tutorial-TOV_Initial_Data.ipynb) ([older version here](Tutorial-GRMHD_UnitConversion.ipynb)), we will now set up TOV initial data, storing the densely-sampled result to file (***Courtesy Phil Chang***).

The TOV solver uses an ODE integration routine provided by scipy, so we first make sure that scipy is installed:_____no_output_____

!pip install scipy > /dev/null_____no_output_____

Next we call the [`TOV.TOV_Solver()` function](../edit/TOV/TOV_Solver.py) ([NRPy+ Tutorial module](Tutorial-ADM_Initial_Data-TOV.ipynb)) to set up the initial data, using the default parameters for initial data. This function outputs the solution to a file named "outputTOVpolytrope.txt"._____no_output_____

############################
# Single polytrope example #
############################
import TOV.Polytropic_EOSs as ppeos

# Set neos = 1 (single polytrope)
neos = 1

# Set rho_poly_tab (not needed for a single polytrope)
rho_poly_tab = []

# Set Gamma_poly_tab
Gamma_poly_tab = [2.0]

# Set K_poly_tab0
K_poly_tab0 = 1. # ZACH NOTES: CHANGED FROM 100.

# Set the eos quantities
eos = ppeos.set_up_EOS_parameters__complete_set_of_input_variables(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab0)

import TOV.TOV_Solver as TOV
M_TOV, R_Schw_TOV, R_iso_TOV = TOV.TOV_Solver(eos,
                                              outfile="outputTOVpolytrope.txt",
                                              rho_baryon_central=0.129285,
                                              return_M_RSchw_and_Riso = True,
                                              verbose = True)

# domain_size sets the default value for:
#   * Spherical's params.RMAX
#   * SinhSpherical*'s params.AMAX
#   * Cartesians*'s -params.{x,y,z}min & .{x,y,z}max
#   * Cylindrical's -params.ZMIN & .{Z,RHO}MAX
#   * SinhCylindrical's params.AMPL{RHO,Z}
#   * *SymTP's params.AMAX
domain_size = 2.0 * R_iso_TOV1256 1256 1256 1256 1256 1256
Just generated a TOV star with
* M        = 1.405030336771405e-01 ,
* R_Schw   = 9.566044579232513e-01 ,
* R_iso    = 8.100085557410308e-01 ,
* M/R_Schw = 1.468768334847266e-01

## Step 2.a: Interpolate the TOV data file as needed to set up ADM spacetime quantities in spherical basis (for input into the `Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear` module) and $T^{\mu\nu}$ in the chosen reference metric basis \[Back to [top](#toc)\]
$$\label{tov_interp}$$

The TOV data file just written stored $\left(r,\rho(r),P(r),M(r),e^{\nu(r)}\right)$, where $\rho(r)$ is the total mass-energy density (cf. $\rho_{\text{baryonic}}$).

**METRIC DATA IN TERMS OF ADM QUANTITIES**

The [TOV line element](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) in *Schwarzschild coordinates* is written (in the $-+++$ form):
$$
ds^2 = - c^2 e^\nu dt^2 + \left(1 - \frac{2GM}{rc^2}\right)^{-1} dr^2 + r^2 d\Omega^2.
$$

In *isotropic coordinates* with $G=c=1$ (i.e., the coordinate system we'd prefer to use), the ($-+++$ form) line element is written:
$$
ds^2 = - e^{\nu} dt^2 + e^{4\phi} \left(d\bar{r}^2 + \bar{r}^2 d\Omega^2\right),
$$
where $\phi$ here is the *conformal factor*.

The ADM 3+1 line element for this diagonal metric in isotropic spherical coordinates is given by:
$$
ds^2 = (-\alpha^2 + \beta_k \beta^k) dt^2 + \gamma_{\bar{r}\bar{r}} d\bar{r}^2 + \gamma_{\theta\theta} d\theta^2+ \gamma_{\phi\phi} d\phi^2,
$$

from which we can immediately read off the ADM quantities:
\begin{align}
\alpha &= e^{\nu(\bar{r})/2} \\
\beta^k &= 0 \\
\gamma_{\bar{r}\bar{r}} &= e^{4\phi}\\
\gamma_{\theta\theta} &= e^{4\phi} \bar{r}^2 \\
\gamma_{\phi\phi} &= e^{4\phi} \bar{r}^2 \sin^2 \theta \\
\end{align}

**STRESS-ENERGY TENSOR $T^{\mu\nu}$**

We will also need the stress-energy tensor $T^{\mu\nu}$. [As discussed here](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation), the stress-energy tensor is diagonal:

\begin{align}
T^t_t &= -\rho \\
T^i_j &= P \delta^i_j \\
\text{All other components of }T^\mu_\nu &= 0.
\end{align}

Since $\beta^i=0$ the inverse metric expression simplifies to (Eq. 4.49 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf)):
$$
g^{\mu\nu} = \begin{pmatrix}
-\frac{1}{\alpha^2} & \frac{\beta^i}{\alpha^2} \\
\frac{\beta^i}{\alpha^2} & \gamma^{ij} - \frac{\beta^i\beta^j}{\alpha^2}
\end{pmatrix} =
\begin{pmatrix}
-\frac{1}{\alpha^2} & 0 \\
0 & \gamma^{ij}
\end{pmatrix},
$$

and since the 3-metric is diagonal we get

\begin{align}
\gamma^{\bar{r}\bar{r}} &= e^{-4\phi}\\
\gamma^{\theta\theta} &= e^{-4\phi}\frac{1}{\bar{r}^2} \\
\gamma^{\phi\phi} &= e^{-4\phi}\frac{1}{\bar{r}^2 \sin^2 \theta}.
\end{align}

Thus raising $T^\mu_\nu$ yields a diagonal $T^{\mu\nu}$

\begin{align}
T^{tt} &= -g^{tt} \rho = \frac{1}{\alpha^2} \rho = e^{-\nu(\bar{r})} \rho \\
T^{\bar{r}\bar{r}} &= g^{\bar{r}\bar{r}} P = \frac{1}{e^{4 \phi}} P \\
T^{\theta\theta} &= g^{\theta\theta} P = \frac{1}{e^{4 \phi}\bar{r}^2} P\\
T^{\phi\phi} &= g^{\phi\phi} P = \frac{1}{e^{4\phi}\bar{r}^2 \sin^2 \theta} P
\end{align}_____no_output_____

thismodule = "TOVID"
rbar,theta,rho,P,expnu,exp4phi = par.Cparameters("REAL",thismodule,
                                                 ["rbar","theta","rho","P","expnu","exp4phi"],1e300)
IDalpha = sp.sqrt(expnu)
gammaSphDD = ixp.zerorank2(DIM=3)
gammaSphDD[0][0] = exp4phi
gammaSphDD[1][1] = exp4phi*rbar**2
gammaSphDD[2][2] = exp4phi*rbar**2*sp.sin(theta)**2

T4SphUU = ixp.zerorank2(DIM=4)
T4SphUU[0][0] = rho/expnu
T4SphUU[1][1] = P/exp4phi
T4SphUU[2][2] = P/(exp4phi*rbar**2)
T4SphUU[3][3] = P/(exp4phi*rbar**2*sp.sin(theta)**2)_____no_output_____expr_list = [IDalpha]
name_list = ["*alpha"]
for i in range(3):
    for j in range(i,3):
        expr_list.append(gammaSphDD[i][j])
        name_list.append("*gammaDD"+str(i)+str(j))

desc = """This function takes as input either (x,y,z) or (r,th,ph) and outputs
all ADM quantities in the Cartesian or Spherical basis, respectively."""
name = "ID_TOV_ADM_quantities"
outCparams = "preindent=1,outCverbose=False,includebraces=False"

outCfunction(
    outfile=os.path.join(Ccodesdir, name + ".h"), desc=desc, name=name,
    params="""  const REAL xyz_or_rthph[3],

                const ID_inputs other_inputs,

                REAL *gammaDD00,REAL *gammaDD01,REAL *gammaDD02,REAL *gammaDD11,REAL *gammaDD12,REAL *gammaDD22,
                REAL *KDD00,REAL *KDD01,REAL *KDD02,REAL *KDD11,REAL *KDD12,REAL *KDD22,
                REAL *alpha,
                REAL *betaU0,REAL *betaU1,REAL *betaU2,
                REAL *BU0,REAL *BU1,REAL *BU2""",
    body="""
      // Set trivial metric quantities:
      *KDD00 = *KDD01 = *KDD02 = 0.0;
      /**/     *KDD11 = *KDD12 = 0.0;
      /**/              *KDD22 = 0.0;
      *betaU0 = *betaU1 = *betaU2 = 0.0;
      *BU0 = *BU1 = *BU2 = 0.0;

      // Next set gamma_{ij} in spherical basis
      const REAL rbar  = xyz_or_rthph[0];
      const REAL theta = xyz_or_rthph[1];
      const REAL phi   = xyz_or_rthph[2];

      REAL rho,rho_baryon,P,M,expnu,exp4phi;
      TOV_interpolate_1D(rbar,other_inputs.Rbar,other_inputs.Rbar_idx,other_inputs.interp_stencil_size,
                         other_inputs.numlines_in_file,
                         other_inputs.r_Schw_arr,other_inputs.rho_arr,other_inputs.rho_baryon_arr,other_inputs.P_arr,other_inputs.M_arr,
                         other_inputs.expnu_arr,other_inputs.exp4phi_arr,other_inputs.rbar_arr,
                         &rho,&rho_baryon,&P,&M,&expnu,&exp4phi);\n"""+
    outputC(expr_list,name_list, "returnstring",outCparams),
    opts="DisableCparameters")Output C function ID_TOV_ADM_quantities() to file TOVID_Ccodes/ID_TOV_ADM_quantities.h

As all input quantities are functions of $r$, we will simply read the solution from file and interpolate it to the values of $r$ needed by the initial data.

1. First we define functions `ID_TOV_ADM_quantities()` and `ID_TOV_TUPMUNU()` that call the [1D TOV interpolator function](../edit/TOV/tov_interp.h) to evaluate the ADM spacetime quantities and $T^{\mu\nu}$, respectively, at any given point $(r,\theta,\phi)$ in the Spherical basis. All quantities are defined as above.
1. Next we will construct the BSSN/ADM source terms $\{S_{ij},S_{i},S,\rho\}$ in the Spherical basis
1. Then we will perform the Jacobian transformation on $\{S_{ij},S_{i},S,\rho\}$ to the desired `(xx0,xx1,xx2)` basis
1. Next we call the *Numerical* Spherical ADM$\to$Curvilinear BSSN converter function to conver the above ADM quantities to the rescaled BSSN quantities in the desired curvilinear coordinate system: [BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py); [\[**tutorial**\]](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb).

$$
{\rm Jac\_dUSph\_dDrfmUD[mu][nu]} = \frac{\partial x^\mu_{\rm Sph}}{\partial x^\nu_{\rm rfm}},
$$

via exact differentiation (courtesy SymPy), and the inverse Jacobian
$$
{\rm Jac\_dUrfm\_dDSphUD[mu][nu]} = \frac{\partial x^\mu_{\rm rfm}}{\partial x^\nu_{\rm Sph}},
$$

using NRPy+'s `generic_matrix_inverter3x3()` function. In terms of these, the transformation of BSSN tensors from Spherical to `"reference_metric::CoordSystem"` coordinates may be written:

$$
T^{\mu\nu}_{\rm rfm} =
\frac{\partial x^\mu_{\rm rfm}}{\partial x^\delta_{\rm Sph}}
\frac{\partial x^\nu_{\rm rfm}}{\partial x^\sigma_{\rm Sph}} T^{\delta\sigma}_{\rm Sph}
$$_____no_output_____

r_th_ph_or_Cart_xyz_oID_xx = []
CoordType_in = "Spherical"
if CoordType_in == "Spherical":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxSph
elif CoordType_in == "Cartesian":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxCart
else:
    print("Error: Can only convert ADM Cartesian or Spherical initial data to BSSN Curvilinear coords.")
    exit(1)

# Next apply Jacobian transformations to convert into the (xx0,xx1,xx2) basis

# rho and S are scalar, so no Jacobian transformations are necessary.

Jac4_dUSphorCart_dDrfmUD = ixp.zerorank2(DIM=4)
Jac4_dUSphorCart_dDrfmUD[0][0] = sp.sympify(1)
for i in range(DIM):
    for j in range(DIM):
        Jac4_dUSphorCart_dDrfmUD[i+1][j+1] = sp.diff(r_th_ph_or_Cart_xyz_oID_xx[i],rfm.xx[j])

Jac4_dUrfm_dDSphorCartUD, dummyDET = ixp.generic_matrix_inverter4x4(Jac4_dUSphorCart_dDrfmUD)

# Perform Jacobian operations on T^{mu nu} and gamma_{ij}
T4UU = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","T4UU","sym01",DIM=4)

IDT4UU = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        for delta in range(4):
            for sigma in range(4):
                IDT4UU[mu][nu] += \
                     Jac4_dUrfm_dDSphorCartUD[mu][delta]*Jac4_dUrfm_dDSphorCartUD[nu][sigma]*T4SphUU[delta][sigma]

lhrh_list = []
for mu in range(4):
    for nu in range(mu,4):
        lhrh_list.append(lhrh(lhs=gri.gfaccess("auxevol_gfs","T4UU"+str(mu)+str(nu)),rhs=IDT4UU[mu][nu]))

desc = """This function takes as input either (x,y,z) or (r,th,ph) and outputs
all ADM quantities in the Cartesian or Spherical basis, respectively."""
name = "ID_TOV_TUPMUNU_xx0xx1xx2"
outCparams = "preindent=1,outCverbose=False,includebraces=False"
outCfunction(
    outfile=os.path.join(Ccodesdir, name + ".h"), desc=desc, name=name,
    params="""const paramstruct *restrict params,REAL *restrict xx[3],
              const ID_inputs other_inputs,REAL *restrict auxevol_gfs""",
    body=outputC([rfm.xxSph[0],rfm.xxSph[1],rfm.xxSph[2]],
                 ["const REAL rbar","const REAL theta","const REAL ph"],"returnstring",
                 "CSE_enable=False,includebraces=False")+"""
      REAL rho,rho_baryon,P,M,expnu,exp4phi;
      TOV_interpolate_1D(rbar,other_inputs.Rbar,other_inputs.Rbar_idx,other_inputs.interp_stencil_size,
                         other_inputs.numlines_in_file,
                         other_inputs.r_Schw_arr,other_inputs.rho_arr,other_inputs.rho_baryon_arr,other_inputs.P_arr,other_inputs.M_arr,
                         other_inputs.expnu_arr,other_inputs.exp4phi_arr,other_inputs.rbar_arr,
                         &rho,&rho_baryon,&P,&M,&expnu,&exp4phi);\n"""+
    fin.FD_outputC("returnstring",lhrh_list,params="outCverbose=False,includebraces=False").replace("IDX4","IDX4S"),
    loopopts="AllPoints,Read_xxs")Output C function ID_TOV_TUPMUNU_xx0xx1xx2() to file TOVID_Ccodes/ID_TOV_TUPMUNU_xx0xx1xx2.h

# Step 3: Convert ADM initial data to BSSN-in-curvilinear coordinates \[Back to [top](#toc)\]
$$\label{adm_id_spacetime}$$

This is an automated process, taken care of by [`BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear`](../edit/BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py), and documented [in this tutorial notebook](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb)._____no_output_____

import BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as AtoBnum
AtoBnum.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Spherical","ID_TOV_ADM_quantities",
                                                               Ccodesdir=Ccodesdir,loopopts="")Output C function ID_BSSN_lambdas() to file TOVID_Ccodes/ID_BSSN_lambdas.h
Output C function ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs() to file TOVID_Ccodes/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h
Output C function ID_BSSN__ALL_BUT_LAMBDAs() to file TOVID_Ccodes/ID_BSSN__ALL_BUT_LAMBDAs.h

# Step 4: Validating that the TOV initial data satisfy the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{validate}$$

We will validate that the TOV initial data satisfy the Hamiltonian constraint, modulo numerical finite differencing error_____no_output_____

## Step 4.a: Output the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{ham_const_output}$$

First output the Hamiltonian constraint [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN_constraints.ipynb)_____no_output_____

# Enable rfm_precompute infrastructure, which results in
#   BSSN RHSs that are free of transcendental functions,
#   even in curvilinear coordinates, so long as
#   ConformalFactor is set to "W" (default).
cmd.mkdir(os.path.join(Ccodesdir,"rfm_files/"))
par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(Ccodesdir,"rfm_files/"))

import BSSN.Enforce_Detgammabar_Constraint as EGC
enforce_detg_constraint_symb_expressions = EGC.Enforce_Detgammabar_Constraint_symb_expressions()

# Now register the Hamiltonian as a gridfunction.
H = gri.register_gridfunctions("AUX","H")
# Then define the Hamiltonian constraint and output the optimized C code.
import BSSN.BSSN_constraints as bssncon
import BSSN.BSSN_stress_energy_source_terms as Bsest
bssncon.BSSN_constraints(add_T4UUmunu_source_terms=False)
Bsest.BSSN_source_terms_for_BSSN_constraints(T4UU)
bssncon.H += Bsest.sourceterm_H

# Now that we are finished with all the rfm hatted
#           quantities in generic precomputed functional
#           form, let's restore them to their closed-
#           form expressions.
par.set_parval_from_str("reference_metric::enable_rfm_precompute","False") # Reset to False to disable rfm_precompute.
rfm.ref_metric__hatted_quantities()

desc="Evaluate the Hamiltonian constraint"
name="Hamiltonian_constraint"
outCfunction(
    outfile  = os.path.join(Ccodesdir,name+".h"), desc=desc, name=name,
    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  REAL *restrict in_gfs, REAL *restrict auxevol_gfs, REAL *restrict aux_gfs""",
    body     = fin.FD_outputC("returnstring",lhrh(lhs=gri.gfaccess("aux_gfs", "H"), rhs=bssncon.H),
                              params="outCverbose=False").replace("IDX4","IDX4S"),
    loopopts = "InteriorPoints,Enable_rfm_precompute")Output C function Hamiltonian_constraint() to file TOVID_Ccodes/Hamiltonian_constraint.h

## Step 4.b: Set up boundary condition functions for chosen singular, curvilinear coordinate system \[Back to [top](#toc)\]
$$\label{bc_functs}$$

Next apply singular, curvilinear coordinate boundary conditions [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb)_____no_output_____

import CurviBoundaryConditions.CurviBoundaryConditions as cbcs
cbcs.Set_up_CurviBoundaryConditions(os.path.join(Ccodesdir,"boundary_conditions/"),Cparamspath=os.path.join("../"))Wrote to file "TOVID_Ccodes/boundary_conditions/parity_conditions_symbolic_dot_products.h"
Evolved gridfunction "aDD00" has parity type 4.
Evolved gridfunction "aDD01" has parity type 5.
Evolved gridfunction "aDD02" has parity type 6.
Evolved gridfunction "aDD11" has parity type 7.
Evolved gridfunction "aDD12" has parity type 8.
Evolved gridfunction "aDD22" has parity type 9.
Evolved gridfunction "alpha" has parity type 0.
Evolved gridfunction "betU0" has parity type 1.
Evolved gridfunction "betU1" has parity type 2.
Evolved gridfunction "betU2" has parity type 3.
Evolved gridfunction "cf" has parity type 0.
Evolved gridfunction "hDD00" has parity type 4.
Evolved gridfunction "hDD01" has parity type 5.
Evolved gridfunction "hDD02" has parity type 6.
Evolved gridfunction "hDD11" has parity type 7.
Evolved gridfunction "hDD12" has parity type 8.
Evolved gridfunction "hDD22" has parity type 9.
Evolved gridfunction "lambdaU0" has parity type 1.
Evolved gridfunction "lambdaU1" has parity type 2.
Evolved gridfunction "lambdaU2" has parity type 3.
Evolved gridfunction "trK" has parity type 0.
Evolved gridfunction "vetU0" has parity type 1.
Evolved gridfunction "vetU1" has parity type 2.
Evolved gridfunction "vetU2" has parity type 3.
Auxiliary gridfunction "H" has parity type 0.
AuxEvol gridfunction "T4UU00" has parity type 0.
AuxEvol gridfunction "T4UU01" has parity type 1.
AuxEvol gridfunction "T4UU02" has parity type 2.
AuxEvol gridfunction "T4UU03" has parity type 3.
AuxEvol gridfunction "T4UU11" has parity type 4.
AuxEvol gridfunction "T4UU12" has parity type 5.
AuxEvol gridfunction "T4UU13" has parity type 6.
AuxEvol gridfunction "T4UU22" has parity type 7.
AuxEvol gridfunction "T4UU23" has parity type 8.
AuxEvol gridfunction "T4UU33" has parity type 9.
Wrote to file "TOVID_Ccodes/boundary_conditions/EigenCoord_Cart_to_xx.h"

## Step 4.c: Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint \[Back to [top](#toc)\]
$$\label{enforce3metric}$$

Then enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint (Eq. 53 of [Ruchlin, Etienne, and Baumgarte (2018)](https://arxiv.org/abs/1712.07658)), as [documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN-Enforcing_Determinant_gammabar_equals_gammahat_Constraint.ipynb)

Applying curvilinear boundary conditions should affect the initial data at the outer boundary, and will in general cause the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint to be violated there. Thus after we apply these boundary conditions, we must always call the routine for enforcing the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint:_____no_output_____

# Set up the C function for the det(gammahat) = det(gammabar)
EGC.output_Enforce_Detgammabar_Constraint_Ccode(Ccodesdir,
                                                exprs=enforce_detg_constraint_symb_expressions)Output C function enforce_detgammabar_constraint() to file TOVID_Ccodes/enforce_detgammabar_constraint.h

## Step 4.d: Output C codes needed for declaring and setting Cparameters; also set `free_parameters.h` \[Back to [top](#toc)\]
$$\label{cparams_rfm_and_domainsize}$$

Based on declared NRPy+ Cparameters, first we generate `declare_Cparameters_struct.h`, `set_Cparameters_default.h`, and `set_Cparameters[-SIMD].h`.

Then we output `free_parameters.h`, which sets initial data parameters, as well as grid domain & reference metric parameters, applying `domain_size` and `sinh_width`/`SymTP_bScale` (if applicable) as set above_____no_output_____

# Step 3.d.i: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))

# Step 3.d.ii: Set free_parameters.h
# Output to $Ccodesdir/free_parameters.h reference metric parameters based on generic
#    domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale,
#    parameters set above.
rfm.out_default_free_parameters_for_rfm(os.path.join(Ccodesdir,"free_parameters.h"),
                                        domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale)

# Step 3.d.iii: Generate set_Nxx_dxx_invdx_params__and__xx.h:
rfm.set_Nxx_dxx_invdx_params__and__xx_h(Ccodesdir)

# Step 3.d.iv: Generate xxCart.h, which contains xxCart() for
#               (the mapping from xx->Cartesian) for the chosen
#               CoordSystem:
rfm.xxCart_h("xxCart","./set_Cparameters.h",os.path.join(Ccodesdir,"xxCart.h"))

# Step 3.d.v: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))_____no_output_____

# Step 5: `TOV_Playground.c`: The Main C Code \[Back to [top](#toc)\]
$$\label{mainc}$$
_____no_output_____

# Part P0: Define REAL, set the number of ghost cells NGHOSTS (from NRPy+'s FD_CENTDERIVS_ORDER)

with open(os.path.join(Ccodesdir,"TOV_Playground_REAL__NGHOSTS.h"), "w") as file:
    file.write("""
// Part P0.a: Set the number of ghost cells, from NRPy+'s FD_CENTDERIVS_ORDER
#define NGHOSTS """+str(int(FD_order/2)+1)+"""
// Part P0.b: Set the numerical precision (REAL) to double, ensuring all floating point
//            numbers are stored to at least ~16 significant digits
#define REAL """+REAL+"""
// Part P0.c: Set TOV stellar parameters
#define TOV_Mass  """+str(M_TOV)+"""
#define TOV_Riso  """+str(R_iso_TOV)+"\n")_____no_output_____%%writefile $Ccodesdir/TOV_Playground.c

// Step P0: Define REAL and NGHOSTS. This header is generated by NRPy+.
#include "TOV_Playground_REAL__NGHOSTS.h"

#include "rfm_files/rfm_struct__declare.h"

#include "declare_Cparameters_struct.h"

// Step P1: Import needed header files
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#ifndef M_PI
#define M_PI 3.141592653589793238462643383279502884L
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039L
#endif

// Step P2: Declare the IDX4S(gf,i,j,k) macro, which enables us to store 4-dimensions of
//           data in a 1D array. In this case, consecutive values of "i"
//           (all other indices held to a fixed value) are consecutive in memory, where
//           consecutive values of "j" (fixing all other indices) are separated by
//           Nxx_plus_2NGHOSTS0 elements in memory. Similarly, consecutive values of
//           "k" are separated by Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1 in memory, etc.
#define IDX4S(g,i,j,k) \
( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) + Nxx_plus_2NGHOSTS2 * (g) ) ) )
#define IDX4ptS(g,idx) ( (idx) + (Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2) * (g) )
#define IDX3S(i,j,k) ( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) ) ) )
#define LOOP_REGION(i0min,i0max, i1min,i1max, i2min,i2max) \
  for(int i2=i2min;i2<i2max;i2++) for(int i1=i1min;i1<i1max;i1++) for(int i0=i0min;i0<i0max;i0++)
#define LOOP_ALL_GFS_GPS(ii) _Pragma("omp parallel for") \
  for(int (ii)=0;(ii)<Nxx_plus_2NGHOSTS_tot*NUM_EVOL_GFS;(ii)++)

// Step P3: Set UUGF and VVGF macros, as well as xxCart()
#include "boundary_conditions/gridfunction_defines.h"

// Step P4: Set xxCart(const paramstruct *restrict params,
//                     REAL *restrict xx[3],
//                     const int i0,const int i1,const int i2,
//                     REAL xCart[3]),
//           which maps xx->Cartesian via
//    {xx[0][i0],xx[1][i1],xx[2][i2]}->{xCart[0],xCart[1],xCart[2]}
#include "xxCart.h"

// Step P5: Defines set_Nxx_dxx_invdx_params__and__xx(const int EigenCoord, const int Nxx[3],
//                                       paramstruct *restrict params, REAL *restrict xx[3]),
//          which sets params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for
//          the chosen Eigen-CoordSystem if EigenCoord==1, or
//          CoordSystem if EigenCoord==0.
#include "set_Nxx_dxx_invdx_params__and__xx.h"

// Step P6: Include basic functions needed to impose curvilinear
//          parity and boundary conditions.
#include "boundary_conditions/CurviBC_include_Cfunctions.h"

// Step P8: Include function for enforcing detgammabar constraint.
#include "enforce_detgammabar_constraint.h"

// Step P4: Declare initial data input struct:
//          stores data from initial data solver,
//          so they can be put on the numerical grid.
typedef struct __ID_inputs {
    REAL Rbar;
    int Rbar_idx;
    int interp_stencil_size;
    int numlines_in_file;
    REAL *r_Schw_arr,*rho_arr,*rho_baryon_arr,*P_arr,*M_arr,*expnu_arr,*exp4phi_arr,*rbar_arr;
} ID_inputs;

// Part P11: Declare all functions for setting up TOV initial data.
/* Routines to interpolate the TOV solution and convert to ADM & T^{munu}: */
#include "../TOV/tov_interp.h"
#include "ID_TOV_ADM_quantities.h"
#include "ID_TOV_TUPMUNU_xx0xx1xx2.h"

/* Next perform the basis conversion and compute all needed BSSN quantities */
#include "ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN_lambdas.h"

// Step P10: Declare function necessary for setting up the initial data.
// Step P10.a: Define BSSN_ID() for BrillLindquist initial data

// Step P10.b: Set the generic driver function for setting up BSSN initial data
void initial_data(const paramstruct *restrict params,const bc_struct *restrict bcstruct,
                  const rfm_struct *restrict rfmstruct,
                  REAL *restrict xx[3], REAL *restrict auxevol_gfs, REAL *restrict in_gfs) {
#include "set_Cparameters.h"
        // Step 1: Set up TOV initial data
    // Step 1.a: Read TOV initial data from data file
    // Open the data file:
    char filename[100];
    sprintf(filename,"./outputTOVpolytrope.txt");
    FILE *in1Dpolytrope = fopen(filename, "r");
    if (in1Dpolytrope == NULL) {
        fprintf(stderr,"ERROR: could not open file %s\n",filename);
        exit(1);
    }
    // Count the number of lines in the data file:
    int numlines_in_file = count_num_lines_in_file(in1Dpolytrope);
    // Allocate space for all data arrays:
    REAL *r_Schw_arr     = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rho_arr        = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rho_baryon_arr = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *P_arr          = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *M_arr          = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *expnu_arr      = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *exp4phi_arr    = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rbar_arr       = (REAL *)malloc(sizeof(REAL)*numlines_in_file);

    // Read from the data file, filling in arrays
    // read_datafile__set_arrays() may be found in TOV/tov_interp.h
    if(read_datafile__set_arrays(in1Dpolytrope, r_Schw_arr,rho_arr,rho_baryon_arr,P_arr,M_arr,expnu_arr,exp4phi_arr,rbar_arr) == 1) {
        fprintf(stderr,"ERROR WHEN READING FILE %s!\n",filename);
        exit(1);
    }
    fclose(in1Dpolytrope);
    REAL Rbar = -100;
    int Rbar_idx = -100;
    for(int i=1;i<numlines_in_file;i++) {
        if(rho_arr[i-1]>0 && rho_arr[i]==0) { Rbar = rbar_arr[i-1]; Rbar_idx = i-1; }
    }
    if(Rbar<0) {
        fprintf(stderr,"Error: could not find rbar=Rbar from data file.\n");
        exit(1);
    }

    ID_inputs TOV_in;
    TOV_in.Rbar = Rbar;
    TOV_in.Rbar_idx = Rbar_idx;

    const int interp_stencil_size = 12;
    TOV_in.interp_stencil_size = interp_stencil_size;
    TOV_in.numlines_in_file = numlines_in_file;

    TOV_in.r_Schw_arr     = r_Schw_arr;
    TOV_in.rho_arr        = rho_arr;
    TOV_in.rho_baryon_arr = rho_baryon_arr;
    TOV_in.P_arr          = P_arr;
    TOV_in.M_arr          = M_arr;
    TOV_in.expnu_arr      = expnu_arr;
    TOV_in.exp4phi_arr    = exp4phi_arr;
    TOV_in.rbar_arr       = rbar_arr;
    /* END TOV INPUT ROUTINE */

    // Step 1.b: Interpolate data from data file to set BSSN gridfunctions
    ID_BSSN__ALL_BUT_LAMBDAs(params,xx,TOV_in, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);
    ID_BSSN_lambdas(params, xx, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);

    ID_TOV_TUPMUNU_xx0xx1xx2(params,xx,TOV_in,auxevol_gfs);

    free(rbar_arr);
    free(rho_arr);
    free(rho_baryon_arr);
    free(P_arr);
    free(M_arr);
    free(expnu_arr);
}

// Step P11: Declare function for evaluating Hamiltonian constraint (diagnostic)
#include "Hamiltonian_constraint.h"

// main() function:
// Step 0: Read command-line input, set up grid structure, allocate memory for gridfunctions, set up coordinates
// Step 1: Set up initial data to an exact solution
// Step 2: Start the timer, for keeping track of how fast the simulation is progressing.
// Step 3: Integrate the initial data forward in time using the chosen RK-like Method of
//         Lines timestepping algorithm, and output periodic simulation diagnostics
// Step 3.a: Output 2D data file periodically, for visualization
// Step 3.b: Step forward one timestep (t -> t+dt) in time using
//           chosen RK-like MoL timestepping algorithm
// Step 3.c: If t=t_final, output conformal factor & Hamiltonian
//           constraint violation to 2D data file
// Step 3.d: Progress indicator printing to stderr
// Step 4: Free all allocated memory
int main(int argc, const char *argv[]) {
    paramstruct params;
#include "set_Cparameters_default.h"

    // Step 0a: Read command-line input, error out if nonconformant
    if((argc != 4) || atoi(argv[1]) < NGHOSTS || atoi(argv[2]) < NGHOSTS || atoi(argv[3]) < 2 /* FIXME; allow for axisymmetric sims */) {
        fprintf(stderr,"Error: Expected three command-line arguments: ./BrillLindquist_Playground Nx0 Nx1 Nx2,\n");
        fprintf(stderr,"where Nx[0,1,2] is the number of grid points in the 0, 1, and 2 directions.\n");
        fprintf(stderr,"Nx[] MUST BE larger than NGHOSTS (= %d)\n",NGHOSTS);
        exit(1);
    }
    // Step 0b: Set up numerical grid structure, first in space...
    const int Nxx[3] = { atoi(argv[1]), atoi(argv[2]), atoi(argv[3]) };
    if(Nxx[0]%2 != 0 || Nxx[1]%2 != 0 || Nxx[2]%2 != 0) {
        fprintf(stderr,"Error: Cannot guarantee a proper cell-centered grid if number of grid cells not set to even number.\n");
        fprintf(stderr,"       For example, in case of angular directions, proper symmetry zones will not exist.\n");
        exit(1);
    }

    // Step 0c: Set free parameters, overwriting Cparameters defaults
    //          by hand or with command-line input, as desired.
#include "free_parameters.h"

   // Step 0d: Uniform coordinate grids are stored to *xx[3]
    REAL *xx[3];
    // Step 0d.i: Set bcstruct
    bc_struct bcstruct;
    {
        int EigenCoord = 1;
        // Step 0d.ii: Call set_Nxx_dxx_invdx_params__and__xx(), which sets
        //             params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for the
        //             chosen Eigen-CoordSystem.
        set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);
        // Step 0d.iii: Set Nxx_plus_2NGHOSTS_tot
#include "set_Cparameters-nopointer.h"
        const int Nxx_plus_2NGHOSTS_tot = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;
        // Step 0e: Find ghostzone mappings; set up bcstruct
#include "boundary_conditions/driver_bcstruct.h"
        // Step 0e.i: Free allocated space for xx[][] array
        for(int i=0;i<3;i++) free(xx[i]);
    }

    // Step 0f: Call set_Nxx_dxx_invdx_params__and__xx(), which sets
    //          params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for the
    //          chosen (non-Eigen) CoordSystem.
    int EigenCoord = 0;
    set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);

    // Step 0g: Set all C parameters "blah" for params.blah, including
    //          Nxx_plus_2NGHOSTS0 = params.Nxx_plus_2NGHOSTS0, etc.
#include "set_Cparameters-nopointer.h"
    const int Nxx_plus_2NGHOSTS_tot = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;

    // Step 0j: Error out if the number of auxiliary gridfunctions outnumber evolved gridfunctions.
    //              This is a limitation of the RK method. You are always welcome to declare & allocate
    //              additional gridfunctions by hand.
    if(NUM_AUX_GFS > NUM_EVOL_GFS) {
        fprintf(stderr,"Error: NUM_AUX_GFS > NUM_EVOL_GFS. Either reduce the number of auxiliary gridfunctions,\n");
        fprintf(stderr,"       or allocate (malloc) by hand storage for *diagnostic_output_gfs. \n");
        exit(1);
    }

    // Step 0k: Allocate memory for gridfunctions
#include "MoLtimestepping/RK_Allocate_Memory.h"
    REAL *restrict auxevol_gfs = (REAL *)malloc(sizeof(REAL) * NUM_AUXEVOL_GFS * Nxx_plus_2NGHOSTS_tot);

    // Step 0l: Set up precomputed reference metric arrays
    // Step 0l.i: Allocate space for precomputed reference metric arrays.
#include "rfm_files/rfm_struct__malloc.h"

    // Step 0l.ii: Define precomputed reference metric arrays.
    {
    #include "set_Cparameters-nopointer.h"
    #include "rfm_files/rfm_struct__define.h"
    }

    // Step 1: Set up initial data to an exact solution
    initial_data(&params,&bcstruct, &rfmstruct, xx, auxevol_gfs, y_n_gfs);

    // Step 1b: Apply boundary conditions, as initial data
    //          are sometimes ill-defined in ghost zones.
    //          E.g., spherical initial data might not be
    //          properly defined at points where r=-1.
    apply_bcs_curvilinear(&params, &bcstruct, NUM_EVOL_GFS,evol_gf_parity, y_n_gfs);
    enforce_detgammabar_constraint(&rfmstruct, &params, y_n_gfs);

    // Evaluate Hamiltonian constraint violation
    Hamiltonian_constraint(&rfmstruct, &params, y_n_gfs,auxevol_gfs, diagnostic_output_gfs);
    char filename[100];
    sprintf(filename,"out%d.txt",Nxx[0]);
    FILE *out2D = fopen(filename, "w");
    LOOP_REGION(NGHOSTS,Nxx_plus_2NGHOSTS0-NGHOSTS,
                NGHOSTS,Nxx_plus_2NGHOSTS1-NGHOSTS,
                NGHOSTS,Nxx_plus_2NGHOSTS2-NGHOSTS) {
        REAL xx0 = xx[0][i0];
        REAL xx1 = xx[1][i1];
        REAL xx2 = xx[2][i2];
        REAL xCart[3];
        xxCart(&params,xx,i0,i1,i2,xCart);
        int idx = IDX3S(i0,i1,i2);
        fprintf(out2D,"%e %e %e %e\n",xCart[1]/TOV_Mass,xCart[2]/TOV_Mass, y_n_gfs[IDX4ptS(CFGF,idx)],
                log10(fabs(diagnostic_output_gfs[IDX4ptS(HGF,idx)])));
    }
    fclose(out2D);

    // Step 4: Free all allocated memory
#include "rfm_files/rfm_struct__freemem.h"
#include "boundary_conditions/bcstruct_freemem.h"
#include "MoLtimestepping/RK_Free_Memory.h"
    free(auxevol_gfs);
    for(int i=0;i<3;i++) free(xx[i]);

    return 0;
}Writing TOVID_Ccodes//TOV_Playground.c
import cmdline_helper as cmd
cmd.C_compile(os.path.join(Ccodesdir,"TOV_Playground.c"), "TOV_Playground")
cmd.delete_existing_files("out96.txt")
cmd.Execute("TOV_Playground", "96 96 2", "out96.txt")Compiling executable...
Executing `gcc -Ofast -fopenmp -march=native -funroll-loops TOVID_Ccodes/TOV_Playground.c -o TOV_Playground -lm`...
Finished executing in 2.015650510787964 seconds.
Finished compilation.
Executing `taskset -c 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ./TOV_Playground 96 96 2`...
Finished executing in 0.2118375301361084 seconds.

# Step 6: Plotting the single-neutron-star initial data \[Back to [top](#toc)\]
$$\label{plot}$$

Here we plot the conformal factor of these initial data on a 2D grid, such that darker colors imply stronger gravitational fields. Hence, we see the single neutron star centered at the origin: $x/M=y/M=z/M=0$, where $M$ is an arbitrary mass scale (conventionally the [ADM mass](https://en.wikipedia.org/w/index.php?title=ADM_formalism&oldid=846335453) is chosen), and our formulation of Einstein's equations adopt $G=c=1$ [geometrized units](https://en.wikipedia.org/w/index.php?title=Geometrized_unit_system&oldid=861682626)._____no_output_____

import numpy as np
from scipy.interpolate import griddata
from pylab import savefig
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from IPython.display import Image

x96,y96,valuesCF96,valuesHam96 = np.loadtxt('out96.txt').T #Transposed for easier unpacking

bounds = 7.5
pl_xmin = -bounds
pl_xmax = +bounds
pl_ymin = -bounds
pl_ymax = +bounds

grid_x, grid_y = np.mgrid[pl_xmin:pl_xmax:100j, pl_ymin:pl_ymax:100j]
points96 = np.zeros((len(x96), 2))
for i in range(len(x96)):
    points96[i][0] = x96[i]
    points96[i][1] = y96[i]

grid96 = griddata(points96, valuesCF96, (grid_x, grid_y), method='nearest')
grid96cub = griddata(points96, valuesCF96, (grid_x, grid_y), method='cubic')

plt.clf()
plt.title("Neutron Star: log10( max(1e-6,Energy Density) )")
plt.xlabel("x/M")
plt.ylabel("y/M")

# fig, ax = plt.subplots()
# ax.plot(grid96cub.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
# plt.close(fig)
fig96cf = plt.imshow(grid96.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
cb = plt.colorbar(fig96cf)
savefig("BHB.png")
from IPython.display import Image
Image("BHB.png")
# #           interpolation='nearest', cmap=cm.gist_rainbow)_____no_output_____

# Step 7: Validation: Convergence of numerical errors (Hamiltonian constraint violation) to zero \[Back to [top](#toc)\]
$$\label{convergence}$$

The equations behind these initial data solve Einstein's equations exactly, at a single instant in time. One reflection of this solution is that the Hamiltonian constraint violation should be exactly zero in the initial data.

However, when evaluated on numerical grids, the Hamiltonian constraint violation will *not* generally evaluate to zero due to the associated numerical derivatives not being exact. However, these numerical derivatives (finite difference derivatives in this case) should *converge* to the exact derivatives as the density of numerical sampling points approaches infinity.

In this case, all of our finite difference derivatives agree with the exact solution, with an error term that drops with the uniform gridspacing to the fourth power: $\left(\Delta x^i\right)^4$.

Here, as in the [Start-to-Finish Scalar Wave (Cartesian grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWave.ipynb) and the [Start-to-Finish Scalar Wave (curvilinear grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWaveCurvilinear.ipynb) we confirm this convergence.

First, let's take a look at what the numerical error looks like on the x-y plane at a given numerical resolution, plotting $\log_{10}|H|$, where $H$ is the Hamiltonian constraint violation:_____no_output_____

grid96 = griddata(points96, valuesHam96, (grid_x, grid_y), method='nearest')
grid96cub = griddata(points96, valuesHam96, (grid_x, grid_y), method='cubic')

# fig, ax = plt.subplots()

plt.clf()
plt.title("96^3 Numerical Err.: log_{10}|Ham|")
plt.xlabel("x/M")
plt.ylabel("y/M")

fig96cub = plt.imshow(grid96cub.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
cb = plt.colorbar(fig96cub)_____no_output_____

Next, we set up the same initial data but on a lower-resolution, $48\times 8\times 2$ grid (axisymmetric in the $\phi$ direction). Since the constraint violation (numerical error associated with the fourth-order-accurate, finite-difference derivatives) should converge to zero with the uniform gridspacing to the fourth power: $\left(\Delta x^i\right)^4$, we expect the constraint violation will increase (relative to the $96\times 16\times 2$ grid) by a factor of $\left(96/48\right)^4$. Here we demonstrate that indeed this order of convergence is observed as expected, *except* at the star's surface where the stress-energy tensor $T^{\mu\nu}$ sharply drops to zero._____no_output_____

# Now rerun TOV_Playground with twice lower resolution.
cmd.delete_existing_files("out48.txt")
cmd.Execute("TOV_Playground", "48 48 2", "out48.txt")

x48,y48,valuesCF48,valuesHam48 = np.loadtxt('out48.txt').T #Transposed for easier unpacking
points48 = np.zeros((len(x48), 2))
for i in range(len(x48)):
    points48[i][0] = x48[i]
    points48[i][1] = y48[i]

grid48 = griddata(points48, valuesHam48, (grid_x, grid_y), method='cubic')

griddiff_48_minus_96 = np.zeros((100,100))
griddiff_48_minus_96_1darray = np.zeros(100*100)
gridx_1darray_yeq0 = np.zeros(100)
grid48_1darray_yeq0 = np.zeros(100)
grid96_1darray_yeq0 = np.zeros(100)
count = 0
outarray = []
for i in range(100):
    for j in range(100):
        griddiff_48_minus_96[i][j] = grid48[i][j] - grid96[i][j]
        griddiff_48_minus_96_1darray[count] = griddiff_48_minus_96[i][j]
        if j==49:
            gridx_1darray_yeq0[i] = grid_x[i][j]
            grid48_1darray_yeq0[i] = grid48[i][j] + np.log10((48./96.)**4)
            grid96_1darray_yeq0[i] = grid96[i][j]
        count = count + 1

plt.clf()
fig, ax = plt.subplots()
plt.title("Plot Demonstrating 4th-order Convergence")
plt.xlabel("x/M")
plt.ylabel("log10(Relative error)")

ax.plot(gridx_1darray_yeq0, grid96_1darray_yeq0, 'k-', label='Nr=96')
ax.plot(gridx_1darray_yeq0, grid48_1darray_yeq0, 'k--', label='Nr=48, mult by (48/96)^4')
ax.set_ylim([-12.5,1.5])

legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')
legend.get_frame().set_facecolor('C1')
plt.show()Executing `taskset -c 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ./TOV_Playground 48 48 2`...
Finished executing in 0.21308159828186035 seconds.

# Step 8: Output this notebook to $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{latex_pdf_output}$$

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
[Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.pdf](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)_____no_output_____

!jupyter nbconvert --to latex --template latex_nrpy_style.tplx --log-level='WARN' Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb
!pdflatex -interaction=batchmode Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.tex
!pdflatex -interaction=batchmode Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.tex
!pdflatex -interaction=batchmode Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.tex
!rm -f Tut*.out Tut*.aux Tut*.logThis is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode


# Notebook from danjaq/weather-mn-phenology
Path: etl.ipynb

# Weather Enhanced MN Phenology Dataset
Combines phenology and weather data at the county level for the state of Minnesota._____no_output_____

import pandas as pd
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import DoubleType, DateType

INPUT_FOLDER = "data/"
OUTPUT_FOLDER = "out/"
CLIMHEADER=["FIPS-YEAR","JAN","FEB","MAR","APR","MAY","JUN","JUL",\
            "AUG","SEP","OCT","NOV","DEC"]_____no_output_____

## Phenology and County Codes
Done together since FIPS codes aid with phenology data cleanup_____no_output_____### Data clean up for phenology data
- remove empty counties
- drop duplicates
- align multiple spellings
- correct abbreviations, etc..._____no_output_____County Changes to make based off of mismatched county data
- "ASHLAND" county is in WI, remove since it's out of scope for this project
- "RAMSEY " has an extra space to take out
- "ST.LOUIS" and "ST. LOUIS" should be "ST LOUIS"
- "BR" is probably "Brown", it's the only "Br*" county and there are no counties with "BR" as initials
- "MORRISON COUNTY" should just be "MORRISON"
- "AITKIN/ITASCA" & "AITKIN /ITASCA" could be either one, remove for sake of cleanliness_____no_output_____

def processPhenology(spark, phenology):
    """
    Takes a spark session and the data to be read in, performing some data cleanup.
    """
    # read in phenology
    mnphn_df = spark.read.option("header", True) \
        .csv(INPUT_FOLDER + phenology)
    # Data clean up
    mnphn_df = mnphn_df.filter("COUNTY is not NULL").dropDuplicates()
    mnphn_df = mnphn_df.filter("COUNTY not in ('ASHLAND', 'AITKIN /ITASCA', 'AITKIN/ITASCA')")
    mnphn_df = mnphn_df.withColumn("COUNTY", when(mnphn_df.COUNTY == "RAMSEY ","RAMSEY")
                    .when(mnphn_df.COUNTY == "ST.LOUIS","ST LOUIS")
                    .when(mnphn_df.COUNTY == "ST. LOUIS","ST LOUIS")
                    .when(mnphn_df.COUNTY == "BR","BROWN")
                    .when(mnphn_df.COUNTY == "MORRISON COUNTY","MORRISON")
                    .otherwise(mnphn_df.COUNTY))
    return mnphn_df_____no_output_____def processCounties(spark, counties):
    """
    Takes a spark session and the input data for counties and returns a data frame.
    """
    fips_df = spark.read.json(INPUT_FOLDER + counties, multiLine=True)
    # filter to minnesota for coutnies
    mnfips_df = fips_df.filter(fips_df.State == "MN")
    return mnfips_df_____no_output_____

#### Data Quality Check
Compare phenology data's counties to FIPS codes to determine invalid county names. Zero mismatches is a pass._____no_output_____

def chkPhnCntMismatch(mnphn_df, fips_df):
    mismatches = mnphn_df.join(mnfips_df, lower(mnphn_df.COUNTY) == lower(mnfips_df.Name), "leftanti") \
        .select("COUNTY").distinct().count()
    if mismatches == 0:
        return True
    else:
        return False_____no_output_____

#### Climate Data
Percipitation and temperatures per county and month.

For perciptation, range of values is listed as 00.00 to 99.99. "-9.99" indicates no reading, so should be null. For temperature, the range is -50 to 140 and -99.99 indicates no reading._____no_output_____

@udf(returnType=DoubleType())
def noRead2null(obsv, nullVal):
    '''
    When percipitation is the "no reading" value it should be null
    '''
    if obsv == nullVal:
        return None
    else:
        return obsv_____no_output_____def processClim(spark, clim, noReadVal):
    """
    Takes in the spark session, input climate file, and no reading value,
    and and returns a data frame.
    """
    # Climate tables have a variable amount of spaces as their delimiters
    # Pandas can handle this where pyspark can't, so use that first
    clim_pd = pd.read_csv(INPUT_FOLDER + clim, \
                         sep="\s+",\
                         names=CLIMHEADER)
    clim_df = spark.createDataFrame(clim_pd)
    nullVal = lit(noReadVal)
    clim_df = clim_df.withColumn("JAN", noRead2null(col("JAN"), nullVal))\
    .withColumn("FEB", noRead2null(col("FEB"), nullVal))\
    .withColumn("MAR", noRead2null(col("MAR"), nullVal))\
    .withColumn("APR", noRead2null(col("APR"), nullVal))\
    .withColumn("MAY", noRead2null(col("MAY"), nullVal))\
    .withColumn("JUN", noRead2null(col("JUN"), nullVal))\
    .withColumn("JUL", noRead2null(col("JUL"), nullVal))\
    .withColumn("AUG", noRead2null(col("AUG"), nullVal))\
    .withColumn("SEP", noRead2null(col("SEP"), nullVal))\
    .withColumn("OCT", noRead2null(col("OCT"), nullVal))\
    .withColumn("NOV", noRead2null(col("NOV"), nullVal))\
    .withColumn("DEC", noRead2null(col("DEC"), nullVal))
    return clim_df_____no_output_____

#### Data Quality Check
For perciptation, range of values is listed as 00.00 to 99.99, so make sure we don't have any invalid numbers. We can use this same function for temperature as well._____no_output_____

def chkClimRange(df, low, high):
    """
    Takes a climate data frame and low and high values and checks for invalid entries.
    """
    invalids = df.filter("""JAN NOT BETWEEN {lo} AND {hi} OR
            FEB NOT BETWEEN {lo} AND {hi} OR
            MAR NOT BETWEEN {lo} AND {hi} OR
            APR NOT BETWEEN {lo} AND {hi} OR
            MAY NOT BETWEEN {lo} AND {hi} OR
            JUN NOT BETWEEN {lo} AND {hi} OR
            JUL NOT BETWEEN {lo} AND {hi} OR
            AUG NOT BETWEEN {lo} AND {hi} OR
            SEP NOT BETWEEN {lo} AND {hi} OR
            OCT NOT BETWEEN {lo} AND {hi} OR
            NOV NOT BETWEEN {lo} AND {hi} OR
            DEC NOT BETWEEN {lo} AND {hi}""".format(lo = low, hi = high)).count()
    if invalids == 0:
        return True
    else:
        return False_____no_output_____

## Create Analytic Tables
Star schema with _Observations_ as the fact table and the following dimension tables:
_Biological_, _County_, _Climate_, _Time______no_output_____### Biological
- species
- common name
- genus
- lifeform
- group
- invasive
_____no_output_____

def createBioTbl(mnphn_df):
    """
    Takes in the phenology data frame and returns the biological table.
    """
    bio_tbl = mnphn_df.select(\
       col("SPECIES").alias("species"),
       col("SPECIES (COMMON NAME)").alias("common_name"),
       col("GENUS ").alias("genus"),
       col("LIFEFORM").alias("lifeform"),
       col("GROUP").alias("group"),
       col("INVASIVE").alias("mn_invasive")).distinct()
    return bio_tbl_____no_output_____

### County
- county_code
- county
- state_____no_output_____

def createCntyTbl(mnfips_df):
    """
    Takes in the county data frame to create the county table.
    """
    cnty_tbl = mnfips_df.select(\
        col("FIPS").alias("county_code"),
        col("Name").alias("county"),
        col("State").alias("state"))
    return cnty_tbl_____no_output_____

### Climate
- clim_id
- county_code
- year
- month
- tmpMin
- tmpMax
- tmpAvg
- pcpn

County code gets a little tricky here since NOAA and everyone else uses different state codes. For this MN restricted set, it's easy to go from 21 => 27, but for a national set, more care would be needed.
Also, the climate data is monthly data, so there's no day involved._____no_output_____

def convertClim(df, dataCol):
    '''
    Takes a climate data frame with month columns and FIPS-YEAR and converts
    them into month rows and splits county_code and year.
    Thanks to Doug on this post for the method: https://stackoverflow.com/a/64130519
    '''
    df = df.withColumn("county_code", col("FIPS-YEAR").substr(1,5))\
    .withColumn("year", col("FIPS-YEAR").substr(8,4)).drop("FIPS-YEAR")

    df = df.withColumn('j1', array(lit('01'), col('JAN')))
    df = df.withColumn('f2', array(lit('02'), col('FEB')))
    df = df.withColumn('m3', array(lit('03'), col('MAR')))
    df = df.withColumn('a4', array(lit('04'), col('APR')))
    df = df.withColumn('m5', array(lit('05'), col('MAY')))
    df = df.withColumn('j6', array(lit('06'), col('JUN')))
    df = df.withColumn('j7', array(lit('07'), col('JUL')))
    df = df.withColumn('a8', array(lit('08'), col('AUG')))
    df = df.withColumn('s9', array(lit('09'), col('SEP')))
    df = df.withColumn('o10', array(lit('10'), col('OCT')))
    df = df.withColumn('n11', array(lit('11'), col('NOV')))
    df = df.withColumn('d12', array(lit('12'), col('DEC')))

    df = df.withColumn('months', array('j1','f2','m3','a4','m5','j6','j7','a8','s9','o10','n11','d12'))

    df = df.withColumn('months', explode('months'))

    df = df.withColumn('month', col('months')[0])
    df = df.withColumn(dataCol, col('months')[1])
    return df.select("county_code", "year", "month", dataCol)_____no_output_____def createClimTbls(pcpn_df, tmpMin_df, tmpMax_df, tmpAvg_df):
    """
    Takes in the climate data frames to create the climate table.
    """
    # filter to minnesota and change FIPS code
    pcpn_tbl = pcpn_df.filter(col("FIPS-YEAR").like('21%'))\
        .withColumn("FIPS-YEAR", ((col("FIPS-YEAR")\
        .cast('bigint')+6000000000).cast('string')))
    tmpMin_tbl = tmpMin_df.filter(col("FIPS-YEAR").like('21%'))\
        .withColumn("FIPS-YEAR", ((col("FIPS-YEAR")\
        .cast('bigint')+6000000000).cast('string')))
    tmpMax_tbl = tmpMax_df.filter(col("FIPS-YEAR").like('21%'))\
        .withColumn("FIPS-YEAR", ((col("FIPS-YEAR")\
        .cast('bigint')+6000000000).cast('string')))
    tmpAvg_tbl = tmpAvg_df.filter(col("FIPS-YEAR").like('21%'))\
        .withColumn("FIPS-YEAR", ((col("FIPS-YEAR")\
        .cast('bigint')+6000000000).cast('string')))
    # run the convert function on our four climate tables
    pcpn_tbl = convertClim(pcpn_tbl, 'pcpn')
    tmpMin_tbl = convertClim(tmpMin_tbl, 'tmpMin')
    tmpMax_tbl = convertClim(tmpMax_tbl, 'tmpMax')
    tmpAvg_tbl = convertClim(tmpAvg_tbl, 'tmpAvg')
    return pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl_____no_output_____def joinClimTbls(pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl):
    """
    Takes in the individual climate tables and joins them.
    """
    # Combine all four climate tables
    clim_tbl = pcpn_tbl.join(tmpMin_tbl, ["county_code", "year", "month"])
    clim_tbl = clim_tbl.join(tmpMax_tbl, ["county_code", "year", "month"])
    clim_tbl = clim_tbl.join(tmpAvg_tbl, ["county_code", "year", "month"])
    #add id column and set data types
    clim_tbl = clim_tbl.withColumn("clim_id", monotonically_increasing_id())\
        .withColumn("year", col("year").cast('int'))\
        .withColumn("month", col("month").cast('int'))\
        .withColumn("pcpn", col("pcpn").cast('double'))\
        .withColumn("tmpMin", col("tmpMin").cast('double'))\
        .withColumn("tmpMax", col("tmpMax").cast('double'))\
        .withColumn("tmpAvg", col("tmpAvg").cast('double'))
    return clim_tbl_____no_output_____

#### Data Quality Check
Ensure the joining was succesfull.
Row counts for all the tables should be the same and a look at a random entry to spot check validity. _____no_output_____

def chkClimJoin(clim_tbl, pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl):
    """
    Takes a random row out of the climate table and confirms the values match
    against the cliamte data frames.
    """
    randRow = clim_tbl.sample(False, 0.0001).first()
    # print(randRow)
    pcpn_match = pcpn_tbl.select(col("pcpn")).where((col("county_code") == randRow["county_code"])\
                                       & (col("year") == randRow["year"])\
                                       & (col("month") == randRow["month"]))
    tmpMin_match = tmpMin_tbl.select(col("tmpMin")).where((col("county_code") == randRow["county_code"])\
                                       & (col("year") == randRow["year"])\
                                       & (col("month") == randRow["month"]))
    tmpMax_match = tmpMax_tbl.select(col("tmpMax")).where((col("county_code") == randRow["county_code"])\
                                       & (col("year") == randRow["year"])\
                                       & (col("month") == randRow["month"]))
    tmpAvg_match = tmpAvg_tbl.select(col("tmpAvg")).where((col("county_code") == randRow["county_code"])\
                                       & (col("year") == randRow["year"])\
                                       & (col("month") == randRow["month"]))
    # print(float(pcpn_match.first()["pcpn"]), float(tmpMin_match.first()["tmpMin"]), float(tmpMax_match.first()["tmpMax"]), float(tmpAvg_match.first()["tmpAvg"]))
    # All *match should be one row and match the randRow.
    if (pcpn_match.count() != 1)\
        or (tmpMin_match.count() != 1)\
        or (tmpMax_match.count() != 1)\
        or (tmpAvg_match.count() != 1):
        raise Exception("Join failed, too many entries.")
        return False
    elif (randRow["pcpn"] != float(pcpn_match.first()["pcpn"]))\
            or (randRow["tmpMin"] != float(tmpMin_match.first()["tmpMin"]))\
            or (randRow["tmpMax"] != float(tmpMax_match.first()["tmpMax"]))\
            or (randRow["tmpAvg"] != float(tmpAvg_match.first()["tmpAvg"])):
        raise Exception("Join failed, mismatched data.")
        return False
    else:
        return True_____no_output_____

### Observations (fact table)
- obsv_id
- date
- species
- county_code
- clim_id
- event
Join in the county data to the phenology set, so we can select it back out._____no_output_____

def createObsvTbl(mnphn_df, mnfips_df, clim_tbl):
    """
    Takes in the phenology and counties data frames and the climate table
    to return the observation table.
    """
    mnphn_df = mnphn_df.join(mnfips_df, lower(mnphn_df.COUNTY) == lower(mnfips_df.Name))
    obsv_tbl = mnphn_df.select(monotonically_increasing_id().alias("obsv_id"), \
        from_unixtime(unix_timestamp(\
            concat_ws("-",col("YEAR"),col("DAY")), 'yyyy-dd-MMM')).cast("date").alias("date"), \
        col("YEAR").cast("int").alias("year"), \
        from_unixtime(unix_timestamp(col("DAY"), 'dd-MMM'), 'MM').cast("int").alias("month"), \
        col("SPECIES").alias("species"), \
        col("FIPS").alias("county_code"), \
        col("EVENT").alias("event"))
    # join with the climate table to extract the clim_ids.
    # this also has the effect of pruning poorly formatted dates from phenology
    obsv_tbl = obsv_tbl.join(clim_tbl, ["county_code", "year", "month"])
    obsv_tbl = obsv_tbl.select("obsv_id", "date", "species", "county_code", "clim_id", "event")
    return obsv_tbl_____no_output_____

### Time Table
The most specific time item is date from observations, so go from there.
- date
- day
- week
- month
- year
- weekday_____no_output_____

def createTimeTbl(obsv_tbl):
    """
    Takes the observations table and creates the time table on the same scale.
    """
    # create datetime column from original timestamp column
    time_tbl = obsv_tbl.select('date').distinct()

    # extract columns to create time table
    time_tbl = time_tbl.select(
        col("date"),
        dayofmonth(col("date")).alias("day"),
        weekofyear(col("date")).alias("week"),
        month(col("date")).alias("month"),
        year(col("date")).alias("year"),
        date_format(col("date"), "E").alias("weekday")
        )

    return time_tbl_____no_output_____

## Write out Analysis Tables to Parquet files_____no_output_____

def writeTables():
    """
    Writes out the analysis tables into parquet files.
    "limit(1000)" is added due to size constraints on the Udacity Workspace.
    """
    obsv_tbl.limit(1000).write.partitionBy("date", "county_code").mode("overwrite")\
        .parquet(OUTPUT_FOLDER + "obsv.parquet")
    bio_tbl.limit(1000).write.mode("overwrite")\
        .parquet(OUTPUT_FOLDER + "bio.parquet")
    cnty_tbl.limit(1000).write.partitionBy("state").mode("overwrite")\
        .parquet(OUTPUT_FOLDER + "cnty.parquet")
    clim_tbl.limit(1000).write.partitionBy("year", "county_code").mode("overwrite")\
        .parquet(OUTPUT_FOLDER + "clim.parquet")
    time_tbl.limit(1000).write.partitionBy("year").mode("overwrite")\
        .parquet(OUTPUT_FOLDER + "time.parquet")_____no_output_____

## Run ETL_____no_output_____

spark = SparkSession \
        .builder \
        .getOrCreate()_____no_output_____

Extract and clean-up on Phenology and Counties.
Returns True for county mismatch data quality check._____no_output_____

mnphn_df = processPhenology(spark, "2018.4.6-MNPN-WEBSITE-master-dataset.csv")
mnfips_df = processCounties(spark, "USDA-NRCS_FIPS_Codes.json")
print(chkPhnCntMismatch(mnphn_df, mnfips_df))True

Extract and clean-up for Percipitation and temperature data. Returns true for a passing check of invalid measurements outside of the appropriate ranges (0-99.99 for percipitation and -50 to 140 for temperature)._____no_output_____

pcpn_df = processClim(spark, "climdiv-pcpncy-v1.0.0-20211104", -9.99)
print(chkClimRange(pcpn_df, 0, 99.99))True
tmpMax_df = processClim(spark, "climdiv-tmaxcy-v1.0.0-20211104", -99.99)
print(chkClimRange(pcpn_df, -50, 140))True
tmpMin_df = processClim(spark, "climdiv-tmincy-v1.0.0-20211104", -99.99)
print(chkClimRange(pcpn_df, -50, 140))True
tmpAvg_df = processClim(spark, "climdiv-tmpccy-v1.0.0-20211104", -99.99)
print(chkClimRange(pcpn_df, -50, 140))True

Create analytics tables._____no_output_____

bio_tbl = createBioTbl(mnphn_df)
cnty_tbl = createCntyTbl(mnfips_df)
pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl \
    = createClimTbls(pcpn_df, tmpMin_df, tmpMax_df, tmpAvg_df)
clim_tbl = joinClimTbls(pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl)
obsv_tbl = createObsvTbl(mnphn_df, mnfips_df, clim_tbl)
time_tbl = createTimeTbl(obsv_tbl)_____no_output_____# Data Qualtiy check on climate table join
print(chkClimJoin(clim_tbl, pcpn_tbl, tmpMin_tbl, tmpMax_tbl, tmpAvg_tbl))True
writeTables()_____no_output_____

## Sample Query
A query to examing percipitation in the month of the last flower._____no_output_____

obsv_tbl.createOrReplaceTempView("OBSV")
bio_tbl.createOrReplaceTempView("BIO")
cnty_tbl.createOrReplaceTempView("CNTY")
clim_tbl.createOrReplaceTempView("CLIM")
time_tbl.createOrReplaceTempView("TIME")_____no_output_____spark.sql("""
        SELECT o.date,
        c.pcpn,
        cy.county,
        o.event,
        b.common_name
        FROM OBSV o
        JOIN CLIM c
        ON o.clim_id == c.clim_id
        JOIN BIO b
        ON o.species == b.species
        JOIN CNTY cy
        ON o.county_code == cy.county_code
        WHERE o.event == 'LAST FLOWER' AND
        cy.county == 'Hennepin'
        ORDER BY c.pcpn DESC
        """).limit(10).toPandas()_____no_output_____


# Notebook from jayac251/AZ-900T0x-MicrosoftAzureFundamentals
Path: LinearModel.ipynb

_____no_output_____

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

Ambree_Score = np.array([['English',1,93],
                ['Math',6,87],
                ['History',2,70],
                ['CSE',4,62],
                ['Arts',4,86],
                ['Biology',1,7],
                ['Physics',3,73],
                ['Chemistry',1,80],
                ['Spanish',3,96]])

Score_t = Ambree_Score.transpose();
y = Score_t[2]
x=Score_t[1]

x = x.astype(np.int);
y= y.astype(np.int);
print(x)
print(y)

slope, intercept, r, p, std_err = stats.linregress(x, y)
print(slope)
print(intercept)
print("Correlation Coefficient :", r)
print(p)
print(std_err)

def myfunc(x):
  return slope * x + intercept

mymodel = list(map(myfunc, x))

plt.figure(figsize=(5, 8))
plt.scatter(x, y)
plt.xticks(np.arange(0, 10, 1))
plt.yticks(np.arange(0, 100, 5))
plt.plot(x,mymodel,color='red')
plt.show()[1 6 2 4 4 1 3 1 3]
[93 87 70 62 86  7 73 80 96]
5.278301886792454
58.00471698113208
Correlation Coefficient : 0.33556845610254354
0.37732834342550775
5.6004416092649825