```python
# some_file.py
import sys

# insert at 1, 0 is the script path (or '' in REPL)
sys.path.insert(1, "/Users/dhruvbalwada/work_root/sogos/")
```

```python
import os
from numpy import *
import pandas as pd
import xarray as xr
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
```

```python
from xgcm import Grid
from xgcm.autogenerate import generate_grid_ds
```

```python
import sogos.download_product as dlp
import sogos.load_product as ldp
import sogos.time_tools as tt
import sogos.geo_tools as gt
import sogos.download_file as df
```

```python
import gsw
import cmocean as cmocean
```

# Download Latest Data

```python
data_dir = "/Users/dhruvbalwada/work_root/sogos/data/raw/climatology/"
```

FTP ADDRESS: ftp://kakapo.ucsd.edu/pub/gilson/argo_climatology/

Data prior to 2017 (till Dec 2016) is in a single file

```python
# download the big climatology files
wget.download(
    "ftp://kakapo.ucsd.edu/pub/gilson/argo_climatology/RG_ArgoClim_Salinity_2017.nc.gz",
    data_dir,
)
wget.download(
    "ftp://kakapo.ucsd.edu/pub/gilson/argo_climatology/RG_ArgoClim_Temperature_2017.nc.gz",
    data_dir,
)
```

```python
from ftplib import FTP
```

```python
ftp_address = "ftp://kakapo.ucsd.edu/pub/gilson/argo_climatology/RG_ArgoClim_2019"
```

```python
url_root = "/pub/gilson/argo_climatology/"
ftp_root = "kakapo.ucsd.edu"
```

```python
ftp = FTP(ftp_root)
ftp.login()
ftp.cwd(url_root)
contents = ftp.nlst("RG_ArgoClim_2017*")
```

```python
contents = ftp.nlst("RG_ArgoClim_201*")
```

```python
for i in contents:
    print("Downloading" + i)
    wget.download("ftp://kakapo.ucsd.edu/pub/gilson/argo_climatology/" + i, data_dir)
```

    DownloadingRG_ArgoClim_201701.nc.gz
    DownloadingRG_ArgoClim_201702.nc.gz
    DownloadingRG_ArgoClim_201703.nc.gz
    DownloadingRG_ArgoClim_201704.nc.gz
    DownloadingRG_ArgoClim_201705.nc.gz
    DownloadingRG_ArgoClim_201706.nc.gz
    DownloadingRG_ArgoClim_201707.nc.gz
    DownloadingRG_ArgoClim_201708.nc.gz
    DownloadingRG_ArgoClim_201709.nc.gz
    DownloadingRG_ArgoClim_201710.nc.gz
    DownloadingRG_ArgoClim_201711.nc.gz
    DownloadingRG_ArgoClim_201712.nc.gz
    DownloadingRG_ArgoClim_201801.nc.gz
    DownloadingRG_ArgoClim_201802.nc.gz
    DownloadingRG_ArgoClim_201803.nc.gz
    DownloadingRG_ArgoClim_201804.nc.gz
    DownloadingRG_ArgoClim_201805.nc.gz
    DownloadingRG_ArgoClim_201806.nc.gz
    DownloadingRG_ArgoClim_201807.nc.gz
    DownloadingRG_ArgoClim_201808.nc.gz
    DownloadingRG_ArgoClim_201809.nc.gz
    DownloadingRG_ArgoClim_201810.nc.gz
    DownloadingRG_ArgoClim_201811.nc.gz
    DownloadingRG_ArgoClim_201812.nc.gz
    DownloadingRG_ArgoClim_201901.nc.gz
    DownloadingRG_ArgoClim_201902.nc.gz
    DownloadingRG_ArgoClim_201903.nc.gz
    DownloadingRG_ArgoClim_201904.nc.gz
    DownloadingRG_ArgoClim_201905.nc.gz
    DownloadingRG_ArgoClim_201906.nc.gz
    DownloadingRG_ArgoClim_201907.nc.gz
    DownloadingRG_ArgoClim_201908.nc.gz
    DownloadingRG_ArgoClim_201909.nc.gz

## Load some data

```python
Tclim = xr.open_dataset(data_dir + "RG_ArgoClim_Temperature_2017.nc", decode_times=False)
Sclim = xr.open_dataset(data_dir + "RG_ArgoClim_Salinity_2017.nc", decode_times=False)
```

```python
Climextra = xr.open_mfdataset(data_dir+ 'RG_ArgoClim_201*', decode_times=False)
```

    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/ipykernel_launcher.py:1: FutureWarning: In xarray version 0.15 the default behaviour of `open_mfdataset`
    will change. To retain the existing behavior, pass
    combine='nested'. To use future default behavior, pass
    combine='by_coords'. See
    http://xarray.pydata.org/en/stable/combining.html#combining-multi

      """Entry point for launching an IPython kernel.
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/xarray/backends/api.py:931: FutureWarning: The datasets supplied have global dimension coordinates. You may want
    to use the new `combine_by_coords` function (or the
    `combine='by_coords'` option to `open_mfdataset`) to order the datasets
    before concatenation. Alternatively, to continue concatenating based
    on the order the datasets are supplied in future, please use the new
    `combine_nested` function (or the `combine='nested'` option to
    open_mfdataset).
      from_openmfds=True,

```python
RG_clim = xr.merge([Tclim, Sclim, Climextra])
```

```python
# Calendar type was missing, and giving errors in decoding time
RG_clim.TIME.attrs['calendar'] = '360_day'
RG_clim = xr.decode_cf(RG_clim)
```

```python
## Add density and other things
SA = xr.apply_ufunc(gsw.SA_from_SP, RG_clim.ARGO_SALINITY_MEAN+RG_clim.ARGO_SALINITY_ANOMALY, RG_clim.PRESSURE ,
                                    RG_clim.LONGITUDE, RG_clim.LATITUDE,
                    dask='parallelized', output_dtypes=[float,]).rename('SA')
CT = xr.apply_ufunc(gsw.CT_from_t, SA, RG_clim.ARGO_TEMPERATURE_MEAN+RG_clim.ARGO_SALINITY_ANOMALY, RG_clim.PRESSURE,
                     dask='parallelized', output_dtypes=[float,]).rename('CT')
SIGMA0 = xr.apply_ufunc(gsw.sigma0, SA, CT, dask='parallelized', output_dtypes=[float,]).rename('SIGMA0')
```

```python
RG_clim = xr.merge([RG_clim, SIGMA0])
```

```python
T_region = RG_clim.ARGO_TEMPERATURE_ANOMALY.groupby('TIME.season').mean() + RG_clim.ARGO_TEMPERATURE_MEAN
S_region = RG_clim.ARGO_SALINITY_ANOMALY.groupby('TIME.season').mean() + RG_clim.ARGO_SALINITY_MEAN
rho_region = RG_clim.SIGMA0.groupby('TIME.season').mean()
```

```python
plt.figure(figsize=(18,3))
plt.subplot(141)
T_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='DJF').plot.contourf(levels=11, vmin=-9, vmax=9);

plt.subplot(142)
T_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='MAM').plot.contourf(levels=11, vmin=-9, vmax=9);

plt.subplot(143)
T_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='JJA').plot.contourf(levels=11, vmin=-9, vmax=9);

plt.subplot(144)
T_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='SON').plot.contourf(levels=11, vmin=-9, vmax=9);
```

```python
plt.figure(figsize=(18,3))
plt.subplot(141)
S_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='DJF').plot.contourf(levels=11, vmin=33.7, vmax=34.2)

plt.subplot(142)
S_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='MAM').plot.contourf(levels=11, vmin=33.7, vmax=34.2)

plt.subplot(143)
S_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='JJA').plot.contourf(levels=11, vmin=33.7, vmax=34.2)

plt.subplot(144)
S_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='SON').plot.contourf(levels=11, vmin=33.7, vmax=34.2)
plt.tight_layoutout()
```

```python
plt.figure(figsize=(18,3))
plt.subplot(141)
rho_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='DJF').plot.contourf(levels=11)

plt.subplot(142)
rho_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='MAM').plot.contourf(levels=11)

plt.subplot(143)
rho_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='JJA').plot.contourf(levels=11)

plt.subplot(144)
rho_region.sel(LATITUDE=slice(-65,-45), LONGITUDE=slice(20,60)).isel(PRESSURE=0).sel(season='SON').plot.contourf(levels=11)
```

### Some Climatological Mean Sections

```python
dens_section30 = RG_clim.SIGMA0.sel(LONGITUDE=30, method='nearest').sel(LATITUDE=slice(-70,-40)).load()
dens_section40 = RG_clim.SIGMA0.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).load()
```

```python
glider = {"start_month": 4.99, "end_month":7.8,  "start_lat": -51.5, "end_lat": -53, "max_depth": 1000}
```

```python
plt.figure(figsize=(15,4))

plt.subplot(121)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=30, method='nearest').sel(LATITUDE=slice(-70,-40)
                                                                     ).plot.contourf(vmin=-10, levels=24)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=30, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(linestyles='-.',levels=[1])
dens_section.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C0',
                                                                     linestyles='dashed', linewidths=4)
plt.plot([glider['start_lat'], glider['start_lat']], [4, glider['max_depth']], color='C2', alpha=0.5)
plt.plot([glider['end_lat'], glider['end_lat']], [4, glider['max_depth']], color='C2', alpha=0.5)
plt.gca().invert_yaxis()

plt.subplot(122)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contourf(vmin=33.77, vmax=35.21, levels=24, cmap=cmocean.cm.haline)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(levels=[34.2], linestyles='dashdot')
dens_section.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.plot([glider['start_lat'], glider['start_lat']], [4, 1900], color='C3', alpha=0.5)

plt.gca().invert_yaxis()

plt.tight_layout()

#plt.savefig('../figures/clim_TS_30E.png')
```

```python
plt.figure(figsize=(15,4))

plt.subplot(121)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)
                                                                     ).plot.contourf(vmin=-10,levels=24)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(linestyles='-.',levels=[1])
dens_section40.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section40.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C0',
                                                                       linestyles='dashed', linewidths=4)
plt.gca().invert_yaxis()

plt.subplot(122)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contourf(vmin=33.77, vmax=35.21,levels=24, cmap=cmocean.cm.haline)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(levels=[34.2], linestyles='dashdot')
dens_section40.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section40.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                       linestyles='dashed', linewidths=4)

plt.gca().invert_yaxis()

plt.tight_layout()
plt.savefig('../figures/clim_TS_40E.png')
```

```python
plt.figure(figsize=(12,4))

plt.subplot(121)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contourf(levels=24)
RG_clim.ARGO_TEMPERATURE_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(levels=[1])
plt.gca().invert_yaxis()

plt.subplot(122)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contourf(levels=24)
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=40, method='nearest').sel(LATITUDE=slice(-70,-40)).plot.contour(levels=[34.2])
plt.gca().invert_yaxis()

plt.tight_layout()
```

```python
# Not much seasonality below 200m
dens_section.groupby('TIME.month').mean().mean('month').sel(PRESSURE=slice(0,1000)).plot.contourf(cmap='Blues')
dens_section.groupby('TIME.month').mean().isel(month=0).sel(PRESSURE=slice(0,1000)).plot.contour(levels=[27.2])
dens_section.groupby('TIME.month').mean().isel(month=3).sel(PRESSURE=slice(0,1000)).plot.contour(levels=[27.2])
dens_section.groupby('TIME.month').mean().isel(month=6).sel(PRESSURE=slice(0,1000)).plot.contour(levels=[27.2])
dens_section.groupby('TIME.month').mean().isel(month=9).sel(PRESSURE=slice(0,1000)).plot.contour(levels=[27.2])

plt.gca().invert_yaxis()
```

## N2

\begin{equation}
N^2 = db/dz
\end{equation}

$b = -\frac{g}{\rho_0} \rho'$

$b = g(\alpha \triangle T - \beta \triangle S)$

```python
RG_clim
```

    <xarray.Dataset>
    Dimensions:                   (LATITUDE: 145, LONGITUDE: 360, PRESSURE: 58, TIME: 189)
    Coordinates:
      * TIME                      (TIME) object 2004-01-16 00:00:00 ... 2019-09-16 00:00:00
      * LONGITUDE                 (LONGITUDE) float32 20.5 21.5 22.5 ... 378.5 379.5
      * LATITUDE                  (LATITUDE) float32 -64.5 -63.5 -62.5 ... 78.5 79.5
      * PRESSURE                  (PRESSURE) float32 2.5 10.0 20.0 ... 1900.0 1975.0
    Data variables:
        ARGO_TEMPERATURE_MEAN     (PRESSURE, LATITUDE, LONGITUDE) float32 ...
        ARGO_TEMPERATURE_ANOMALY  (TIME, PRESSURE, LATITUDE, LONGITUDE) float32 dask.array<chunksize=(157, 58, 145, 360), meta=np.ndarray>
        BATHYMETRY_MASK           (PRESSURE, LATITUDE, LONGITUDE) float32 ...
        MAPPING_MASK              (PRESSURE, LATITUDE, LONGITUDE) float32 ...
        ARGO_SALINITY_MEAN        (PRESSURE, LATITUDE, LONGITUDE) float32 ...
        ARGO_SALINITY_ANOMALY     (TIME, PRESSURE, LATITUDE, LONGITUDE) float32 dask.array<chunksize=(157, 58, 145, 360), meta=np.ndarray>
        SIGMA0                    (PRESSURE, LATITUDE, LONGITUDE, TIME) float64 dask.array<chunksize=(58, 145, 360, 157), meta=np.ndarray>

```python
RG_clim = generate_grid_ds(RG_clim,
        {'Z':'PRESSURE', 'X':'LONGITUDE', 'Y':'LATITUDE'})
```

```python
grid = Grid(RG_clim, periodic='X')
```

```python
g = 9.81
rho0 = 1000
```

```python
dens_clim_monthly = RG_clim.SIGMA0.groupby('TIME.month').mean()
```

```python
dens_clim_monthly
```

    <xarray.DataArray 'SIGMA0' (PRESSURE: 58, LATITUDE: 145, LONGITUDE: 360, month: 12)>
    dask.array<transpose, shape=(58, 145, 360, 12), dtype=float64, chunksize=(58, 145, 360, 1), chunktype=numpy.ndarray>
    Coordinates:
      * LONGITUDE  (LONGITUDE) float32 20.5 21.5 22.5 23.5 ... 377.5 378.5 379.5
      * LATITUDE   (LATITUDE) float32 -64.5 -63.5 -62.5 -61.5 ... 77.5 78.5 79.5
      * PRESSURE   (PRESSURE) float32 2.5 10.0 20.0 30.0 ... 1800.0 1900.0 1975.0
      * month      (month) int64 1 2 3 4 5 6 7 8 9 10 11 12

```python
N2_clim_monthly = grid.interp(-g/rho0* grid.diff(dens_clim_monthly, 'Z', boundary='extend') / -(grid.diff(RG_clim.PRESSURE, 'Z', boundary='extend')), 'Z', boundary='extend')
```

```python
N2_clim_monthly_SO = N2_clim_monthly.sel(LATITUDE=slice(-70, -30)).load()
```

    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: divide by zero encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: invalid value encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/core.py:119: RuntimeWarning: divide by zero encountered in true_divide
      return func(*args2)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/core.py:119: RuntimeWarning: invalid value encountered in true_divide
      return func(*args2)

```python
N2_clim_monthly_SO = N2_clim_monthly_SO.rename('N2')
```

```python
plt.figure(figsize=(18,12))

plt.subplot(221)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=1).plot(vmin=-5e-5)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=1).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('January')
plt.gca().invert_yaxis()

plt.subplot(222)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=4).plot(vmin=-5e-5)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=4).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('April')
plt.gca().invert_yaxis()

plt.subplot(223)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=7).plot(vmin=-5e-5)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=7).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('July')
plt.gca().invert_yaxis()

plt.subplot(224)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=10).plot(vmin=-5e-5)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=10).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('October')
plt.gca().invert_yaxis()

plt.savefig('../figures/clim_N2_30E.png')
```

```python
plt.figure(figsize=(8,3))

plt.subplot(121)
#plt.pcolormesh(N2_clim_monthly_SO.LATITUDE.sel(LATITUDE=slice(-70, -40)),
#               N2_clim_monthly_SO.LATITUDE.sel(=slice(-70, -40)),
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=4).plot(vmin=-5e-5,
                                                                                rasterized=True,add_colorbar=False)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=4).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('April')
plt.gca().invert_yaxis()
plt.ylim([1500, 0])
plt.xlabel('Latitude')
plt.ylabel('Depth (m)')

plt.subplot(122)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=9).plot(vmin=-5e-5,
                                                                                rasterized=True,add_colorbar=False)
N2_clim_monthly_SO.sel(LATITUDE=slice(-70, -40)).sel(LONGITUDE=30, method='nearest').sel(month=9).plot.contour(levels=[2e-5])
RG_clim.ARGO_SALINITY_MEAN.sel(LONGITUDE=30, method='nearest').sel(
    LATITUDE=slice(-70,-40)).plot.contour(levels=[34.35], linestyles='dashdot')

dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(colors='k', levels=7)
dens_section30.groupby('TIME.month').mean().mean('month').plot.contour(levels=[27.189, 27.752], colors='C1',
                                                                     linestyles='dashed', linewidths=4)
plt.title('September')
plt.gca().invert_yaxis()
plt.ylim([1500, 0])
plt.xlabel('Latitude')
plt.ylabel('Depth (m)')

plt.tight_layout()
plt.savefig('N2_climatology.pdf')
```

```python

```

```python
N2.sel(LATITUDE=slice(-60, -40)).sel(LONGITUDE=30, method='nearest').isel(TIME=-1).plot()
```

```python

```

```python

```

```python

```

The apply ufunc way, not working yet.

```python
CT_clim = CT.groupby('TIME.month').mean()
SA_clim = SA.groupby('TIME.month').mean()
```

```python
CT_clim_region = CT_clim.sel(LATITUDE=slice(-65,-35), LONGITUDE=slice(20,50)).load()
SA_clim_region = SA_clim.sel(LATITUDE=slice(-65,-35), LONGITUDE=slice(20,50)).load()
```

    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: divide by zero encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: invalid value encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: divide by zero encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: invalid value encountered in true_divide
      x = np.divide(x1, x2, out)

```python
(N2, pmid) = xr.apply_ufunc(gsw.Nsquared, SA_clim_region, CT_clim_region, RG_clim.PRESSURE,
                            dask='parallelized',
                            input_core_dims=[['PRESSURE'],['PRESSURE'],['PRESSURE']],
                            output_core_dims=[['PRESSURE'],['PRESSURE']], exclude_dims=set(['PRESSURE']))
```

```python

```

```python

```

```python

```

### Gestrophic Velocities

```python
psi = xr.apply_ufunc(gsw.geo_strf_dyn_height, SA, CT , RG_clim.PRESSURE,
                    dask='parallelized', output_dtypes=[float,]).rename('psi')
```

```python
psi
```

    <xarray.DataArray 'psi' (PRESSURE: 58, LATITUDE: 145, LONGITUDE: 360, TIME: 189)>
    dask.array<geo_strf_dyn_height, shape=(58, 145, 360, 189), dtype=float64, chunksize=(58, 145, 360, 157), chunktype=numpy.ndarray>
    Coordinates:
      * LONGITUDE  (LONGITUDE) float32 20.5 21.5 22.5 23.5 ... 377.5 378.5 379.5
      * LATITUDE   (LATITUDE) float32 -64.5 -63.5 -62.5 -61.5 ... 77.5 78.5 79.5
      * PRESSURE   (PRESSURE) float32 2.5 10.0 20.0 30.0 ... 1800.0 1900.0 1975.0
      * TIME       (TIME) float64 0.5 1.5 2.5 3.5 4.5 ... 185.5 186.5 187.5 188.5

```python
vels = xr.apply_ufunc(gsw.geostrophic_velocity, psi, psi.LONGITUDE, psi.LATITUDE,
                     dask='parallelized', output_core_dims=[4,4], output_dtypes=[float,]).rename('vels')
```

```python
vels
```

    <xarray.DataArray 'vels' (PRESSURE: 58, LATITUDE: 145, LONGITUDE: 360, TIME: 189)>
    dask.array<geostrophic_velocity, shape=(58, 145, 360, 189), dtype=float64, chunksize=(58, 145, 360, 157), chunktype=numpy.ndarray>
    Coordinates:
      * LONGITUDE  (LONGITUDE) float32 20.5 21.5 22.5 23.5 ... 377.5 378.5 379.5
      * LATITUDE   (LATITUDE) float32 -64.5 -63.5 -62.5 -61.5 ... 77.5 78.5 79.5
      * PRESSURE   (PRESSURE) float32 2.5 10.0 20.0 30.0 ... 1800.0 1900.0 1975.0
      * TIME       (TIME) float64 0.5 1.5 2.5 3.5 4.5 ... 185.5 186.5 187.5 188.5

### Mixed Layer Depth

Ended up going with Holte's climatology for MLD work

```python
delta_dens = RG_clim.SIGMA0 - RG_clim.SIGMA0.isel(PRESSURE=0)
```

```python
import nc_time_axis
```

```python
RG_clim.SIGMA0.sel(LONGITUDE=30, method='nearest').sel( LATITUDE=slice(-63,-45)).isel(TIME=-1).plot.contourf()
plt.gca().invert_yaxis()
```

```python
RG_clim.SIGMA0.sel(LONGITUDE=30, LATITUDE=-50, method='nearest').isel(TIME=-1).plot()
RG_clim.SIGMA0.sel(LONGITUDE=30, LATITUDE=-50, method='nearest').isel(TIME=-7).plot()
RG_clim.SIGMA0.sel(LONGITUDE=30, LATITUDE=-60, method='nearest').isel(TIME=-1).plot()
RG_clim.SIGMA0.sel(LONGITUDE=30, LATITUDE=-60, method='nearest').isel(TIME=-7).plot()
```

```python
delta_dens.sel(LONGITUDE=30, LATITUDE=-50, method='nearest').isel(TIME=-1).plot()
```

```python
temp = delta_dens.where(delta_dens>0.03).sel(LONGITUDE=30, LATITUDE=-50, method='nearest').isel(TIME=-1).plot()
```

```python
temp = delta_dens.where(delta_dens>0.03)
```

```python
MLD = temp.PRESSURE.where(temp == temp.min('PRESSURE')).min('PRESSURE')
```

```python
MLD_clim = temp.PRESSURE.where(temp == temp.min('PRESSURE')).min('PRESSURE').groupby('TIME.month').mean()
```

```python
MLD_clim.load()
```

    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/core.py:119: RuntimeWarning: invalid value encountered in greater
      return func(*args2)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/utils.py:29: RuntimeWarning: All-NaN slice encountered
      return func(*args, **kwargs)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/toolz/functoolz.py:488: RuntimeWarning: All-NaN slice encountered
      ret = f(ret)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: divide by zero encountered in true_divide
      x = np.divide(x1, x2, out)
    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/dask/array/numpy_compat.py:40: RuntimeWarning: invalid value encountered in true_divide
      x = np.divide(x1, x2, out)

    <xarray.DataArray 'PRESSURE' (LATITUDE: 145, LONGITUDE: 360, month: 12)>
    array([[[20.      , 30.      , 38.125   , ..., 82.      , 62.666668,
             32.      ],
            [20.      , 30.      , 36.875   , ..., 83.333336, 64.666664,
             28.666666],
            [19.375   , 30.      , 35.      , ..., 83.333336, 65.333336,
             28.666666],
            ...,
            [20.625   , 30.      , 38.75    , ..., 75.333336, 61.333332,
             27.333334],
            [20.625   , 30.      , 38.75    , ..., 76.666664, 61.333332,
             31.333334],
            [20.      , 30.      , 38.75    , ..., 80.      , 62.      ,
             36.      ]],

           [[21.25    , 31.25    , 38.125   , ..., 80.      , 52.666668,
             37.333332],
            [21.25    , 31.25    , 38.125   , ..., 82.      , 55.333332,
             33.333332],
            [21.25    , 30.625   , 38.75    , ..., 82.      , 64.666664,
             32.      ],
            ...,
            [22.5     , 30.      , 38.75    , ..., 82.      , 60.      ,
             33.333332],
            [21.875   , 30.      , 38.75    , ..., 81.333336, 57.333332,
             38.      ],
            [21.875   , 30.      , 38.75    , ..., 80.666664, 54.666668,
             38.      ]],

           [[25.625   , 33.125   , 38.75    , ..., 70.666664, 45.333332,
             33.333332],
            [25.625   , 33.75    , 38.75    , ..., 73.333336, 48.      ,
             34.      ],
            [25.625   , 33.75    , 38.75    , ..., 78.666664, 51.333332,
             34.      ],
            ...,
            [25.625   , 33.125   , 40.      , ..., 72.666664, 48.      ,
             32.666668],
            [25.625   , 32.5     , 38.75    , ..., 71.333336, 45.333332,
             33.333332],
            [25.625   , 33.125   , 38.75    , ..., 70.666664, 45.333332,
             34.      ]],

           ...,

           [[      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            ...,
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan]],

           [[      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            ...,
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan]],

           [[      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            ...,
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan],
            [      nan,       nan,       nan, ...,       nan,       nan,
                   nan]]], dtype=float32)
    Coordinates:
      * LONGITUDE  (LONGITUDE) float32 20.5 21.5 22.5 23.5 ... 377.5 378.5 379.5
      * LATITUDE   (LATITUDE) float32 -64.5 -63.5 -62.5 -61.5 ... 77.5 78.5 79.5
      * month      (month) int64 1 2 3 4 5 6 7 8 9 10 11 12

```python
MLD_clim.month
```

    <xarray.DataArray 'month' (month: 12)>
    array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])
    Coordinates:
      * month    (month) int64 1 2 3 4 5 6 7 8 9 10 11 12

```python
plt.figure(figsize=(12,4))

plt.subplot(121)
MLD_clim.sel(LATITUDE=slice(-75,-25), LONGITUDE=slice(20,90)).sel(month=1).plot(vmin=0, vmax=120)

plt.subplot(122)
MLD_clim.sel(LATITUDE=slice(-75,-25), LONGITUDE=slice(20,90)).sel(month=7).plot(vmin=0, vmax=120)
```

```python
MLD_clim.max('month').sel(LATITUDE=slice(-75,-25), LONGITUDE=slice(20,380)).plot(vmin=0, vmax=150)
```

```python
deltaH = MLD_clim.max('month') - MLD_clim.min('month')
```

    /Users/dhruvbalwada/code/miniconda/envs/sogos/lib/python3.7/site-packages/xarray/core/nputils.py:223: RuntimeWarning: All-NaN slice encountered
      result = getattr(npmodule, name)(values, axis=axis, **kwargs)

```python
deltaH.sel(LATITUDE=slice(-75,-25), LONGITUDE=slice(20,380)).plot(vmin=0, vmax=80)
```

```python
MLD_clim.sel(LATITUDE=-45, LONGITUDE=35, method='nearest').plot(label='45S')
MLD_clim.sel(LATITUDE=-50, LONGITUDE=35, method='nearest').plot(label='50S')
MLD_clim.sel(LATITUDE=-55, LONGITUDE=35, method='nearest').plot(label='55S')
MLD_clim.sel(LATITUDE=-60, LONGITUDE=35, method='nearest').plot(label='60S')
plt.legend()
```

```python
MLD_clim.sel(LATITUDE=-45, LONGITUDE=45, method='nearest').plot(label='45S')
MLD_clim.sel(LATITUDE=-50, LONGITUDE=45, method='nearest').plot(label='50S')
MLD_clim.sel(LATITUDE=-55, LONGITUDE=45, method='nearest').plot(label='55S')
MLD_clim.sel(LATITUDE=-60, LONGITUDE=45, method='nearest').plot(label='60S')
plt.legend()
```

```python

```


# 格子ボルツマン法による流体シミュレーション (Python)
>
- toc: true
- badges: true
- comments: true
- categories: [physics]
- author: 山拓

1年次の時のレポートを記事化したものです。元はProcessingによるシミュレーションでした。

## 格子ボルツマン法
### 計算方法の概要
流体解析（流体シミュレーション, computational fluid dynamics : CFD）を行う上では、連続体である流体の運動を離散化して計算する必要があります。このとき計算する方程式は次式で表される、流体の運動量保存則です.

$$
\begin{aligned}
\rho\frac{D\boldsymbol{v}}{Dt}=-\nabla P+\mu\nabla^2\boldsymbol{v}+\boldsymbol{f}
\end{aligned}
$$

この式はNavier-Stokes方程式と呼ばれます。ここで、$\rho$は密度、$P$は圧力、$\mu$は粘性抵抗、$\boldsymbol{f}$は外力であり、$\dfrac{D\boldsymbol{v}}{Dt}$は流れの速度場$\boldsymbol{v}$のLagrange微分です。よって

$$
\begin{aligned}
\frac{D\boldsymbol{v}}{Dt}=\frac{\partial\boldsymbol{v}}{\partial t}+(\boldsymbol{v}\cdot \nabla)\boldsymbol{v}
\end{aligned}
$$

が成り立ちます。また、質量の保存則として連続の式

$$
\begin{aligned}
\nabla\cdot\boldsymbol{v}=0
\end{aligned}
$$

があります。このような偏微分方程式で表される質量・運動量・エネルギーの保存則を離散化して数値的に解く方法としては有限差分法、有限要素法、有限体積法などがあります。また、流体を粒子の集合に分割し、流体の方程式に従う粒子の動きを計算します方法を粒子法と呼びます。今回用いる格子ボルツマン法（Lattice Boltzmann Method;LBM）とは、これらに属さない方法であり、流体を有限個の種類の速度を持つ仮想的な粒子の集合とみなし、それらの粒子の衝突と並進を計算します。流体を連続体ではなく粒子として扱う点は粒子法に似ていますが、LBMではあくまでも仮想的な粒子の分布を扱います。質量・運動量・エネルギーの保存則を巨視的（マクロ）な方程式であるとすれば、LBMが解く方程式は統計熱力学に基づく微視的 (ミクロ) な方程式と言えます。

### LBMの格子
LBMの格子は2次元では正方形、3次元では立方体を用いることが多いです。今回は、2次元平面における流れを考えるため、2D9Qモデルを用います。''2D''というのは2次元である事を意味し、''9Q''は粒子の取りうる速度$\boldsymbol{c}_i$の方向が9つあることを示しています (下図：LBMの格子(2D9Q)と9方向の速度 ($c=1$のとき))。

9つの速度は
$$
\begin{aligned}
\boldsymbol{c}_0&=(0,0)\ \ (i=0)\\
\boldsymbol{c}_i&=c\left[\cos\left(\frac{i-1}{2}\pi\right), \sin\left(\frac{i-1}{2}\pi\right)\right]\ \ (i=1,2,3,4)\\
\boldsymbol{c}_i&=\sqrt{2}c\left[\cos\left(\frac{i-1}{2}\pi+\frac{\pi}{4}\right), \sin\left(\frac{i-1}{2}\pi+\frac{\pi}{4}\right)\right]\ \ (i=5,6,7,8)
\end{aligned}
$$

で表されます。ここで$c$は定数ですが、実装上は簡単のため$c=1$とします。なお、この2D9Qモデルですが、数式で表記する場合には数字で方向をラベリングするほうがよいです。逆に実装する上では方角による方向のラベリングを行う方がミスがなくなりやすいでしょう (下図：2D9Q のラベリング)。

### 時間発展の式
座標$\boldsymbol{x}=(x,y)$、時刻$t$において速度$\boldsymbol{c}_i$を持つ粒子についての速度分布関数(確率密度関数)を$f_i(\boldsymbol{x},t)$とします。この速度分布関数$f_i(\boldsymbol{x},t)$は以下の格子ボルツマン方程式 (lattice Boltzmann equation) に従います.

$$
\begin{aligned}
\underbrace{f_i(\boldsymbol{x}+\boldsymbol{c}_i\Delta t,t+\Delta t)-f_i(\boldsymbol{x},t)}_{並進\textrm{(streaming)}}=\underbrace{\Omega_i[f_i(\boldsymbol{x},t)]}_{衝突\textrm{(collision)}}
\end{aligned}\tag{1}
$$

ただし、$\Delta t$は時間刻みです。速度$\boldsymbol{c}_i$の粒子は$\Delta t$で空間刻み$\Delta \boldsymbol{x}=\boldsymbol{c}_i\Delta t$だけ離れた格子に移動します。(1)式は初期条件さえ与えれば速度分布関数$f_i(\boldsymbol{x},t)$を陽的に計算できます。また上式の左辺は粒子の並進(streaming)を表し、右辺は粒子同士の衝突(collision)を表します。並進は下図のように計算します。

実装上では$\Omega_i$の取り扱いが重要とりますが、BGK (Bhatnagar-Gross-Krook)近似に基づく衝突則を用いられることが多いです。BGK近似モデルを用いたLBMの基礎方程式は次式で表されます。

$$
\begin{aligned}
f_i(\boldsymbol{x}+\boldsymbol{c}_i\Delta t,t+\Delta t)-f_i(\boldsymbol{x},t)=-\frac{1}{\tau}[f_i(\boldsymbol{x},t)-f_i^{\textrm{eq}}(\boldsymbol{x},t)]
\end{aligned}\tag{2}
$$

ここで、$\tau$は単一緩和時間です。 実装上は$\omega:=\dfrac{1}{\tau}$とします。なお、粘度（動粘性抵抗）$\nu$と単一緩和時間$\tau$および$\omega$との間には

$$
\tau=\frac{1}{\omega}\approx 3\nu+0.5\Delta t
$$

が成り立ちます。

### 平衡分布関数の計算
(2)式中の$f_i^{\textrm{eq}}(\boldsymbol{x},t)$を平衡分布関数と呼びます。ここでMaxwell-Boltzmann平衡分布は次式で表されます。

$$
g^{\textrm{eq}}(\boldsymbol{\xi})=\rho{\left(\frac{m}{2\pi k_BT}\right)}^{\frac{D}{2}} \exp\left(-\frac{m(\boldsymbol{c}_i-\boldsymbol{v})^2}{2k_BT}\right)\ \
$$

ただし、$\boldsymbol{\xi}:=\boldsymbol{c}_i-\boldsymbol{v}$とし、$D$ は次元数としました。上式を$\boldsymbol{v}$についてTayler展開することで平衡分布関数は求められ、次式で表されます。

$$
\begin{aligned}
f_i^{\textrm{eq}}(\boldsymbol{x},t)=w_i\rho\left[1+\frac{3(\boldsymbol{c}_i\cdot\boldsymbol{v})}{c^2}+\frac{9}{2c^4}(\boldsymbol{c}_i\cdot\boldsymbol{v})^2-\frac{3}{2c^2}\boldsymbol{v}^2\right]
\end{aligned}\tag{3}
$$

ただし、

$$
  w_i = \begin{cases}
    \frac{4}{9} & (i=0) \\
    \frac{1}{9} & (i=1, 2, 3, 4) \\
    \frac{1}{36} & (i=5, 6, 7, 8) \\
  \end{cases}
$$

です。この$f_i^{\textrm{eq}}(\boldsymbol{x},t)$の計算のために密度$\rho(\boldsymbol{x},t)$と流速$\boldsymbol{v}(\boldsymbol{x},t)=(v_x(\boldsymbol{x},t), v_y(\boldsymbol{x},t))$を計算する必要がありますが、これらは次のように$f_i(\boldsymbol{x},t)$から求めることができます。

$$
\begin{align}
\rho(\boldsymbol{x},t)&=\sum_{i=0}^{8}f_i(\boldsymbol{x},t)\tag{4}\\
v_x(\boldsymbol{x},t)&=\frac{1}{\rho}\left[(f_1+f_5+f_8)-(f_3+f_6+f_7)\right]\tag{5}\\
v_y(\boldsymbol{x},t)&=\frac{1}{\rho}\left[(f_2+f_5+f_6)-(f_4+f_7+f_8)\right]\tag{6}
\end{align}
$$

これより、仮想粒子の速度$\boldsymbol{c}_i$と流速$\boldsymbol{v}(\boldsymbol{x}、t)$の内積$(\boldsymbol{c}_i\cdot\boldsymbol{v})$の計算ができます。また$c=1$とするので、最終的な実装上の式は次のようになります.

$$
\begin{array}[H]{c||c|c}
i&(\boldsymbol{c}_i\cdot\boldsymbol{v})&f_i^{\textrm{eq}}(\boldsymbol{x},t)\\ \hline
0&0&\frac{4}{9}\rho\left[1-\frac{3}{2}\boldsymbol{v}^2\right]\\
1(E)&v_x&\frac{1}{9}\rho\left[1+3v_x+\frac{9}{2}v_x^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
2(N)&v_y&\frac{1}{9}\rho\left[1+3v_y+\frac{9}{2}v_y^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
3(W)&-v_x&\frac{1}{9}\rho\left[1-3v_x+\frac{9}{2}v_x^2-\frac{3}{2}\boldsymbol{v}^2\right] \\
4(S)&-v_y&\frac{1}{9}\rho\left[1-3v_y+\frac{9}{2}v_y^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
5(NE)&v_x+v_y&\frac{1}{36}\rho\left[1+3(v_x+v_y)+\frac{9}{2}(v_x+v_y)^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
6(NW)&-v_x+v_y&\frac{1}{36}\rho\left[1+3(-v_x+v_y)+\frac{9}{2}(-v_x+v_y)^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
7(SW)&-v_x-v_y&\frac{1}{36}\rho\left[1-3(v_x+v_y)+\frac{9}{2}(v_x+v_y)^2-\frac{3}{2}\boldsymbol{v}^2\right]\\
8(SE)&v_x-v_y&\frac{1}{36}\rho\left[1+3(v_x-v_y)+\frac{9}{2}(v_x-v_y)^2-\frac{3}{2}\boldsymbol{v}^2\right]\\ \hline
\end{array}
$$

### 境界条件
粒子と障壁(barrier)との衝突を考えます。滑りなし壁の条件($\boldsymbol{v}=0$)を簡易に実装するために用いられているのが、**bounce-back条件** です。障害物に指定された格子点に仮想粒子が並進運動で侵入した場合、bounce-back条件では、仮想粒子をその侵入方向と180°反対の方向に跳ね返します (次図)。なお、今回は障害物の境界は格子点の中央に存在するとします。

### 計算のアルゴリズム
アルゴリズムは以下のようになります.

1. 密度$\rho$, 流速$\boldsymbol{v}$, 速度分布関数$f_i$, 平衡分布関数$f$を初期化します。今回の場合は、左から右に流れが生じている場合を考えるので$\boldsymbol{v}$は$x$方向のみ大きさを持ちます。また、計算開始前、分布関数は平衡に達していたとします。よって次式のようになります。

    $$
    \begin{align}
    \rho(\boldsymbol{x},t)&:=1\tag{7}\\
    \boldsymbol{v}(\boldsymbol{x},t)=(v_x, v_y)&:=(u,0)\ \ (u:定数)\tag{8}\\
    f_i(\boldsymbol{x},t=0)&:=f_i^{\textrm{eq}}(\boldsymbol{x},t=0)\tag{9}\\
    \end{align}
    $$

1. 密度$\rho$, 流速$\boldsymbol{v}$を$f_i(\boldsymbol{x},t)$から(4), (5), (6)式によって求めます。
1. 平衡分布関数$f_i^{\textrm{eq}}(\boldsymbol{x},t)$を(3)式より計算します。

1. (**Streaming Step**) (2)式の左辺により、並進後の分布関数を次のように計算します。

    $$
    \begin{aligned}
    f_i(\boldsymbol{x}+\boldsymbol{c}_i\Delta t,t+\Delta t)=f_i^*(\boldsymbol{x},t)\\
    \end{aligned}\tag{10}
    $$

1. (**Bounce Step**) 5において並進した格子に障害物（境界）が存在した場合、bounce-back条件により、修正します。

1. (**Collision Step**) 仮想粒子の衝突を(2)式の右辺により次のように計算します。ただし、$f_i^*(\boldsymbol{x},t)$は仮想粒子が衝突し、並進する前の分布関数です。

    $$
    \begin{aligned}
    f_i^*(\boldsymbol{x},t)=f_i(\boldsymbol{x},t)-\frac{1}{\tau}[f_i(\boldsymbol{x},t)-f_i^{\textrm{eq}}(\boldsymbol{x},t)]\\
    \end{aligned}\tag{11}
    $$

1. 2～6を繰り返します。

## Pythonでの実装
ここからはPythonでの実装について解説していきます。なお、コードは[Fluid Dynamics Simulation](http://physics.weber.edu/schroeder/fluids/)を参考にしています。Processing版はOpenProcessingで公開しています (<https://www.openprocessing.org/sketch/497312/>)。

ライブラリをimportします。このとき、`%matplotlib nbagg`を付ければipynb上でアニメーションが実行できます。

```python
%matplotlib nbagg
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
```

各種定数を定義していきます。

```python
# Define constants:
height = 80 # lattice dimensions
width = 200
viscosity = 0.02 # fluid viscosity
omega = 1 / (3*viscosity + 0.5) # "relaxation" parameter
u0 = 0.1  # initial and in-flow speed
four9ths = 4.0/9.0 # abbreviations for lattice-Boltzmann weight factors
one9th   = 1.0/9.0
one36th  = 1.0/36.0
```

格子の初期化を(3)式に基づいて行います。

```python
# Initialize all the arrays to steady rightward flow:
n0 = four9ths * (np.ones((height,width)) - 1.5*u0**2) # particle densities along 9 directions
nN = one9th * (np.ones((height,width)) - 1.5*u0**2)
nS = one9th * (np.ones((height,width)) - 1.5*u0**2)
nE = one9th * (np.ones((height,width)) + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
nW = one9th * (np.ones((height,width)) - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
nNE = one36th * (np.ones((height,width)) + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
nSE = one36th * (np.ones((height,width)) + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
nNW = one36th * (np.ones((height,width)) - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
nSW = one36th * (np.ones((height,width)) - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
rho = n0 + nN + nS + nE + nW + nNE + nSE + nNW + nSW # macroscopic density
ux = (nE + nNE + nSE - nW - nNW - nSW) / rho # macroscopic x velocity
uy = (nN + nNE + nNW - nS - nSE - nSW) / rho # macroscopic y velocity
```

障害物の位置を初期化します。このとき、障害物内の粒子の数は0とします。

```python
# Initialize barriers:
barrier = np.zeros((height,width), bool) # True wherever there's a barrier
barrier[int((height/2)-8):int((height/2)+8), int(height/2)] = True			# simple linear barrier
barrierN = np.roll(barrier,  1, axis=0) # sites just north of barriers
barrierS = np.roll(barrier, -1, axis=0) # sites just south of barriers
barrierE = np.roll(barrier,  1, axis=1) # etc.
barrierW = np.roll(barrier, -1, axis=1)
barrierNE = np.roll(barrierN,  1, axis=1)
barrierNW = np.roll(barrierN, -1, axis=1)
barrierSE = np.roll(barrierS,  1, axis=1)
barrierSW = np.roll(barrierS, -1, axis=1)
```

Streaming StepとBounce-back stepです。`np.roll`により仮想粒子を移動させます。このとき, 粒子の進行方向と逆向きの順で格子を選び, 分布関数を更新します.

```python
# Move all particles by one step along their directions of motion (pbc):
def stream():
    global nN, nS, nE, nW, nNE, nNW, nSE, nSW
    nN  = np.roll(nN,   1, axis=0)	# axis 0 is north-south; + direction is north
    nNE = np.roll(nNE,  1, axis=0)
    nNW = np.roll(nNW,  1, axis=0)
    nS  = np.roll(nS,  -1, axis=0)
    nSE = np.roll(nSE, -1, axis=0)
    nSW = np.roll(nSW, -1, axis=0)
    nE  = np.roll(nE,   1, axis=1)	# axis 1 is east-west; + direction is east
    nNE = np.roll(nNE,  1, axis=1)
    nSE = np.roll(nSE,  1, axis=1)
    nW  = np.roll(nW,  -1, axis=1)
    nNW = np.roll(nNW, -1, axis=1)
    nSW = np.roll(nSW, -1, axis=1)
    # Use tricky boolean arrays to handle barrier collisions (bounce-back):
    nN[barrierN] = nS[barrier]
    nS[barrierS] = nN[barrier]
    nE[barrierE] = nW[barrier]
    nW[barrierW] = nE[barrier]
    nNE[barrierNE] = nSW[barrier]
    nNW[barrierNW] = nSE[barrier]
    nSE[barrierSE] = nNW[barrier]
    nSW[barrierSW] = nNE[barrier]
```

Collide stepの関数を定義します。

```python
# Collide particles within each cell to redistribute velocities (could be optimized a little more):
def collide():
    global rho, ux, uy, n0, nN, nS, nE, nW, nNE, nNW, nSE, nSW
    rho = n0 + nN + nS + nE + nW + nNE + nSE + nNW + nSW
    ux = (nE + nNE + nSE - nW - nNW - nSW) / rho
    uy = (nN + nNE + nNW - nS - nSE - nSW) / rho
    ux2 = ux * ux # pre-compute terms used repeatedly...
    uy2 = uy * uy
    u2 = ux2 + uy2
    omu215 = 1 - 1.5*u2 # "one minus u2 times 1.5"
    uxuy = ux * uy
    n0 = (1-omega)*n0 + omega * four9ths * rho * omu215
    nN = (1-omega)*nN + omega * one9th * rho * (omu215 + 3*uy + 4.5*uy2)
    nS = (1-omega)*nS + omega * one9th * rho * (omu215 - 3*uy + 4.5*uy2)
    nE = (1-omega)*nE + omega * one9th * rho * (omu215 + 3*ux + 4.5*ux2)
    nW = (1-omega)*nW + omega * one9th * rho * (omu215 - 3*ux + 4.5*ux2)
    nNE = (1-omega)*nNE + omega * one36th * rho * (omu215 + 3*(ux+uy) + 4.5*(u2+2*uxuy))
    nNW = (1-omega)*nNW + omega * one36th * rho * (omu215 + 3*(-ux+uy) + 4.5*(u2-2*uxuy))
    nSE = (1-omega)*nSE + omega * one36th * rho * (omu215 + 3*(ux-uy) + 4.5*(u2-2*uxuy))
    nSW = (1-omega)*nSW + omega * one36th * rho * (omu215 + 3*(-ux-uy) + 4.5*(u2+2*uxuy))
    # Force steady rightward flow at ends (no need to set 0, N, and S components):
    nE[:,0] = one9th * (1 + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
    nW[:,0] = one9th * (1 - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
    nNE[:,0] = one36th * (1 + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
    nSE[:,0] = one36th * (1 + 3*u0 + 4.5*u0**2 - 1.5*u0**2)
    nNW[:,0] = one36th * (1 - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
    nSW[:,0] = one36th * (1 - 3*u0 + 4.5*u0**2 - 1.5*u0**2)
```

次に流速$\boldsymbol{v}$の回転$\textrm{rot}\ \boldsymbol{v}$の$z$成分を計算する関数を定義します。$\textrm{rot}\ \boldsymbol{v}$の$z$成分は

$$
(\textrm{rot}\ \boldsymbol{v})_z=\frac{\partial v_y}{\partial x}-\frac{\partial v_x}{\partial y}
$$

で表されます。偏微分項の中心差分を取ると,

$$
(\textrm{rot}\ \boldsymbol{v})_z\approx\frac{v_y(x+\epsilon,y)-v_y(x-\epsilon, y)}{2\epsilon}-\frac{v_x(x, y+\epsilon)-v_x(x, y-\epsilon)}{2\epsilon}
$$

となります。流れの速度場が格子状に離散化されていることから, $\epsilon=1$とすべきであるので次式のように計算されます。

$$
(\textrm{rot}\ \boldsymbol{v})_z\approx\frac{v_y(x+1,y)-v_y(x-1, y)}{2}-\frac{v_x(x, y+1)-v_x(x, y-1)}{2}
$$

なお、実装上は2で割ることを省略しています。

```python
# Compute rot of the macroscopic velocity field:
def rot(ux, uy):
    return np.roll(uy,-1,axis=1) - np.roll(uy,1,axis=1) - np.roll(ux,-1,axis=0) + np.roll(ux,1,axis=0)
```

シミュレーションのメインの関数です。`nextFrame`関数を実行することでシミュレーションが進行します。

```python
# Here comes the graphics and animation
theFig = plt.figure(figsize=(8,3))
fluidImage = plt.imshow(rot(ux, uy), origin='lower', norm=plt.Normalize(-.1,.1),
                        cmap=plt.get_cmap('jet'), interpolation='none')

bImageArray = np.zeros((height, width, 4), np.uint8)
bImageArray[barrier,3] = 255
barrierImage = plt.imshow(bImageArray, origin='lower', interpolation='none')

def nextFrame(arg):
    for step in range(20):
        stream()
        collide()
    fluidImage.set_array(rot(ux, uy))
    return (fluidImage, barrierImage) # return the figure elements to redraw

animate = animation.FuncAnimation(theFig, nextFrame, interval=1, blit=True)
plt.show()
```

    <IPython.core.display.Javascript object>

## 参考文献
- Schroeder, D., [Fluid Dynamics Simulation](http://physics.weber.edu/schroeder/fluids/),  (2018年1月30日閲覧)
- Guo, Z. and Shu, C., 2013, Lattice Boltzmann method and its applications in engineering, World Scientific (Singapore)
- 蔦原 道久, 他2名, 1999, 『格子気体法・格子ボルツマン法―新しい数値流体力学の手法』, コロナ社
- 太田 光浩, 他4名, 2015, 『混相流の数値シミュレーション 』, 丸善出版
- Bao, Y. and Meskas, J.,  2011。[Lattice Boltzmann Method for Fluid Simulations](https://cims.nyu.edu/~billbao/report930.pdf), (2018年1月30日閲覧)
- 立石 絢也, 樫山 和男, 2006, 「非圧縮性粘性流体解析のためのCIVA-格子ボルツマン法の精度と安定性」, 『日本計算工学会論文集』, 2006年度号, No.20060008


# Welcome to the WHOI Summer Math Review in Data Analysis!

This course will be taught in **Python** in the format of a Jupyter notebook, which you will be sent following the course! That way, you can try out the exercises interactively as well, as all of the data will be stored in this format for your later use.

**Major Learning Objectives**
- Load netCDF files as well as a FASTA file for biological sequences
- Learn skills for plotting and analyzing data in 2D and 3D
- Linear and multinomial regression
- Statistical tests

### netCDF data type manipulation
The netCDF data we will be working with come from the [UCAR Community Programs Database](https://www.unidata.ucar.edu/software/netcdf/examples/files.html) and include sea surface temperature data which were collected for the purposes of use with the IPCC climate assessment and modeling efforts. The data should all be stored in the same location as this Jupyter notebook for the code to work when you try it out on your own later. It should be in the zip file you receive as part of the course, but it also is freely downloadable from the embedded link.

*Note*: you will also need to have `netCDF4`, a Python package for working with netCDF data, installed. You can install it either by creating a `conda` environment to use with your Jupyter notebook that has this package available, or by downloading the package directly in Anaconda Navigator.

You can opt for the second option by navigating to the "Environments" tab in Anaconda Navigator, searching for `netCDF4` to add to the currently active environment which you used to launch your Jupyter Notebook instance (which should couple together by default), and following the install dialog.

**After** you have `netCDF4` installed, you should be able to call the following import commands to make the libraries accessible to your current notebook instance.

In addition to `netCDF4`, we'll also be using `pandas`, `scipy`, `datetime`, `numpy`, `matplotlib` and potentially `plotnine`. If you don't have any of these packages, you can use the same import procedure on Anaconda Navigator.

```python
import pandas as pd
import numpy as np
import datetime
from scipy import stats
from netCDF4 import Dataset
import matplotlib.pyplot as plt
```

```python
from Bio import SeqIO
```

## A primer on `netCDF`

A huge problem in environmental data is **dimensionality**. Because we have at least 4 dimensions - horizontal space on two axes, vertical space, and the axis of time - it can be super unwieldy to deal with data even from a single site. To get a sense of how big these numbers can be, let's consider a patch of ocean 1 meter by 1 meter in size. Let's say we're only interested in phytoplankton.

So, we can say for the sake of argument we only care about the surface one meter of water, but we need subdivisions of *micro*meters to say anything useful about the critters we're interested in. We need to observe the evolution of a phytoplankton bloom over 10 days, and we need time points every 1 hour to track diversity. All of the sudden, we have:

$$\begin{equation} \textrm{height} \times \textrm{width} \times \textrm{depth} \times \textrm{depth}  = \left( 1 \textrm{ m} \times \frac{10^6 \mu \textrm{m}}{\textrm{m}} \right) \times \left( 1 \textrm{ m} \times \frac{10^6 \mu \textrm{m}}{\textrm{m}} \right) \times \left( 1 \textrm{ m} \times \frac{10^6 \mu \textrm{m}}{\textrm{m}} \right) \times \left( 10 \textrm{ days} \times \frac{24 \textrm{ hours}}{\textrm{day}} \right)\end{equation} = \\2.40 \times 10^{20} \textrm{ hourly snapshots of 1 micrometer size}$$

So...in 1 cubic meter of water, if we use high enough resolution, we can get on the order of $10^{20}$ data points with *hourly* sampling in a single 10-day cruise. That's before we even try any fancy tricks with data processing. This is why we need `netCDF`!

You are free to store your data as a CSV or other spreadsheet, but it just won't be packed as tidily as a `netCDF` can be, which is why so many earth scientists use this format for their 3-dimensional data. A lot of the physical oceanographic resources that you can find will be stored as a `netCDF` file (you will know this by their `.nc` extension).

## Getting started with the `netcdf4` package

If you've worked with `netCDF` files before, you've almost definitely used `MATLAB`. In this course, we'll try out an alternative option: the `netcdf4` package in `Python`. This should help those of you that use `Python` for most everything else in your research!

To get started, we want to perform the analogous operation to the `ncdisp` functionality in `MATLAB`. This prints out the variables inside of our `netCDF` file, so that we have a bit of an idea about how to best access them.

`netcdf4` does *not* have this function. However, we can still check out our variable names. To begin, let's use the `Dataset` package that we loaded.

### Loading packages in Python

When we load packages in Python, we can use the `from` directive to specifically load a particular function that we plan to use from inside a function a lot. That's what we did with `Dataset` above:

```
from netCDF4 import Dataset
```

This tells `Python` to be selective, and give us the function that we need from this package. This is the most important function in `netCDF`!

```python
surface_temperatures = Dataset('data/tos_O1_2001-2002.nc')
```

#### *Remember*: you need to make sure that you have the NC file in the `data` directory for this to work!

Let's take a look at what we have inside this `netCDF` file!

First, let's check out the `ncattrs`, which are a grouping of all of the *attributes*, or metadata, that characterize our `netCDF` file.

```python
surface_temperatures.ncattrs()
```

We can grab an individual `netCDF` attribute by specifying it as a slot or name within this `Python` object.

```python
surface_temperatures.title
```

Or, we can get a more global view of all of the variables and data inside of our `netCDF` object.

```python
print(surface_temperatures)
```

### Zooming in on a few of these features

```
<class 'netCDF4._netCDF4.Dataset'>
root group (NETCDF3_CLASSIC data model, file format NETCDF3):
    title: IPSL  model output prepared for IPCC Fourth Assessment SRES A2 experiment
    institution: IPSL (Institut Pierre Simon Laplace, Paris, France)
    source: IPSL-CM4_v1 (2003) : atmosphere : LMDZ (IPSL-CM4_IPCC, 96x71x19) ; ocean ORCA2 (ipsl_cm4_v1_8, 2x2L31); sea ice LIM (ipsl_cm4_v
    contact: Sebastien Denvil, sebastien.denvil@ipsl.jussieu.fr
    project_id: IPCC Fourth Assessment
    table_id: Table O1 (13 November 2004)
    experiment_id: SRES A2 experiment
    realization: 1
    cmor_version: 0.96
    Conventions: CF-1.0
    history: YYYY/MM/JJ: data generated; YYYY/MM/JJ+1 data transformed  At 16:37:23 on 01/11/2005, CMOR rewrote data to comply with CF standards and IPCC Fourth Assessment requirements
    references: Dufresne et al, Journal of Climate, 2015, vol XX, p 136
    comment: Test drive
    dimensions(sizes): lon(180), lat(170), time(24), bnds(2)
    variables(dimensions): float64 lon(lon), float64 lon_bnds(lon,bnds), float64 lat(lat), float64 lat_bnds(lat,bnds), float64 time(time), float64 time_bnds(time,bnds), float32 tos(time,lat,lon)
    groups:
```

A few things jump out right away:

- dimensions(sizes): lon(180), lat(170), time(24), bnds(2)
- variables(dimensions): float64 lon(lon), float64 lon_bnds(lon,bnds), float64 lat(lat), float64 lat_bnds(lat,bnds), float64 time(time), float64 time_bnds(time,bnds), float32 tos(time,lat,lon)

We also have some metadata about where the data came from and what it was used for.

### Let's try grabbing out some of these data variables!

```python
surface_temperatures.variables
```

Having a list of variables, we can extract the actual data associated with each variable using *array syntax* in `Python`.

```python
 surface_temperatures['lat'][:]
```

But **remember**, these are multi-dimensional values (that are compatible with the other values in this `netCDF` file). So we can't just use them like a column in a `DataFrame`, like you might have been introduced to in the `Python` course.

To get started, let's subset our data out (the first time point), so that it's a bit easier to work with.

If you remember from above, our "tos" variable has three dimensions: time, latitude, and longitude, in that order. So we'll take the zero-indexed first entry in `tos`, and then start with all of the latitude and longitude values.

```python
sst = surface_temperatures['tos'][0,:,:]
```

```python
sst
```

```python
plt.imshow(sst, interpolation='none')
```

This looks recognizable, but it's pretty...upside down. Let's try naïvely plotting out these data for sea surface temperatures (the `tos` variable)!  This time, let's try a smaller slice on latitude and longitude.

```python
sst = surface_temperatures['tos'][0,100:400,100:400]
plt.imshow(sst, interpolation='none')
plt.show()
```

**Does anyone recognize this map**? Notice that here, we have some null values, which are being masked as white points on the plot above. Let's convert these to black, and use the old friend of the physical oceanographer: the "jetcolors" colormap (though it's important to note that there are a lot of [opinions](https://gorelik.net/2020/08/17/what-is-the-biggest-problem-of-the-jet-and-rainbow-color-maps-and-why-is-it-not-as-evil-as-i-thought/) on whether this is a totally terrible colormap to use).

```python
cmap = plt.cm.jet
cmap.set_bad('black',1.) # set the missing points to black
plt.imshow(sst, interpolation='nearest', cmap=cmap)
```

Is it any clearer now where we might be? To figure it out, let's use the latitude and longitude variables for the same slice of data. For this, we have to combine three different arrays, so we'll use the function `pcolor` to specify different axes.

```python
lon = surface_temperatures['lon'][100:]
lat = surface_temperatures['lat'][100:]
sst = surface_temperatures['tos'][0, 100:, 100:]

# We use vmin and vmax to set the bounds of the colorbar. Notice that these measurements are in Kelvin!
plt.pcolor(lon, lat, sst, cmap=cmap, vmin=270, vmax=300)
```

Much better! Now we have the first time point of sea surface temperatures for the North Atlantic and Pacific Oceans nearest to the U.S.

```python
print(surface_temperatures.variables["time"])
surface_temperatures["time"][0] # in days since January 1, 2001.
```

So, the data point we initially pulled is associated with 15 January, 2001. We can get into summer 2001 (August 18th) by taking the fourth data point in this sea surface temperature data and plotting it:

```python
surface_temperatures["time"][4]

lon = np.subtract(surface_temperatures['lon'],180)
lat = surface_temperatures['lat']
sst = surface_temperatures['tos'][1,:,:]

fig, axs = plt.subplots(ncols=1, nrows=2, subplot_kw={'projection': "mollweide"})

pcm1 = axs[0].pcolormesh(np.radians(lon), np.radians(lat), sst, cmap=cmap, vmin=270, vmax=310)
axs[0].title.set_text('15 January')
fig.colorbar(pcm1, ax=axs[0])

lon = np.subtract(surface_temperatures['lon'],180)
lat = surface_temperatures['lat']
sst = surface_temperatures['tos'][20,:,:]

pcm2 = axs[1].pcolormesh(np.radians(lon), np.radians(lat), sst, cmap=cmap, vmin=270, vmax=310)
axs[1].title.set_text('18 August')
fig.colorbar(pcm2, ax=axs[1])
```

It's a little difficult to see anything here, let's try zooming in a little further.

```python
lon = surface_temperatures['lon'][100:]
lat = surface_temperatures['lat'][100:]

surface_temperatures["time"][4]

lon = np.subtract(surface_temperatures['lon'][100:],180)
lat = surface_temperatures['lat'][100:]
sst = surface_temperatures['tos'][1,100:,100:]

fig, axs = plt.subplots(ncols=1, nrows=2)

pcm1 = axs[0].pcolormesh(np.radians(lon), np.radians(lat), sst, cmap=cmap, vmin=270, vmax=300)
axs[0].title.set_text('15 January')
fig.colorbar(pcm1, ax=axs[0])

lon = np.subtract(surface_temperatures['lon'][100:],180)
lat = surface_temperatures['lat'][100:]
sst = surface_temperatures['tos'][4,100:,100:]

pcm2 = axs[1].pcolormesh(np.radians(lon), np.radians(lat), sst, cmap=cmap, vmin=270, vmax=300)
axs[1].title.set_text('18 August')
fig.colorbar(pcm2, ax=axs[1])
```

**Note that there are better ways to plot these data, such as `xarray`, but this works for an initial look**

We can start to see some differences in the surface temperatures, but overall this is still pretty obscure. Instead, let's go backwards and convert our data to 1-dimensional data. Then, we can calculate the
- mean temperature in each region, for each season
- repeat this process for some delimited regions across every time point in our dataset
- we can convert the numeric time to dates using some handy packages in `Python`

### Selecting and processing regions

We select the following regions of the ocean:
- **REGION 1** - 290-310 longitude, 30-50 latitude (U.S. East Coast)
- **REGION 2** - 230-250 longitude, 30-50 latitude (U.S. West Coast)
- **REGION 3** - 320-350 longitude, 60-70 latitude (Greenland Sea)

To take the average over the full area for each time point in the sampling period.

```python
## REGION 1
long_indices = np.where((surface_temperatures['lon'][:] > 290) & (surface_temperatures['lon'][:] < 310))
lon = np.subtract(surface_temperatures['lon'][long_indices],180)
lat_indices = np.where((surface_temperatures['lat'][:] > 30) & (surface_temperatures['lat'][:] < 50))
lat = surface_temperatures['lat'][lat_indices]
sst = surface_temperatures['tos'][:,lat_indices[0],long_indices[0]]

# take mean over axis 1&2 (axis 0 is time)
mean_sst_region1 = np.mean(sst,(1, 2))

## REGION 2
long_indices = np.where((surface_temperatures['lon'][:] > 230) & (surface_temperatures['lon'][:] < 250))
lon = np.subtract(surface_temperatures['lon'][long_indices],180)
lat_indices = np.where((surface_temperatures['lat'][:] > 30) & (surface_temperatures['lat'][:] < 50))
lat = surface_temperatures['lat'][lat_indices]
sst = surface_temperatures['tos'][:,lat_indices[0],long_indices[0]]

# take mean over axis 1&2 (axis 0 is time)
mean_sst_region2 = np.mean(sst,(1, 2))

## REGION 3
long_indices = np.where((surface_temperatures['lon'][:] > 320) & (surface_temperatures['lon'][:] < 350))
lon = np.subtract(surface_temperatures['lon'][long_indices],180)
lat_indices = np.where((surface_temperatures['lat'][:] > 60) & (surface_temperatures['lat'][:] < 70))
lat = surface_temperatures['lat'][lat_indices]
sst = surface_temperatures['tos'][:,lat_indices[0],long_indices[0]]

# take mean over axis 1&2 (axis 0 is time)
mean_sst_region3 = np.mean(sst,(1, 2))
```

Now, we'll play with converting the time over, using the `datetime` package in Python.

```python
times_all = surface_temperatures['time'][:].data # all of the times in days
```

```python
date_start = datetime.datetime.strptime("1/1/2001", "%m/%d/%Y") # the start date in datetime format

timepoint_dates = [date_start + datetime.timedelta(days=curr) for curr in times_all]
```

Now, let's organize all of our data into a tidy `pandas` `DataFrame` to work with it more easily!

```python
sst_summary = pd.DataFrame({"Dates": timepoint_dates,
              "Region-1": mean_sst_region1,
              "Region-2": mean_sst_region2,
              "Region-3": mean_sst_region3})
sst_summary
```

```python
sst_summary.plot(x="Dates")
```

```python
## We could also convert our data to a tidier format
pd.wide_to_long(sst_summary,stubnames="Region",i="Dates",j="Number",sep="-").reset_index()
```

## Answering a research question with these data
Now that we've seen a few ways to manipulate and plot our data, perhaps we would like to give it a go to perform *statistical regression* on our data to establish some trend over space or time.

In this case, we don't really have enough time points to establish any kind of time trend, using the dataset we picked. So instead, let's pick out a few different latitudes and see if we can establish a trend via _linear regression_.

### Linear Regression

In linear regression, we have the goal of representing our data with a function in the format:

$$\begin{equation} y_{est} = \beta_0 + \beta_1 x + \epsilon \end{equation}$$

Where $y_{est}$ is some estimated response variable, $\beta_1$ and $\beta_2$ are *parameters* of the equation (you may have seen them expressed as $b$ and $m$, the y-intercept and slope, respectively), and $\epsilon$ is some error, because this regression procedure we're following is imperfect. In our case, because we're going to be modeling the changes in sea surface temperature (in Kelvin) along some latitude axis, we can name SST $s$ and latitude $a$ and have:

$$\begin{equation} \hat{s} = \beta_0 + \beta_1 a + \epsilon \end{equation}$$

#### Using linear algebra to estimate parameters

Where we use the notation $\hat{s}$ to denote that these are _estimated_ values for sea surface temperature. Because it wasn't covered in a previous math review, the way that we actually estimate these parameters $\beta_0$ and $\beta_1$, the unique values which describe our specific data, is using linear algebra. We can imagine our data in matrix-vector format, where $n$ is the total number of observations that we have:

$$\left[\begin{array}{c} s_0 \\ s_1 \\ \vdots \\ s_n \end{array}\right] = \left[ \begin{array}{cc} 1& a_0 \\ 1 & a_2 \\ \vdots & \vdots \\ 1 & a_n \end{array} \right] \left[ \begin{array}{c} \beta_0 \\ \beta_1 \end{array} \right]$$

Because $\beta_0$ is just multiplied by the identity to keep $\beta_0$ as the values, and $\beta_1$ has as its coefficient the latitude observations that we're using to generate a *straight-line model*.

We can say in general that this equation has the form $\mathbf{s} = \mathbf{E}\mathbf{b}$, where we use a capital letter to denote a matrix, in this case the matrix of the multipliers on our coefficients, and a lowercase letter to represent a vector (our SST predictions and the $\beta$ coefficients). We use **boldface** for both of these to indicate their multi-dimensionality.

So for every way that we define the parameters, we're going to have some error that we wish to minimize. Because the equation of the prediction is $\beta_0 + \beta_1 a$, this error can be represented as:

$$\textrm{error}(\beta_0, \beta_1) = \sum_{t=1}^n (s_t - (\beta_0 + \beta_1 a_t))^2 $$

So we solve the linear algebra-based matrix-form equation above to make the error expression we just wrote out *as small as possible*. This yields the following formula for the $\beta$ coefficients:

$$\mathbf{\beta}=(E^TE)^{-1}E^T s$$

Which has a derivation that we won't go through here, since we're talking through how to analyze data using available toolkits (but it's a cool process to know!).

#### Estimating percentage of explained variance using $R^2$

A metric we often use to express the "percentage of variance explained", which is just a fancy way of how much of the trend we captured using the summary linear model, is called $R^2$, or the **coefficient of determination**. The formula for this value is:

$$R^2 = 1- \frac{\sum_{t=1}^n (s_t - (\beta_0 + \beta_1 a_t))^2 }{\sum_{t=1}^n (s_t - \bar{s})}$$

So in this case, we're calculating the error associated with our regression process ($\hat{s} = \beta_0 + \beta_1 a_t$) and dividing that by the difference of each one of our dependent variable values (SST measurements) from the *mean* of all of the SST measurements ($\bar{s}$). This gives us a ratio of how far off we are, relative to how different _all_ of the values we have are from the mean. We subtract this ratio from 1 to get how much of the variation _is_ explained, as opposed to expressing how far off we are.

## Extracting some values to regress on
This time, we'll write a *function* in `Python` in order to extract the mean SST at each latitude over the course of all time points.

```python
# A function which takes as input a set of latitude and longitude bounds
# and then calculates & returns mean SST across time and longitude.
def extract_mean_sst_by_lat(min_lat, max_lat, min_lon, max_lon, sst_frame):
    long_indices = np.where((sst_frame['lon'][:] > min_lon) & \
                            (sst_frame['lon'][:] < max_lon))
    lat_indices = np.where((sst_frame['lat'][:] >= min_lat) & \
                           (sst_frame['lat'][:] <= max_lat)) # use >= so that we can use a single point

    lon = np.subtract(sst_frame['lon'][long_indices],180)
    lat = sst_frame['lat'][lat_indices]
    sst = surface_temperatures['tos'][:,lat_indices[0],long_indices[0]]

    mean_sst = np.mean(sst,(0,2))
    latitude_frame = pd.DataFrame({"Latitude": lat, "SST": mean_sst})

    return latitude_frame
```

We're just interested in _each_ available latitude across all times and longitudes, so we just give placeholder values for maximum.

```python
mean_by_lat = extract_mean_sst_by_lat(0, 500, 0, 500, surface_temperatures)
mean_by_lat.plot(x="Latitude",y="SST")
```

We will use `SciPy` to estimate a quick linear regression on these not-totally-linear data.

```python
scipy_linreg = scipy.stats.linregress(x = mean_by_lat.Latitude, y = mean_by_lat.SST)
scipy_linreg
```

Notice that `SciPy` is giving us $R$, which is correlation, and not $R^2$. We can see that the correlation between latitude and SST is negative (increasing latitude - closer to the poles - results in lower mean SST across longitudes). We can calculate $R^2$ to get percentage of variation explained:

```python
scipy_linreg.rvalue**2
```

So using this simple linear model, we can explain 95.4% of the variation in SST with latitude (*note*: correlation is not causation!).

```python
mean_by_lat["Regression"] = scipy_linreg.slope * mean_by_lat.Latitude + scipy_linreg.intercept

# Let's plot our data and regression using PyPlot
plt.plot(mean_by_lat.Latitude, mean_by_lat.Regression, color = "red", label = "Regression")
plt.plot(mean_by_lat.Latitude, mean_by_lat.SST, label = "Original Data")
plt.legend()
```

## Part 2: Biological Data and `FASTA` files via `SeqIO`!

We'll do a short example of parsing and investigating data from the mouse genome (*Mus musculus*). This is just a collection of predicted genes from one location in the mouse genome that I have subsetted (so it's a small fraction of the overall data just for the first chromosome) for the purposes of this exercise.

```python
mouse_genome = list(SeqIO.parse("data/Mus_musculus.GRCm39.cds.subsetted.fa", "fasta"))
```

`SeqIO` allows us to treat every sequence in our FASTA file as an individual record and process it according to characteristics extracted from the `FASTA` format sequence file.

```python
mouse_genome[0]
```

The `SeqRecord` data type will always come with a field `seq` that is associated with every record inside of the `FASTA` file.

```python
mouse_genome[0].seq
```

We can perform typical array operations on these character array sequence entries.

```python
len(mouse_genome[0].seq)
```

### Performing list operations on a character array

If we treat our string of mouse sequences as character arrays, we can perform _array operations_ on those data.

```python
sequence_as_list = list(mouse_genome[0])
```

Similarly, we can use **list comprehension** within Python to treat _all_ our sequences as lists, and to process the data inside each list.

In bioinformatics, one metric we often use for a quick assessment of a sequence is **GC content**. You may remember from an introductory biology course that guanine and cytosine are a purine and a pyrimidine, respectively, that bind together within DNA (or RNA). When these two molecules latch together, they use *3 hydrogen bonds*. This is in contrast to the bond between adenine and thymine (*A* and *T*, respectively), which only has two hydrogen bonds.

This small difference changes the melting temperature of DNA molecules that contain more C & G relative to the A & T that they contain. This is important for molecular reactions that we impose on DNA, and it also has pretty major implications for organisms...it's a common recommendation to make *taxonomic classifications* based on GC-content.

So, let's measure the GC content of some sequences!

```python
GC_content_per_seq = [(list(curr).count("C") + list(curr).count("G"))/len(list(curr)) for curr in mouse_genome]
```

```python
plt.hist(GC_content_per_seq)
plt.xlim([0,1])
```


# Newton-Euler equations for rigid bodies

> Marcos Duarte, Renato Naville Watanabe

> Laboratory of Biomechanics and Motor Control ([http://pesquisa.ufabc.edu.br/bmclab](http://pesquisa.ufabc.edu.br/bmclab))
> Federal University of ABC, Brazil

Table of Contents

1  Mechanics

2  Recapitulation

2.1  Newton's laws of motion

2.2  Linear momentum

2.3  Angular momentum

2.4  Torque (moment of force)

2.5  Moment of inertia

3  Principle of transmissibility and Principle of moments

3.1  Principle of transmissibility

3.2  Varignon's Theorem (Principle of Moments)

4  Equivalent systems

5  Mechanics (dynamics) of rigid bodies

6  Euler's laws of motion (for a rigid body)

6.1  Derivation of the Euler's laws of motion

7  Problems

8  References

## Mechanics

In Mechanics we are interested in the study of motion (including deformation) and forces (and the relation between them) of anything in nature.

As a good rule of thumb, we model the phenomenon of interest as simple as possible, with just enough complexity to understand the phenomenon.

For example, we could model a person jumping as a particle (the center of gravity, with no size) moving in one direction (the vertical) if all we want is to estimate the jump height and relate that to the external forces to the human body. So, mechanics of a particle might be all we need.

However, if the person jumps and performs a somersault, to understand this last part of the motion we have to model the human body as one of more objects which displaces and rotates in two or three dimensions. In this case, we would need what is called mechanics of rigid bodies.

If, besides the gross motions of the segments of the body, we are interested in understanding the deformation in the the human body segments and tissues, now we would have to describe the mechanical behavior of the body (e.g., how it deforms) under the action of forces. In this case we would have to include some constitutive laws describing the mechanical properties of the body.

In the chapter mechanics of rigid bodies, the body deformation is neglected, i.e., the distance between every pair of points in the body is considered constant. Consequently, the position and orientation of a rigid body can be completely described by a corresponding coordinate system attached to it.

Let's review some Newton's laws of motion for a particle and then extend these equations to motion of rigid bodies.

## Recapitulation

### Newton's laws of motion

The Newton's laws of motion describe the relationship between the forces acting on a body and the resultant linear motion due to those forces:

- **First law**: An object will remain at rest or in uniform motion in a straight line unless an external force acts on the body.
- **Second law**: The acceleration of an object is directly proportional to the net force acting on the object and inversely proportional to the mass of the object: $\mathbf{\vec{F}} = m\mathbf{\vec{a}}.$
- **Third law**: Whenever an object exerts a force $\mathbf{\vec{F}}_1$ (action) on a second object, this second object simultaneously exerts a force $\mathbf{\vec{F}}_2$ on the first object with the same magnitude but opposite direction (reaction): $\mathbf{\vec{F}}_2 = −\mathbf{\vec{F}}_1.$

### Linear momentum

The linear momentum, or quantity of motion, is defined as the product between mass and velocity:

$$ \mathbf{\vec{L}} = m\mathbf{\vec{v}} $$

### Angular momentum

In analogy to the linear momentum, the angular momentum is the quantity of movement of a particle rotating around an axis passing through any point O at a distance $\mathbf{\vec{r}}$ to the particle:

$$ \mathbf{\vec{H_O}} = \mathbf{\vec{r_{O}}} \times \mathbf{\vec{L}} $$

### Torque (moment of force)

In analogy to the second Newton's law for the linear case, torque or moment of force (or simply moment) is the time derivative of angular momentum:

$$ \mathbf{\vec{M_O}} = \frac{d\mathbf{\vec{H_O}}}{dt} = \frac{d}{dt}(\mathbf{\mathbf{\vec{r}} \times \mathbf{\vec{L}}}) = \frac{d\mathbf{\vec{r_O}}}{dt} \times \mathbf{\vec{L}} + \mathbf{\vec{r_O}} \times \frac{d\mathbf{\vec{L}}}{dt} = \frac{d\mathbf{\vec{r_O}}}{dt} \times (m\mathbf{\mathbf{\vec{v}}}) + \mathbf{\vec{r_O}} \times \frac{d(m\mathbf{\vec{v}})}{dt} = \mathbf{\vec{v}} \times (m\mathbf{\mathbf{\vec{v}}}) + \mathbf{\vec{r_O}} \times \frac{d(m\mathbf{\vec{v}})}{dt} = 0 + \mathbf{\vec{r_O}} \times \mathbf{\vec{F}} $$

$$ \mathbf{\vec{M_O}} = \mathbf{\vec{r_O}} \times \mathbf{\vec{F}} $$

$$ \mathbf{\vec{M_O}} = (r_{O_x}\:\mathbf{\hat{i}}+r_{O_y}\:\mathbf{\hat{j}}+r_{O_z}\:\mathbf{\hat{k}}) \times  (F_x\:\mathbf{\hat{i}}+F_y\:\mathbf{\hat{j}}+F_z\:\mathbf{\hat{k}}) $$

Where the symbol $\times$ stands for the [cross product](http://nbviewer.ipython.org/github/demotu/BMC/blob/master/notebooks/ScalarVector.ipynb) mathematical function.
The the moment of force can be calculated as the determinant of the following matrix:

$$ \mathbf{\vec{M_O}} = \det \begin{bmatrix}
\mathbf{\hat{i}} & \mathbf{\hat{j}} & \mathbf{\hat{k}} \\
r_{O_x} & r_{O_y} & r_{O_z} \\
F_x & F_y & F_z
\end{bmatrix} $$

$$ \mathbf{\vec{M_O}} = (r_{O_y}F_z-r_{O_z}F_y)\mathbf{\hat{i}}+(r_{O_z}F_x-r_{O_x}F_z)\mathbf{\hat{j}}+(r_{O_x}F_y-r_{O_y}F_x)\mathbf{\hat{k}} $$

The magnitude of moment of force can also be calculated by the geometric equivalent formula:

$$ ||\mathbf{\vec{M_O}}|| = ||\mathbf{r_O} \times \mathbf{\vec{F}}|| = ||\mathbf{\vec{r_O}}||\:||\mathbf{\vec{F}}||\sin(\theta) $$

Where $\theta$ is the angle between the vectors $\mathbf{\vec{r_O}}$ and $\mathbf{\vec{F}}$.

The animation below illustrates the relationship between force, torque, and momentum vectors:

Figure. Relationship between force ($\mathbf{\vec{F}}$), torque ($\mathbf{\vec{M}}$), linear momentum ($\mathbf{\vec{L}}$) and angular momentum ($\mathbf{\vec{H}}$). Adapted from [Wikipedia](http://en.wikipedia.org/wiki/File:Torque_animation.gif).

### Moment of inertia

Let's use the example above, where $\mathbf{\vec{r_O}}$ and $\mathbf{\vec{F}}$ are orthogonal and derive an expression for the magnitude of these quantities as the equivalent of Newton's second law for angular motion:

$$ M_O = r_OF = r_Oma $$

Replacing the linear acceleration $a$ by the angular acceleration $\alpha$:

$$ M_O = r_Omr_O\alpha = mr_O^2 \alpha $$

In analogy to Newton's second law, where the constant of proportionality between $a$ and $F$ is called inertia (mass), the constant of proportionality between $M_O$ and $\alpha$ is called rotational inertia or moment of inertia, $I_O=mr_O^2$ for a particle with mass $m$ rotating at a distance $r$ from the center of rotation O.

## Principle of transmissibility and Principle of moments

On the effects of forces, there are two important principles:

### Principle of transmissibility

> *For rigid bodies with no deformation, an external force can be applied at any point on its line of action without changing the resultant effect of the force.*

### Varignon's Theorem (Principle of Moments)

> *The moment of a force about a point is equal to the sum of moments of the components of the force about the same point.*
Note that the components of the force don't need to be orthogonal.

## Equivalent systems

A set of forces and moments is considered equivalent if its resultant force and sum of the moments computed relative to a given point are the same. Normally, we want to reduce all the forces and moments being applied to a body into a single force and a single moment.

We have done this with particles for the resultant force. The resultant force is simply the sum of all the forces being applied to the body.

\begin{equation}
    \vec{\bf{F}} = \sum\limits_{i=1}^n \vec{\bf{F_i}}
\end{equation}

where $\vec{\bf{F_i}}$ is each force applied to the body.

Similarly, the total moment applied to the body relative to a point O is:

\begin{equation}
    \vec{\bf{M_O}} = \sum\limits_{i}\vec{\bf{r_{i/O}}} \times \vec{\bf{F_i}} + \sum\limits_{i}\vec{\bf{M_{F_i}}}
\end{equation}

where $\vec{\bf{r_{i/O}}}$ is the vector from the point O to the point where the force $\vec{\bf{F_i}}$ is being applied and $\vec{\bf{M_{F_i}}}$ are the free moments.

Free moments are moments created by a pair of forces. They do not sum um to the resultant force, since they have have the same magnitude and opposite directions, but create a moment because there is a distance between these forces.

But where the resultant force should be applied in the body? If the resultant force were applied to any point different from the point O, it would produce an additional  moment to the body relative to point O. So, the resultant force must be applied to the point O.

So, any set of forces can be reduced to a moment relative to a chosen point O and a resultant force applied to the point O.

To compute the resultant force and moment relative to another point O', the new moment is:

\begin{equation}
    \vec{\bf{M_{O'}}} = \vec{\bf{M_O}} + \vec{\bf{r_{O'/O}}} \times \vec{\bf{F}}
\end{equation}

And the resultant force is the same.

It is worth to note that if the resultant force  $\vec{\bf{F}}$ is zero, than the moment is the same relative to any point.

## Mechanics (dynamics) of rigid bodies

A [rigid body](https://en.wikipedia.org/wiki/Rigid_body) is a model (an idealization) for a body in which deformation is neglected, i.e., the distance between every pair of points in the body is considered constant. This definition also also implies that the total mass of a rigid body is constant.

Consequently, the motion of a rigid body can be completely described by its pose (position and orientation) in space. In a three-dimensional space, at least three coordinates and three angles are necessary to describe the pose of the rigid body, totalizing six degrees of freedom for a rigid body. This also implies that we will need six equations of motion for these components to describe the dynamics of a rigid body.

## Euler's laws of motion (for a rigid body)

Euler's laws of motion extend Newton's laws of motion for particles for the motion of a rigid body.

**First law**: The linear momentum of a body is equal to the product of the mass of the body and the velocity of its center of mass:

$$ \mathbf{\vec{L}} = m\mathbf{\vec{v}}_{cm} $$

And calculating the time derivative of this equation:

$$ \mathbf{\vec{F}} = m\mathbf{\vec{a}}_{cm} $$

**Second law**: The rate of change of angular momentum about a point that is fixed in an inertial reference frame is equal to the resultant external moment of force about that point:

$$ \mathbf{\vec{M_O}} = \frac{d\mathbf{\vec{H_O}}}{dt} $$

### Derivation of the Euler's laws of motion

**First law**:

The sum of the linear momentum of all the particles of a rigid body (considering the body as a discrete sum of elements, but this also holds for the continuous case):

$$ \mathbf{\vec{L}} = \sum m_i\mathbf{\vec{v}}_i $$

Looking at the definition of center of mass:

$$ \mathbf{\vec{r}}_{cm} = \frac{1}{m_{B}}\sum m_{i}\mathbf{\vec{r}}_i \quad \text{where} \quad m_{B} = \sum m_{i} $$

By differentiation, the velocity of the center of mass is:

$$ \mathbf{\vec{v}}_{cm} = \frac{1}{m_{B}}\sum m_{i}\mathbf{\vec{v}}_i $$

And finally:

$$ \mathbf{\vec{L}} = m_{B} \mathbf{\vec{v}}_{cm} = m_B \mathbf{\vec{v}}_{cm} $$

We can get the second equation of the first law calculating the time derivative of the equation above.
Another way to derive this second equation is considering the effects of all forces acting on each particle of the rigid body and apply Newton's second law to them:

$$ \sum \mathbf{\vec{F}}_i = \sum m_i\mathbf{\vec{a}}_i $$

With respect to the origin of these forces, they can be divided in two types: external and internal forces to the rigid body. Internal forces are interaction forces between particles inside the body and because of Newton's third law (action and reaction) they cancel each other. So, the equation above becomes:

$$ \sum \mathbf{\vec{F}}_{external} = \sum m_i\mathbf{\vec{a}}_i $$

But the acceleration of the center of mass is:

$$ \mathbf{\vec{a}}_{cm} = \frac{1}{m_B}\sum m_{i}\mathbf{\vec{a}}_i $$

And finally:

$$ \mathbf{\vec{F}} = \sum \mathbf{\vec{F}}_{external} = m_B\mathbf{\vec{a}}_{cm} $$

This means that for a rigid body the internal forces between the particles of the body do not contribute to changing the total momentum nor changing the acceleration of the center of mass.

**Second law**:

For a complete derivation of the second Euler's law of motion, see for example Taylor (2005) or [http://emweb.unl.edu/NEGAHBAN/EM373/note19/note19.htm](http://emweb.unl.edu/NEGAHBAN/EM373/note19/note19.htm).

Let's derive the second Euler's law of motion for a simpler case of a rigid body rotating in a plane.

First, a general consideration about the total angular momentum of a rotting rigid body:
The total angular momentum of a rigid body rotating around a point $O$ can be expressed as the angular momentum of the body center of mass around the point $O$ plus the sum of the angular momentum of each particle around the body center of mass (for a proof see page 368 of Taylor, 2005):

$$ \mathbf{\vec{H_O}} = \mathbf{\vec{r}}_{cm/O} \times m\mathbf{\vec{v}}_{cm/O} + \sum \mathbf{\vec{r}}_{i/cm} \times m_i\mathbf{\vec{v}}_{i/cm} $$

For a two-dimensional case, where the rigid body rotates around its own center of mass and also rotates around another parallel axis (fixed), the second term of the right side of the equation above can be simplified to $\sum (m_i\mathbf{r}^2_{i/cm}) \mathbf{\vec{\omega}}$ and calculating the time derivative of the whole equation,

$$ \mathbf{\vec{M_O}} = \frac{d\mathbf{\vec{H_O}}}{dt} \rightarrow \mathbf{\vec{M_O}} = \frac{d\left( \mathbf{\vec{r}}_{cm/O} \times m\mathbf{\vec{v}}_{cm/O} + \sum \mathbf{\vec{r}}_{i/cm} \times m_i\mathbf{\vec{v}}_{i/cm} \right)}{dt}$$

the second Euler's law of motion simplifies to:

$$ \mathbf{\vec{M_O}} = \mathbf{\vec{r}}_{cm/O} \times m\mathbf{\vec{a}}_{cm} + I_{cm} \mathbf{\vec{\alpha}} $$

where $\mathbf{\vec{r}}_{cm}$ is the position vector of the center of mass with respect to the point $O$ about which moments are summed, $\mathbf{\vec{\alpha}}$ is the angular acceleration of the body about its center of mass, and $I_{cm}$ is the moment of inertia of the body about its center of mass.

If $d$ is the (shortest) distance between the point $O$ and the line of the acceleration vector, then the equation above becomes:

$$ \mathbf{M} = ma_{cm}d + I_{cm} \mathbf{\alpha} $$

Note that if the acceleration of the center of mass is zero or the sum of moments of force is calculated around the center of mass (then $\mathbf{r}_{cm}=0$), this case of rotation in a plane simplifies to the well-known simple equation:

$$ \mathbf{\vec{M_{cm}}} = I_{cm} \mathbf{\vec{\alpha}} $$

*Three-dimensional case*

In the three-dimensional space, if we describe the rotation of a rigid body using a rotating reference frame with axes parallel to the principal axes of inertia (referred by the subscripts 1,2,3) of the body, the Euler's second law becomes:

$$ M_1 = I_1\dot{\omega_1} + (I_3-I_2)\omega_2\omega_3 $$

$$ M_2 = I_2\dot{\omega_2} + (I_1-I_3)\omega_3\omega_1 $$

$$ M_3 = I_3\dot{\omega_3} + (I_2-I_1)\omega_1\omega_2 $$

## Problems

1. (Recap.) Solve problems 12.2.1, 12.2..2, 12.2.9, 12.2.11 and 12.2.21 of Ruina and Rudra (2019).

2. Calculate the magnitude of the moment about the base point *O* of the 600-N force in five different ways for the structure shown below (hint: use the equation for torque in different ways, and also the principles of moments and of transmissibility).

## References

- Ruina A, Rudra P (2019) [Introduction to Statics and Dynamics](http://ruina.tam.cornell.edu/Book/index.html). Oxford University Press.
- Taylor JR (2005) [Classical Mechanics](https://books.google.com.br/books?id=P1kCtNr-pJsC). University Science Books.

```python

```