# The Riemann Solution on $\tilde{S}_i$ using HLLE
### Author: Patrick Nelson

This notebook documents the function from the original `GiRaFFE` that calculates the flux for $\tilde{S}_i$ according to the method of Harten, Lax, von Leer, and Einfeldt (HLLE), assuming that we have calculated the values of the velocity and magnetic field on the cell faces according to the piecewise-parabolic method (PPM) of [Colella and Woodward (1984)](https://crd.lbl.gov/assets/pubs_presos/AMCS/ANAG/A141984.pdf), modified for the case of GRFFE.

**Notebook Status:**  Validated against the original `GiRaFFE`

**Validation Notes:** Everything should be written at this point, but we need to finish the rest of the algorithm before we can validate this.

The differential equations that `GiRaFFE` evolves are written in conservation form, and thus have two different terms that contribute to the time evolution of some quantity: the flux term and the source term. The PPM method is what the original `GiRaFFE` uses to handle the flux term; hopefully, using this instead of finite-differencing will fix some of the problems we've been having with `GiRaFFE_NRPy`.

In GRFFE, the evolution equation for the Poynting flux $\tilde{S}_i$ is given as
$$
\boxed{\partial_t \tilde{S}_i + \underbrace{ \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right)}_{\rm Flux\ term} = \underbrace{\frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}}_{\rm Source\ term}.}
$$
We can then see that, if we rewrite this, the right-hand side (RHS) describing the time evolution $\partial_t \tilde{S}_i$ consists of two terms: the flux term and the source term. The flux term in particular can be tricky, as it may be discontinuous due to shocks or other sharp features. This presents difficulties when we take finite-difference derivatives of that term, leading to the Gibbs phenomenon. So, we implement a different algorithm to take the derivative.

It is the flux term $\alpha \sqrt{\gamma} T^j_{{\rm EM} i} = \alpha \sqrt{\gamma} g_{i \mu} T^{\mu i}_{\rm EM}$ itself that concerns us here, where $T^{\mu \nu}_{\rm EM} = b^2 u^\mu u^\nu + \frac{1}{2} b^2 g^{\mu \nu} - b^\mu b^\nu$; the following functions will compute this value so that we can easily take its derivative later. Having reconstructed the values of $v^i_{(n)}$ and $B^i$ on the cell faces, we will now compute the value of the flux of $\tilde{S}_i$ on each face. For each component of $\tilde{S}_i$ in each direction, we compute the flux as
$$
F^{\rm HLL} = \frac{c_{\rm min} f_{\rm R} + c_{\rm max} f_{\rm L} - c_{\rm min} c_{\rm max} ({\rm st\_j\_r}-{\rm st\_j\_l})}{c_{\rm min} + c_{\rm max}},
$$
where
$$
f = \alpha \sqrt{\gamma} T^j_{{\rm EM} i}
$$
and
$$
{\rm st\_j} = \tilde{S}_j.
$$
Here, $i$ is direction in which we are computing the flux, and $j$ is the component of the momentum we are computing it for. Note that these two quantities are computed on both the left and right sides of the cell face. We will be able to draw heavily on the module [Tutorial-u0_smallb_Poynting-Cartesian.ipynb](Tutorial-u0_smallb_Poynting-Cartesian.ipynb) to compute $u^0$, $u^i$, and $b^\mu$, as well as the index-lowered forms of those vectors. Critically, these quantities depend on the Valencia 3-velocity $v^i_{(n)}$ and magnetic field $B^i$. We will not be using the normal gridfunctions for these, but rather the ones that we have previosly calculated on the left and right sides of the cell faces using the [Piecewise Parabolic Method](Tutorial-GiRaFFE_NRPy_Ccode_library-PPM.ipynb).

The speeds $c_\min$ and $c_\max$ are characteristic speeds that waves can travel through the plasma. In GRFFE, the expressions defining them reduce a function of only the metric quantities. $c_\min$ is the negative of the minimum amongst the speeds $c_-$ and $0$ and $c_\max$ is the maximum amongst the speeds $c_+$ and $0$. The speeds $c_\pm = \sqrt{b^2-4ac}$ must be calculated on both the left and right faces, where
$$a = 1/\alpha^2,$$
$$b = 2 \beta^i / \alpha^2$$
and $$c = g^{ii} - (\beta^i)^2/\alpha^2.$$

Another point to consider is that since we are working on cell faces, not at the cell center, we can't use the normal metric values that we store. We will instead use the value of the metric interpolated onto the cell face, which in this tutorial, we will assume has been previously done.

The algorithm for finite-volume methods in general is as follows:

1. The Reconstruction Step - Piecewise Parabolic Method
    1. Within each cell, fit to a function that conserves the volume in that cell using information from the neighboring cells
        * For PPM, we will naturally use parabolas
    1. Use that fit to define the state at the left and right interface of each cell
    1. Apply a slope limiter to mitigate Gibbs phenomenon
1. Interpolate the value of the metric gridfunctions on the cell faces
1. **Solving the Riemann Problem - Harten, Lax, (This notebook, $\tilde{S}_i$ only)**
    1. **Use the left and right reconstructed states to calculate the unique state at boundary**
1. Use the unique state to estimate the derivative in the cell

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

This notebook is organized as follows

1. [Step 1](#prelim): Preliminaries
1. [Step 2](#s_i_flux): The $\tilde{S}_i$ function
    1. [Step 2.a](#hydro_speed): GRFFE characteristic wave speeds
    1. [Step 2.b](#fluxes): Compute the HLLE fluxes
1. [Step 3](#code_validation): Code Validation against `GiRaFFE_HO.GiRaFFE_Higher_Order_v2` NRPy+ Module
1. [Step 4](#derive_speed): Complete Derivation of the Wave Speeds
1. [Step 5](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

# Step 1: Preliminaries \[Back to [top](#toc)\]
$$\label{prelim}$$

This first block of code just sets up a subdirectory within `GiRaFFE_standalone_Ccodes/` to which we will write the C code. We will also import the core NRPy+ functionality and register the needed gridfunctions. Doing so will let NRPy+ figure out where to read and write data from/to.

```python
# Step 0: Add NRPy's directory to the path
# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory
import os,sys
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

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 loop as lp                # NRPy+: Generate C code loops
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

thismodule = "GiRaFFE_NRPy-Stilde-flux"

```

# Step 2: The $\tilde{S}_i$ function \[Back to [top](#toc)\]
$$\label{s_i_flux}$$

## Step 2.a: GRFFE characteristic wave speeds \[Back to [top](#toc)\]
$$\label{hydro_speed}$$

Next, we will find the speeds at which the hydrodynamics waves propagate. We start from the speed of light (since FFE deals with very diffuse plasmas), which is $c=1.0$ in our chosen units. We then find the speeds $c_+$ and $c_-$ on each face with the function `find_cp_cm`; then, we find minimum and maximum speeds possible from among those.

Below is the source code for `find_cp_cm`, edited to work with the NRPy+ version of GiRaFFE. One edit we need to make in particular is to the term `psim4*gupii` in the definition of `c`; that was written assuming the use of the conformal metric $\tilde{g}^{ii}$. Since we are not using that here, and are instead using the ADM metric, we should not multiply by $\psi^{-4}$.

```c
static inline void find_cp_cm(REAL &cplus,REAL &cminus,const REAL v02,const REAL u0,
                              const REAL vi,const REAL lapse,const REAL shifti,
                              const REAL gammadet,const REAL gupii) {
  const REAL u0_SQUARED=u0*u0;
  const REAL ONE_OVER_LAPSE_SQUARED = 1.0/(lapse*lapse);
  // sqrtgamma = psi6 -> psim4 = gammadet^(-1.0/3.0)
  const REAL psim4 = pow(gammadet,-1.0/3.0);
  //Find cplus, cminus:
  const REAL a = u0_SQUARED * (1.0-v02) + v02*ONE_OVER_LAPSE_SQUARED;
  const REAL b = 2.0* ( shifti*ONE_OVER_LAPSE_SQUARED * v02 - u0_SQUARED * vi * (1.0-v02) );
  const REAL c = u0_SQUARED*vi*vi * (1.0-v02) - v02 * ( gupii -
                                                               shifti*shifti*ONE_OVER_LAPSE_SQUARED);
  REAL detm = b*b - 4.0*a*c;
  //ORIGINAL LINE OF CODE:
  //if(detm < 0.0) detm = 0.0;
  //New line of code (without the if() statement) has the same effect:
  detm = sqrt(0.5*(detm + fabs(detm))); /* Based on very nice suggestion from Roland Haas */

  cplus = 0.5*(detm-b)/a;
  cminus = -0.5*(detm+b)/a;
  if (cplus < cminus) {
    const REAL cp = cminus;
    cminus = cplus;
    cplus = cp;
  }
}
```
Comments documenting this have been excised for brevity, but are reproduced in $\LaTeX$ [below](#derive_speed).

We could use this code directly, but there's substantial improvement we can make by changing the code into a NRPyfied form. Note the `if` statement; NRPy+ does not know how to handle them, so we must eliminate it if we want to leverage NRPy+'s full power. (Calls to `fabs()` are also cheaper than `if` statements.) This can be done if we rewrite this, taking inspiration from the other eliminated `if` statement documented in the above code block:
```c
  cp = 0.5*(detm-b)/a;
  cm = -0.5*(detm+b)/a;
  cplus  = 0.5*(cp+cm+fabs(cp-cm));
  cminus = 0.5*(cp+cm-fabs(cp-cm));
```
This can be simplified further, by substituting `cp` and `cm` into the below equations and eliminating terms as appropriate. First note that `cp+cm = -b/a` and that `cp-cm = detm/a`. Thus,
```c
  cplus  = 0.5*(-b/a + fabs(detm/a));
  cminus = 0.5*(-b/a - fabs(detm/a));
```
This fulfills the original purpose of the `if` statement in the original code because we have guaranteed that $c_+ \geq c_-$.

This leaves us with an expression that can be much more easily NRPyfied. So, we will rewrite the following in NRPy+, making only minimal changes to be proper Python. However, it turns out that we can make this even simpler. In GRFFE, $v_0^2$ is guaranteed to be exactly one. In GRMHD, this speed was calculated as $$v_{0}^{2} = v_{\rm A}^{2} + c_{\rm s}^{2}\left(1-v_{\rm A}^{2}\right),$$ where the Alfvén speed $v_{\rm A}^{2}$ $$v_{\rm A}^{2} = \frac{b^{2}}{\rho_{b}h + b^{2}}.$$ So, we can see that when the density $\rho_b$ goes to zero, $v_{0}^{2} = v_{\rm A}^{2} = 1$. Then
\begin{align}
a &= (u^0)^2 (1-v_0^2) + v_0^2/\alpha^2 \\
&= 1/\alpha^2 \\
b &= 2 \left(\beta^i v_0^2 / \alpha^2 - (u^0)^2 v^i (1-v_0^2)\right) \\
&= 2 \beta^i / \alpha^2 \\
c &= (u^0)^2 (v^i)^2 (1-v_0^2) - v_0^2 \left(g^{ii} - (\beta^i)^2/\alpha^2\right) \\
&= g^{ii} - (\beta^i)^2/\alpha^2,
\end{align}
are simplifications that should save us some time; we can see that $a \geq 0$ is guaranteed. Note that we also force `detm` to be positive. Thus, `detm/a` is guaranteed to be positive itself, rendering the calls to `nrpyAbs()` superfluous. Furthermore, we eliminate any dependence on the Valencia 3-velocity and the time compoenent of the four-velocity, $u^0$. This leaves us free to solve the quadratic in the familiar way: $$c_\pm = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$$.

```python
# We'll write this as a function so that we can calculate the expressions on-demand for any choice of i
def find_cp_cm(lapse,shifti,gupii):
    # Inputs:  u0,vi,lapse,shift,gammadet,gupii
    # Outputs: cplus,cminus

    # a = 1/(alpha^2)
    a = 1/(lapse*lapse)
    # b = 2 beta^i / alpha^2
    b = 2 * shifti /(lapse*lapse)
    # c = -g^{ii} + (beta^i)^2 / alpha^2
    c = - gupii + shifti*shifti/(lapse*lapse)

    # Now, we are free to solve the quadratic equation as usual. We take care to avoid passing a
    # negative value to the sqrt function.
    detm = b*b - 4*a*c
    detm = sp.sqrt(sp.Rational(1,2)*(detm + nrpyAbs(detm)))
    global cplus,cminus
    cplus  = sp.Rational(1,2)*(-b/a + detm/a)
    cminus = sp.Rational(1,2)*(-b/a - detm/a)

```

We will now write a function in NRPy+ similar to the one used in the old `GiRaFFE`, allowing us to generate the expressions with less need to copy-and-paste code; the key difference is that this one will be in Python, and generate optimized C code integrated into the rest of the operations. Notice that since we eliminated the dependence on velocities, none of the input quantities are different on either side of the face. So, this function won't really do much besides guarantee that `cmax` and `cmin` are positive and negative, respectively, but we'll leave the machinery here since it is likely to be a useful guide to somebody who wants to something similar. We use the same technique as above to replace the `if` statements inherent to the `MAX()` and `MIN()` functions.

```python
# We'll write this as a function, and call it within HLLE_solver, below.
def find_cmax_cmin(flux_dirn,gamma_faceDD,beta_faceU,alpha_face):
    # Inputs:  flux direction flux_dirn, Inverse metric gamma_faceUU, shift beta_faceU,
    #          lapse alpha_face, metric determinant gammadet_face
    # Outputs: maximum and minimum characteristic speeds cmax and cmin
    # First, we need to find the characteristic speeds on each face
    gamma_faceUU,unusedgammaDET = ixp.generic_matrix_inverter3x3(gamma_faceDD)
    find_cp_cm(alpha_face,beta_faceU[flux_dirn],gamma_faceUU[flux_dirn][flux_dirn])
    cpr = cplus
    cmr = cminus
    find_cp_cm(alpha_face,beta_faceU[flux_dirn],gamma_faceUU[flux_dirn][flux_dirn])
    cpl = cplus
    cml = cminus

    # The following algorithms have been verified with random floats:

    global cmax,cmin
    # Now, we need to set cmax to the larger of cpr,cpl, and 0
    cmax = sp.Rational(1,2)*(cpr+cpl+nrpyAbs(cpr-cpl))
    cmax = sp.Rational(1,2)*(cmax+nrpyAbs(cmax))

    # And then, set cmin to the smaller of cmr,cml, and 0
    cmin =  sp.Rational(1,2)*(cmr+cml-nrpyAbs(cmr-cml))
    cmin = -sp.Rational(1,2)*(cmin-nrpyAbs(cmin))

```

## Step 2.b: Compute the HLLE fluxes \[Back to [top](#toc)\]
$$\label{fluxes}$$

Finally, we can compute the flux in each direction. This momentum flux in the $m$ direction is defined as $\alpha \sqrt{\gamma} T^m_{\ \ j}$, based on the input `flux_dirn`. We have already defined $\alpha \sqrt{\gamma}$, so all we need to do is calculate $T^m_{\ \ j}$, where $T^{\mu \nu}_{\rm EM} = b^2 u^\mu u^\nu + \frac{1}{2} b^2 g^{\mu \nu} - b^\mu b^\nu$. In doing this index-lowering operation, recall that $g^{\mu \nu} g_{\nu \alpha} = \delta^\mu_\alpha$. We will do so in accordance with the method published by [Harten, Lax, and von Leer](https://epubs.siam.org/doi/pdf/10.1137/1025002)  and [Einfeldt](https://epubs.siam.org/doi/10.1137/0725021) (hereafter HLLE) to solve the Riemann problem. So, we define $f(u) = T^m_{\ \ j}$ on each face as
$$
f = \alpha \sqrt{\gamma} \left( (\rho+b^2)(u^0 v^m) u_j + (P+\frac{1}{2}b^2) \delta^m_j - b^m b_j \right);
$$
Because $\rho = P = 0$ in GRFFE and $u^0 v^m = u^m$ in general (since $v^m$ is the drift velocity here), this simplifies to
$$
f = \alpha \sqrt{\gamma} \left( b^2 u^m u_j + \frac{1}{2}b^2 \delta^m_j - b^m b_j \right).
$$
We use $j$ to correspond to the component of the flux we are calculating; that is, $j=1$ corresponds to $x$, and so forth (however, remember that in a NRPy+ 3-vector, the numbers will still run from 0 to 2). $\delta^i_j$ is the standard Kronecker delta. We also define `st_x_r` and `st_x_l` (called $u_{\rm R}$ and $u_{\rm L}$ in HLL; we'll not do that, since we're using $u$ for four-velocity):
$$
{\rm st\_j} = \alpha \sqrt{\gamma} \left( (\rho+b^2) u^0 u_j - b^0 b_j \right),
$$
which, in GRFFE, simplifies to
$$
{\rm st\_j} = \alpha \sqrt{\gamma} \left( b^2 u^0 u_j - b^0 b_j \right),
$$

and combine based on eq. 3.15 in the HLLE paper,
$$
F^{\rm HLL} = \frac{c_{\rm min} f_{\rm R} + c_{\rm max} f_{\rm L} - c_{\rm min} c_{\rm max} ({\rm st\_x\_r}-{\rm st\_x\_l})}{c_{\rm min} + c_{\rm max}}
$$

We'll write the HLLE step as a function so that we can loop over `flux_dirn` and `mom_comp` and write each version needed as we need it.

```python
# We'll rewrite this assuming that we've passed the entire reconstructed
# gridfunctions. You could also do this with only one point, but then you'd
# need to declare everything as a Cparam in NRPy+

import GRHD.equations as GRHD
import GRFFE.equations as GRFFE

def calculate_GRFFE_Tmunu_and_contractions(flux_dirn, mom_comp, gammaDD,betaU,alpha,ValenciavU,BU,sqrt4pi):
    GRHD.compute_sqrtgammaDET(gammaDD)

    GRHD.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, ValenciavU)
    GRFFE.compute_smallb4U_with_driftvU_for_FFE(gammaDD, betaU, alpha, GRHD.u4U_ito_ValenciavU, BU, sqrt4pi)
    GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U)

    GRFFE.compute_TEM4UU(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U, GRFFE.smallbsquared, GRHD.u4U_ito_ValenciavU)
    GRFFE.compute_TEM4UD(gammaDD, betaU, alpha, GRFFE.TEM4UU)

    # Compute conservative variables in terms of primitive variables
    GRHD.compute_S_tildeD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)

    global U,F
    # Flux F = alpha*sqrt{gamma}*T^i_j
    F = alpha*GRHD.sqrtgammaDET*GRFFE.TEM4UD[flux_dirn+1][mom_comp+1]
    # U = alpha*sqrt{gamma}*T^0_j = Stilde_j
    U = GRHD.S_tildeD[mom_comp]

def HLLE_solver(cmax, cmin, Fr, Fl, Ur, Ul):
    # This solves the Riemann problem for the mom_comp component of the momentum
    # flux StildeD in the flux_dirn direction.

    # st_j_flux = (c_\min f_R + c_\max f_L - c_\min c_\max ( st_j_r - st_j_l )) / (c_\min + c_\max)
    return (cmin*Fr + cmax*Fl - cmin*cmax*(Ur-Ul) )/(cmax + cmin)

```

Finally, now that we have the equations written, we can write out the files. After writing a brief post string (to close the function), we will loop over `flux_dirn`, creating one C file for each flux direction. In each file, we will include the math to calculate each momentum-flux component `mom_comp` in that direction.

We have written the function `HLLE_solver()` so that we can easily compute the flux as specified by those two indices.

```python
def calculate_Stilde_flux(flux_dirn,inputs_provided=True,alpha_face=None,gamma_faceDD=None,beta_faceU=None,\
                          Valenciav_rU=None,B_rU=None,Valenciav_lU=None,B_lU=None,sqrt4pi=None):
    find_cmax_cmin(flux_dirn,gamma_faceDD,beta_faceU,alpha_face)

    global Stilde_fluxD
    Stilde_fluxD = ixp.zerorank3()
    for mom_comp in range(3):
        calculate_GRFFE_Tmunu_and_contractions(flux_dirn, mom_comp, gamma_faceDD,beta_faceU,alpha_face,\
                                               Valenciav_rU,B_rU,sqrt4pi)
        Fr = F
        Ur = U
        calculate_GRFFE_Tmunu_and_contractions(flux_dirn, mom_comp, gamma_faceDD,beta_faceU,alpha_face,\
                                               Valenciav_lU,B_lU,sqrt4pi)
        Fl = F
        Ul = U
        Stilde_fluxD[mom_comp] = HLLE_solver(cmax, cmin, Fr, Fl, Ur, Ul)

```

# Step 3:  Code Validation against `GiRaFFE_HO.GiRaFFE_Higher_Order_v2` NRPy+ Module \[Back to [top](#toc)\]
$$\label{code_validation}$$

Here, as a code validation check, we verify agreement in the SymPy expressions for the $\texttt{GiRaFFE}$ evolution equations and auxiliary quantities we intend to use between
1. this tutorial and
2. the NRPy+ [GiRaFFE_NRPy.Stilde_flux](../../edit/in_progress/GiRaFFE_NRPy/Stilde_flux.py) module.

```python
all_passed=True
def comp_func(expr1,expr2,basename,prefixname2="C2P_P2C."):
    if str(expr1-expr2)!="0":
        print(basename+" - "+prefixname2+basename+" = "+ str(expr1-expr2))
        all_passed=False

def gfnm(basename,idx1,idx2=None,idx3=None):
    if idx2==None:
        return basename+"["+str(idx1)+"]"
    if idx3==None:
        return basename+"["+str(idx1)+"]["+str(idx2)+"]"
    return basename+"["+str(idx1)+"]["+str(idx2)+"]["+str(idx3)+"]"

# These are the standard gridfunctions we've used before.
#ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","ValenciavU",DIM=3)
#gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01")
#betaU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","betaU")
#alpha = gri.register_gridfunctions("AUXEVOL",["alpha"])
#AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD",DIM=3)
#BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","BU",DIM=3)

# We will pass values of the gridfunction on the cell faces into the function. This requires us
# to declare them as C parameters in NRPy+. We will denote this with the _face infix/suffix.
alpha_face = gri.register_gridfunctions("AUXEVOL","alpha_face")
gamma_faceDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gamma_faceDD","sym01")
beta_faceU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","beta_faceU")

# We'll need some more gridfunctions, now, to represent the reconstructions of BU and ValenciavU
# on the right and left faces
Valenciav_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_rU",DIM=3)
B_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_rU",DIM=3)
Valenciav_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_lU",DIM=3)
B_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_lU",DIM=3)

sqrt4pi = sp.symbols('sqrt4pi',real=True)

# ...and some more for the fluxes we calculate here. These three gridfunctions will each store
# the momentum flux of one component of StildeD in one direction; we'll be able to reuse them
# as we loop over each direction, reducing our memory costs.
Stilde_fluxD = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Stilde_fluxD",DIM=3)

import GiRaFFE_NRPy.Stilde_flux as Sf

for flux_dirn in range(3):
    expr_list = []
    exprcheck_list = []
    namecheck_list = []

    print("Checking the flux in direction "+str(flux_dirn))
    calculate_Stilde_flux(flux_dirn,True,alpha_face,gamma_faceDD,beta_faceU,\
                          Valenciav_rU,B_rU,Valenciav_lU,B_lU,sqrt4pi)
    Sf.calculate_Stilde_flux(flux_dirn,True,alpha_face,gamma_faceDD,beta_faceU,\
                             Valenciav_rU,B_rU,Valenciav_lU,B_lU,sqrt4pi)

    for mom_comp in range(3):
        namecheck_list.extend([gfnm("Stilde_fluxD",mom_comp)])
        exprcheck_list.extend([Sf.Stilde_fluxD[mom_comp]])
        expr_list.extend([Stilde_fluxD[mom_comp]])

    for mom_comp in range(len(expr_list)):
        comp_func(expr_list[mom_comp],exprcheck_list[mom_comp],namecheck_list[mom_comp])

import sys
if all_passed:
    print("ALL TESTS PASSED!")
else:
    print("ERROR: AT LEAST ONE TEST DID NOT PASS")
    sys.exit(1)

```

    Checking the flux in direction 0
    Checking the flux in direction 1
    Checking the flux in direction 2
    ALL TESTS PASSED!

# Step 4: Complete Derivation of the Wave Speeds \[Back to [top](#toc)\]
$$\label{derive_speed}$$

This computes phase speeds in the direction given by flux_dirn. Note that we replace the full dispersion relation with a simpler one, which overestimates the maximum speeds by a factor of ~2. See full discussion around Eqs. 49 and 50 in [Duez, et al.](http://arxiv.org/pdf/astro-ph/0503420.pdf). In summary, we solve the dispersion relation (in, e.g., the $x$-direction) with a wave vector of $k_\mu = (-\omega,k_x,0,0)$. So, we solve the approximate dispersion relation $\omega_{\rm cm}^2 = [v_A^2 + c_s^2 (1-v_A^2)]k_{\rm cm}^2$ for the wave speed $\omega/k_x$, where the sound speed $c_s = \sqrt{\Gamma P/(h \rho_0)}$, the Alfvén speed $v_A = 1$ (in GRFFE), $\omega_{\rm cm} = -k_\mu k^\mu$ is the frequency in the comoving frame, $k_{\rm cm}^2 = K_\mu K^\mu$ is the wavenumber squared in the comoving frame, and $K_\mu = (g_{\mu\nu} + u_\mu u_\nu)k^\nu$ is the part of the wave vector normal to the four-velocity $u^\mu$. See below for a complete derivation.

What follows is a complete derivation of the quadratic we solve.
\begin{align}
w_{\rm cm} &= (-k_0 u^0 - k_x u^x) \\
k_{\rm cm}^2 &= K_{\mu} K^{\mu}, \\
K_{\mu} K^{\mu} &= (g_{\mu a} + u_{\mu} u_a) k^a  g^{\mu b} [ (g_{c b} + u_c u_b) k^c ] \\
\rightarrow g^{\mu b} (g_{c b} + u_{c} u_{b}) k^c &= (\delta^{\mu}_c + u_c u^{\mu} ) k^c \\
                 &= (g_{\mu a} + u_{\mu} u_a) k^a  (\delta^{\mu}_c + u_c u^{\mu} ) k^c \\
                 &=[(g_{\mu a} + u_{\mu} u_a) \delta^{\mu}_c + (g_{\mu a} + u_{\mu} u_a) u_c u^{\mu} ] k^c k^a \\
                 &=[(g_{c a} + u_c u_a) + (u_c u_a -  u_a u_c] k^c k^a \\
                 &=(g_{c a} + u_c u_a) k^c k^a \\
                 &= k_a k^a + u^c u^a k_c k_a \\
k^a = g^{\mu a} k_{\mu} &= g^{0 a} k_0 + g^{x a} k_x \\
k_a k^a &= k_0 g^{0 0} k_0 + k_x k_0 g^{0 x} + g^{x 0} k_0 k_x + g^{x x} k_x k_x \\
         &= g^{00} (k_0)^2 + 2 g^{x0} k_0 k_x + g^{xx} (k_x)^2 \\
u^c u^a k_c k_a &= (u^0 k_0 + u^x k_x) (u^0 k_0 + u^x k_x) = (u^0 k_0)^2 + 2 u^x k_x u^0 k_0 + (u^x k_x)^2 \\
(k_0 u0)^2  + 2 k_x u^x k_0 u^0 + (k_x u^x)^2 &= v_0^2 [ (u^0 k_0)^2 + 2 u^x k_x u^0 k_0 + (u^x k_x)^2 + g^{00} (k_0)^2 + 2 g^{x0} k_0 k_x + g^{xx} (k_x)^2] \\
(1-v_0^2) (u^0 k_0 + u^x k_x)^2 &= v_0^2 (g^{00} (k_0)^2 + 2 g^{x0} k_0 k_x + g^{xx} (k_x)^2) \\
(1-v_0^2) (u^0 k_0/k_x + u^x)^2 &= v_0^2 (g^{00} (k_0/k_x)^2 + 2 g^{x0} k_0/k_x + g^{xx}) \\
(1-v_0^2) (u^0 X + u^x)^2 &= v_0^2 (g^{00} X^2 + 2 g^{x0} X + g^{xx}) \\
(1-v_0^2) ((u^0)^2 X^2 + 2 u^x (u^0) X + (u^x)^2) &= v_0^2 (g^{00} X^2 + 2 g^{x0} X + g^{xx}) \\
&X^2 ( (1-v_0^2) (u^0)^2 - v_0^2 g^{00}) + X (2 u^x u^0 (1-v_0^2) - 2 v_0^2 g^{x0}) + (1-v_0^2) (u^x)^2 - v_0^2 g^{xx} \\
a &= (1-v_0^2) (u^0)^2 - v_0^2 g^{00} = (1-v_0^2) (u^0)^2 + v_0^2/\alpha^2 \leftarrow {\rm VERIFIED} \\
b &= 2 u^x u^0 (1-v_0^2) - 2 v_0^2 \beta^x/\alpha^2 \leftarrow {\rm VERIFIED,\ } X\rightarrow -X, {\rm because\ } X = -w/k_1, {\rm \ and\ we\ are\ solving\ for} -X. \\
c &= (1-v_0^2) (u^x)^2 - v_0^2 (g^{xx}\psi^{-4} - (\beta^x/\alpha)^2) \leftarrow {\rm VERIFIED} \\
v_0^2 &= v_A^2 + c_s^2 (1 - v_A^2) \\
\end{align}

# Step 5: 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-GiRaFFE_HO_Ccode_library-Stilde-flux.pdf](Tutorial-GiRaFFE_HO_Ccode_library-Stilde-flux.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
!jupyter nbconvert --to latex --template latex_nrpy_style.tplx --log-level='WARN' Tutorial-GiRaFFE_NRPy-Stilde-flux.ipynb
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-Stilde-flux.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-Stilde-flux.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-Stilde-flux.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

    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
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode


```python
from gravipy.tensorial import *
from sympy import *
```

Schwarzschild計量のChristoffel記号を計算する。
[gravipy](https://github.com/wojciechczaja/GraviPy)を使う。

レポジトリのdocsディレクトリにあるチュートリアル用ipnbファイルを参考にした。
以下$(−,+,+,+)$型の計量を採用する。

```python
t, r, theta, phi, M = symbols('t, r, theta, phi, M')
chi = Coordinates('\chi', [t, r, theta, phi])
Metric = diag(-(1-2*M/r), 1/(1-2*M/r), r**2, r**2*sin(theta)**2)  #Schwarzschild計量
g = MetricTensor('g', chi, Metric)
Ga = Christoffel('Ga', g)
```

```python
#計量テンソル
g(All, All)
```

$\displaystyle \left[\begin{matrix}\frac{2 M}{r} - 1 & 0 & 0 & 0\\0 & \frac{1}{- \frac{2 M}{r} + 1} & 0 & 0\\0 & 0 & r^{2} & 0\\0 & 0 & 0 & r^{2} \sin^{2}{\left(\theta \right)}\end{matrix}\right]$

```python
#Christoffel記号
Ga(-All, All, All)
```

$\displaystyle \left[\begin{matrix}\left[\begin{matrix}0 & - \frac{M}{r \left(2 M - r\right)} & 0 & 0\\- \frac{M}{r \left(2 M - r\right)} & 0 & 0 & 0\\0 & 0 & 0 & 0\\0 & 0 & 0 & 0\end{matrix}\right] & \left[\begin{matrix}\frac{M \left(- 2 M + r\right)}{r^{3}} & 0 & 0 & 0\\0 & - \frac{M \left(- 2 M + r\right)}{r \left(2 M - r\right)^{2}} & 0 & 0\\0 & 0 & 2 M - r & 0\\0 & 0 & 0 & - \left(- 2 M + r\right) \sin^{2}{\left(\theta \right)}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & \frac{1}{r} & 0\\0 & \frac{1}{r} & 0 & 0\\0 & 0 & 0 & - \frac{\sin{\left(2 \theta \right)}}{2}\end{matrix}\right] & \left[\begin{matrix}0 & 0 & 0 & 0\\0 & 0 & 0 & \frac{1}{r}\\0 & 0 & 0 & \frac{\sin{\left(2 \theta \right)}}{2 \sin^{2}{\left(\theta \right)}}\\0 & \frac{1}{r} & \frac{\sin{\left(2 \theta \right)}}{2 \sin^{2}{\left(\theta \right)}} & 0\end{matrix}\right]\end{matrix}\right]$

次に測地線方程式による四元加速度
$$
a^{\alpha}  = \frac{d^2x^{\alpha}}{d\lambda^2}= -\Gamma^{\alpha}_{\mu \nu} \frac{dx^{\mu}}{d\lambda} \frac{dx^{\nu}}{d\lambda} =  -\Gamma^{\alpha}_{\mu \nu} v^{\mu}v^{\nu}
$$
を計算する。ここで$\lambda$は何らかのパラメータである(固有時ではない)。

```python
from itertools import product
var("v_0, v_1, v_2, v_3")
var("a_0, a_1, a_2, a_3")
a_list = [a_0, a_1, a_2, a_3]
v_list = [v_0, v_1, v_2, v_3]
for i in range(4):
    a_list[i] = 0

#縮約を取る
for i, j, k in product(range(4), repeat=3):
    a_list[i] -= Ga( -i-1, j + 1, k + 1)*v_list[j]*v_list[k]
```

```python
for i in range(4):
    display(a_list[i])
```

$\displaystyle \frac{2 M v_{0} v_{1}}{r \left(2 M - r\right)}$

$\displaystyle \frac{M v_{1}^{2} \left(- 2 M + r\right)}{r \left(2 M - r\right)^{2}} - \frac{M v_{0}^{2} \left(- 2 M + r\right)}{r^{3}} - v_{2}^{2} \left(2 M - r\right) + v_{3}^{2} \left(- 2 M + r\right) \sin^{2}{\left(\theta \right)}$

$\displaystyle \frac{v_{3}^{2} \sin{\left(2 \theta \right)}}{2} - \frac{2 v_{1} v_{2}}{r}$

$\displaystyle - \frac{v_{2} v_{3} \sin{\left(2 \theta \right)}}{\sin^{2}{\left(\theta \right)}} - \frac{2 v_{1} v_{3}}{r}$

実行速度の観点からsympy数式から関数へ変換する。

参考:https://docs.sympy.org/latest/modules/utilities/lambdify.html

```python
from sympy.utilities.lambdify import lambdify
a_func = lambdify((t, r, theta, phi, M, v_0, v_1, v_2, v_3), a_list)
```

位置と速度の四元ベクトル$x^\mu, v^\mu$を入力すると四元加速度$a^\mu$を返す関数を定義する。

```python
import numpy as np
a = lambda x, v: np.array(a_func(x[0], x[1], x[2], x[3], 1, v[0], v[1], v[2], v[3]))
```

時間発展はRunge–Kutta法([wikipedia](https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%B3%E3%82%B2%EF%BC%9D%E3%82%AF%E3%83%83%E3%82%BF%E6%B3%95))で計算する。

ニュートン運動方程式のRunge–Kutta法によるシュミレーションを参考にした。

https://www.compadre.org/PICUP/resources/Numerical-Integration/

今解きたい問題は、

$$
\begin{align}
&\frac{dv^\mu}{d\lambda} = a^\mu(x^\mu, v^\mu)\\
&\frac{dx^\mu}{d\lambda} = v^\mu
\end{align}
$$
であるので、
$$
\begin{align}
&k^\mu_{1v} = a^\mu(x^\mu, v^\mu)d\lambda \\
&k^\mu_{1x} = v^\mu d\lambda \\
&k^\mu_{2v} = a^\mu(x^\mu + \frac{k^\mu_{1x}}{2}, v^\mu+ \frac{k^\mu_{1v}}{2})d\lambda\\
&k^\mu_{2x} = ( v^\mu+ \frac{k^\mu_{1v}}{2})d\lambda \\
&k^\mu_{3v} = a^\mu(x^\mu + \frac{k^\mu_{2x}}{2}, v^\mu+ \frac{k^\mu_{2v}}{2})d\lambda\\
&k^\mu_{3x} = ( v^\mu+ \frac{k^\mu_{2v}}{2})d\lambda\\
&k^\mu_{4v} = a^\mu(x^\mu + k^\mu_{3x}, v^\mu + k^\mu_{3v})d\lambda\\
&k^\mu_{4x} = (v^\mu + k^\mu_{3v})d\lambda\\
\end{align}
$$
を計算して、$x^\mu$, $v^\mu$を
$$
\begin{align}
x^\mu_{\mathrm{next}} = x^\mu + \frac{1}{6}(k^\mu_{1x} + 2k^\mu_{2x} + 2k^\mu_{3x} + k^\mu_{4x}) \\
v^\mu_{\mathrm{next}} = v^\mu + \frac{1}{6}(k^\mu_{1v} + 2k^\mu_{2v} + 2k^\mu_{3v} + k^\mu_{4v})
\end{align}
$$
と更新していけば良い。ニュートン運動方程式で$t$に相当するのがパラメータ$\lambda$であることに注意。

```python
N = 10**5 #計算ステップ数

x = np.array([0.0, 17.32050808,  0.95531662, -0.78539816]) #初期位置
# theta成分(x[2])を0にしてしまうと0/0計算が発生するので適当な値を持たせておく
v = np.array([1, -0.02886728, -0.00824957,  0.01750001]) #初期速度
#t=0付近で\lambda=tと選ぶとdt/d\lambda = 1なので時間成分の速さは1にする
#空間成分の速度は楕円軌道を描くように選ぶ

dlam = 0.1 #1ステップごとに進む\lambda幅
R = []
Theta = []
Phi = []
T = []
for _ in range(N):
    T.append(x[0])
    R.append(x[1])
    Theta.append(x[2])
    Phi.append(x[3])
    k1v = a(x, v)*dlam
    k1x = v*dlam
    k2v = a(x+k1x/2, v+k1v/2)*dlam
    k2x = (v+k1v/2)*dlam
    k3v = a(x+k2x/2, v+k2v/2)*dlam
    k3x = (v+k2v/2)*dlam
    k4v = a(x+k3x, v+k3v)*dlam
    k4x = (v+k3v)*dlam
    v = v + (1/6)*(k1v+2*k2v+2*k3v+k4v)
    x = x + (1/6)*(k1x+2*k2x+2*k3x+k4x)
X = R*np.cos(Phi)*np.sin(Theta)
Y = R*np.sin(Phi)*np.sin(Theta)
Z = R*np.cos(Theta)
```

今、謎のパラメータ$\lambda$で$x$, $y$, $z$がパラメーター付けされているので、$t$によるパラメータ付けに変えたい。時系列データの補間を行う。

参考：
https://qiita.com/kenichi-hamaguchi/items/3c5e63e195e06a21d1da

```python
dt = 10 #時間幅
T_new = np.arange(0, T[-1], dt)
X_new = np.interp(T_new, T, X)
Y_new = np.interp(T_new, T, Y)
Z_new = np.interp(T_new, T, Z)
```

アニメーションを実装する。

```python
%matplotlib nbagg
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
L = 50 #描画空間のサイズ
def update(i):
    ax.clear()
    ax.scatter(0, 0, 0, marker="o", c="orange", s=100)
    ax.plot(X_new[:i], Y_new[:i], Z_new[:i], c="black", alpha = 0.4)
    ax.scatter(X_new[i], Y_new[i], Z_new[i], marker="o", c="blue", s=10)
    ax.set_title(r"$t=$"+str(int(T_new[i])))
    ax.view_init(elev=30, azim=225)
    ax.set_xlim(-L, L)
    ax.set_ylim(-L, L)
    ax.set_zlim(-L, L)

ani = animation.FuncAnimation(fig, update, frames=len(T_new), interval=1)
```

    <IPython.core.display.Javascript object>

# 固有時の計算

```python
var("ds2, dx_0, dx_1, dx_2, dx_3")
dx_list = [dx_0, dx_1, dx_2, dx_3]
ds2 = 0
for i, j in product(range(4), repeat=2):
    ds2 += g(i+1,j+1)*dx_list[i]*dx_list[j]
ds2_func = lambdify((t, r, theta, phi, M, dx_0, dx_1, dx_2, dx_3), ds2)
dtau = lambda x, dx: np.sqrt(-ds2_func(x[0], x[1], x[2], x[3], 1, dx[0], dx[1], dx[2], dx[3]) + 0j)
```

```python
N = 10**5
x = np.array([0.0, 17.32050808,  0.95531662, -0.78539816])
v = np.array([1, -0.02886728, -0.00824957,  0.01750001])
dlam = 0.1
R = []
Theta = []
Phi = []
T = []
tau = 0  #固有時
Tau = []

for _ in range(N):
    Tau.append(tau)
    T.append(x[0])
    R.append(x[1])
    Theta.append(x[2])
    Phi.append(x[3])
    k1v = a(x, v)*dlam
    k1x = v*dlam
    k2v = a(x+k1x/2, v+k1v/2)*dlam
    k2x = (v+k1v/2)*dlam
    k3v = a(x+k2x/2, v+k2v/2)*dlam
    k3x = (v+k2v/2)*dlam
    k4v = a(x+k3x, v+k3v)*dlam
    k4x = (v+k3v)*dlam
    v = v + (1/6)*(k1v+2*k2v+2*k3v+k4v)
    x = x + (1/6)*(k1x+2*k2x+2*k3x+k4x)
    tau = tau + dtau(x, (1/6)*(k1x+2*k2x+2*k3x+k4x))
X = R*np.cos(Phi)*np.sin(Theta)
Y = R*np.sin(Phi)*np.sin(Theta)
Z = R*np.cos(Theta)
```

```python
dt = 10 #時間幅
T_new = np.arange(0, T[-1], dt)
X_new = np.interp(T_new, T, X)
Y_new = np.interp(T_new, T, Y)
Z_new = np.interp(T_new, T, Z)
R_new = np.interp(T_new, T, R)
Tau_new = np.interp(T_new, T, Tau)
Dtau_new = np.diff(Tau_new) #dtごとのd\tau
```

```python
%matplotlib inline
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.plot(T_new[:-1], Dtau_new.real, label=r"$d\tau$")
ax2.plot(T_new[:-1], R_new[:-1], c="orange", label=r"$r$")
ax1.set_xlabel(r"$t$")
ax1.set_ylabel(r"$d\tau$")
ax2.set_ylabel(r"$r$")
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
ax1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
```

重力源に近づいたときに固有時がゆっくり進み、遠ざかると早く進むことがわかる。

# 事象の地平線に衝突する場合

$r=2M$付近では$d\lambda$を小さくして慎重に計算する必要がある。

```python
N = 10**5
x = np.array([0, 4,  np.pi/2, 0])
# theta成分を0にしてしまうと0/0計算が
#発生するので適当な値を持たせておく
v = np.array([1, 0, 0, 0.11])

R = []
Theta = []
Phi = []
T = []
tau = 0  #固有時
Tau = []

np.seterr(all="raise") #エラーが起こったら計算を止める
for _ in range(N):
    try:
        #事象の地平線に近づいたらd\lambdaを小さくする
        dlam = 0.01*(np.abs(x[1] - 2))
        Tau.append(tau)
        T.append(x[0])
        R.append(x[1])
        Theta.append(x[2])
        Phi.append(x[3])
        k1v = a(x, v)*dlam
        k1x = v*dlam
        k2v = a(x+k1x/2, v+k1v/2)*dlam
        k2x = (v+k1v/2)*dlam
        k3v = a(x+k2x/2, v+k2v/2)*dlam
        k3x = (v+k2v/2)*dlam
        k4v = a(x+k3x, v+k3v)*dlam
        k4x = (v+k3v)*dlam
        v = v + (1/6)*(k1v+2*k2v+2*k3v+k4v)
        x = x + (1/6)*(k1x+2*k2x+2*k3x+k4x)
        tau = tau + dtau(x, (1/6)*(k1x+2*k2x+2*k3x+k4x))
    except FloatingPointError:
        break
X = R*np.cos(Phi)*np.sin(Theta)
Y = R*np.sin(Phi)*np.sin(Theta)
Z = R*np.cos(Theta)
```

```python
dt = 1 #時間幅
T_new = np.arange(0, T[-1], dt)
X_new = np.interp(T_new, T, X)
Y_new = np.interp(T_new, T, Y)
Z_new = np.interp(T_new, T, Z)
R_new = np.interp(T_new, T, R)
Tau_new = np.interp(T_new, T, Tau)
Dtau_new = np.diff(Tau_new) #dtごとのds
```

```python
%matplotlib nbagg
fig = plt.figure()
ax = fig.add_subplot(111)
circle_phi = np.linspace(0, 2*np.pi, 100)
circle_x = 2*np.cos(circle_phi)
circle_y = 2*np.sin(circle_phi)
L = 6 #描画する空間のサイズ
def update(i):
    ax.clear()
    ax.plot(circle_x, circle_y, c="black")
    ax.plot(X_new[:i], Y_new[:i], c="black", alpha=0.6)
    ax.scatter(X_new[i], Y_new[i], marker="o", c="blue", s=10)
    ax.set_title(r"$t=$"+str(int(T_new[i]))+"\t"+r"$\tau=$"+str(round(Tau_new[i].real,2)))
    ax.set_xlim(-L, L)
    ax.set_ylim(-L, L)
    ax.set_aspect('equal')

ani = animation.FuncAnimation(fig, update, frames=len(T_new), interval=10)
```

    <IPython.core.display.Javascript object>

```python
%matplotlib inline
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.plot(T_new,Tau_new.real, label=r"$\tau$")
ax2.plot(T_new, R_new, c="orange", label=r"$r$")
ax1.set_xlabel(r"$t$")
ax1.set_ylabel(r"$\tau$")
ax2.set_ylabel(r"$r$")
handler1, label1 = ax1.get_legend_handles_labels()
handler2, label2 = ax2.get_legend_handles_labels()
ax1.legend(handler1 + handler2, label1 + label2, loc=2, borderaxespad=0.)
```

# Kerr計量の場合

```python
t, r, theta, phi, M, a, rhosq, Delta = symbols('t, r, theta, phi, M, a, rhosq, Delta')
chi = Coordinates('\chi', [t, r, theta, phi])

rhosq = r**2+(a**2)*cos(theta)**2
Delta = r**2-2*M*r+a**2

Metric_kerr = Matrix([[(1-(2*M*r)/rhosq),0,0,(2*a*M*r*sin(theta)**2)/rhosq], \
[0,-rhosq/Delta,0,0], [0,0,-rhosq,0], [(2*a*M*r*sin(theta)**2)/rhosq,0,0, \
-(sin(theta)**2)*((r**2+a**2)+(2*(a**2)*M*r*sin(theta)**2)/rhosq)]])

# 結構時間かかる
g_kerr = MetricTensor('g_kerr', chi, Metric_kerr)
Ga_kerr = Christoffel('Ga_kerr', g)
var("v_0, v_1, v_2, v_3")
var("a_0, a_1, a_2, a_3")
a_list = [a_0, a_1, a_2, a_3]
v_list = [v_0, v_1, v_2, v_3]
for i in range(4):
    a_list[i] = 0
#縮約を取る
for i, j, k in product(range(4), repeat=3):
    a_list[i] -= Ga_kerr( -i-1, j + 1, k + 1)*v_list[j]*v_list[k]

a_kerr_func= lambdify((t, r, theta, phi, a, M, v_0, v_1, v_2, v_3), a_list)
# a = 0.8, M = 1で計算
a_kerr = lambda x, v: np.array(a_kerr_func(x[0], x[1], x[2], x[3], 0.8, 1, v[0], v[1], v[2], v[3]))

var("ds2_kerr, dx_0, dx_1, dx_2, dx_3")
dx_list = [dx_0, dx_1, dx_2, dx_3]
ds2_kerr = 0
for i, j in product(range(4), repeat=2):
    ds2_kerr += g_kerr(i+1,j+1)*dx_list[i]*dx_list[j]
ds2_kerr_func = lambdify((t, r, theta, phi, a, M, dx_0, dx_1, dx_2, dx_3), ds2_kerr)
dtau_kerr = lambda x, dx: np.sqrt(ds2_kerr_func(x[0], x[1], x[2], x[3], 0.8, 1, dx[0], dx[1], dx[2], dx[3]) + 0j)
```

```python
N = 10**4
x = np.array([0.0, 17.32050808,  0.95531662, -0.78539816])
v = np.array([1, -0.02886728, -0.00824957,  0.01750001])
dlam = 0.5
R = []
Theta = []
Phi = []
T = []
tau = 0  #固有時
Tau = []

for _ in range(N):
    Tau.append(tau)
    T.append(x[0])
    R.append(x[1])
    Theta.append(x[2])
    Phi.append(x[3])
    k1v = a_kerr(x, v)*dlam
    k1x = v*dlam
    k2v = a_kerr(x+k1x/2, v+k1v/2)*dlam
    k2x = (v+k1v/2)*dlam
    k3v = a_kerr(x+k2x/2, v+k2v/2)*dlam
    k3x = (v+k2v/2)*dlam
    k4v = a_kerr(x+k3x, v+k3v)*dlam
    k4x = (v+k3v)*dlam
    v = v + (1/6)*(k1v+2*k2v+2*k3v+k4v)
    x = x + (1/6)*(k1x+2*k2x+2*k3x+k4x)
    tau = tau + dtau_kerr(x, (1/6)*(k1x+2*k2x+2*k3x+k4x))
X = R*np.cos(Phi)*np.sin(Theta)
Y = R*np.sin(Phi)*np.sin(Theta)
Z = R*np.cos(Theta)
```

```python
%matplotlib nbagg
dt = 10 #時間幅
T_new = np.arange(0, T[-1], dt)
X_new = np.interp(T_new, T, X)
Y_new = np.interp(T_new, T, Y)
Z_new = np.interp(T_new, T, Z)
Tau_new = np.interp(T_new, T, Tau)
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
L = 50 #描画する空間のサイズ
def update(i):
    ax.clear()
    ax.scatter(0, 0, 0, marker="o", c="orange", s=100)
    ax.plot(X_new[:i], Y_new[:i], Z_new[:i], c="black", alpha = 0.4)
    ax.scatter(X_new[i], Y_new[i], Z_new[i], marker="o", c="blue", s=10)
    ax.set_title(r"$t=$"+str(int(T_new[i]))+"\t"+r"$\tau=$"+str(int(Tau_new[i].real)))
    ax.view_init(elev=30, azim=225)
    ax.set_xlim(-L, L)
    ax.set_ylim(-L, L)
    ax.set_zlim(-L, L)

ani = animation.FuncAnimation(fig, update, frames=len(T_new), interval=1)
```

    <IPython.core.display.Javascript object>


# Reduced Helmholtz equation of state: carbon dioxide

**Water equation of state:** You can see the full, state-of-the-art equation of state for water, which also uses a reduced Helmholtz approach: the IAPWS 1995 formulation {cite}`Wagner2002`. This equation is state is available using CoolProp with the `Water` fluid.

One modern approach for calculating thermodynamic properties of real fluids uses a reduced Helmholtz equation of state, using the reduced Helmholtz free energy function $\alpha$:
\begin{equation}
\alpha (\tau, \delta) = \frac{a}{RT} = \frac{u - Ts}{RT}
\end{equation}
which is a function of reduced density $\delta$ and reduced temperature $\tau$:
\begin{equation}
\delta = \frac{\rho}{\rho_{\text{crit}}} \quad \text{and} \quad \tau = \frac{T_{\text{crit}}}{T}
\end{equation}

The reduced Helmhotz free energy function, $\alpha(\tau, \delta)$, is given as the sum of ideal gas and residual components:
\begin{equation}
\alpha(\tau, \delta) = \alpha_{IG} (\tau, \delta) + \alpha_{\text{res}} (\tau, \delta) \;,
\end{equation}
which are both given as high-order fits using many coefficients:

```python
import matplotlib.pyplot as plt
%matplotlib inline

# these are mostly for making the saved figures nicer
import matplotlib_inline.backend_inline
matplotlib_inline.backend_inline.set_matplotlib_formats('pdf', 'png')
plt.rcParams['figure.dpi']= 150
plt.rcParams['savefig.dpi'] = 150

import numpy as np
import cantera as ct
from scipy import integrate, optimize

from pint import UnitRegistry
ureg = UnitRegistry()
Q_ = ureg.Quantity
```

```python
import sympy
sympy.init_printing(use_latex='mathjax')

T, R, tau, delta = sympy.symbols('T, R, tau, delta', real=True)

a_vars = sympy.symbols('a0, a1, a2, a3, a4, a5, a6, a7', real=True)
theta_vars = sympy.symbols('theta3, theta4, theta5, theta6, theta7', real=True)
n_vars = sympy.symbols('n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11', real=True)

alpha_ideal = sympy.log(delta) + a_vars[0] + a_vars[1]*tau + a_vars[2]*sympy.log(tau)
for i in range(3, 8):
    alpha_ideal += a_vars[i] * sympy.log(1.0 - sympy.exp(-tau * theta_vars[i-3]))
display(sympy.Eq(sympy.symbols('alpha_IG'), alpha_ideal))

alpha_res = (
    n_vars[0] * delta * tau**0.25 +
    n_vars[1] * delta * tau**1.25 +
    n_vars[2] * delta * tau**1.50 +
    n_vars[3] * delta**3 * tau**0.25 +
    n_vars[4] * delta**7 * tau**0.875 +
    n_vars[5] * delta * tau**2.375 * sympy.exp(-delta) +
    n_vars[6] * delta**2 * tau**2 * sympy.exp(-delta) +
    n_vars[7] * delta**5 * tau**2.125 * sympy.exp(-delta) +
    n_vars[8] * delta * tau**3.5 * sympy.exp(-delta**2) +
    n_vars[9] * delta * tau**6.5 * sympy.exp(-delta**2) +
    n_vars[10] * delta**4 * tau**4.75 * sympy.exp(-delta**2) +
    n_vars[11] * delta**2 * tau**12.5 * sympy.exp(-delta**3)
    )
display(sympy.Eq(sympy.symbols('alpha_res'), alpha_res))
```

$\displaystyle \alpha_{IG} = a_{0} + a_{1} \tau + a_{2} \log{\left(\tau \right)} + a_{3} \log{\left(1.0 - e^{- \tau \theta_{3}} \right)} + a_{4} \log{\left(1.0 - e^{- \tau \theta_{4}} \right)} + a_{5} \log{\left(1.0 - e^{- \tau \theta_{5}} \right)} + a_{6} \log{\left(1.0 - e^{- \tau \theta_{6}} \right)} + a_{7} \log{\left(1.0 - e^{- \tau \theta_{7}} \right)} + \log{\left(\delta \right)}$

$\displaystyle \alpha_{res} = \delta^{7} n_{4} \tau^{0.875} + \delta^{5} n_{7} \tau^{2.125} e^{- \delta} + \delta^{4} n_{10} \tau^{4.75} e^{- \delta^{2}} + \delta^{3} n_{3} \tau^{0.25} + \delta^{2} n_{11} \tau^{12.5} e^{- \delta^{3}} + \delta^{2} n_{6} \tau^{2} e^{- \delta} + \delta n_{0} \tau^{0.25} + \delta n_{1} \tau^{1.25} + \delta n_{2} \tau^{1.5} + \delta n_{5} \tau^{2.375} e^{- \delta} + \delta n_{8} \tau^{3.5} e^{- \delta^{2}} + \delta n_{9} \tau^{6.5} e^{- \delta^{2}}$

## Carbon dioxide equation of state

The coefficients $a_i$, $\theta_i$, and $n_i$ are given for carbon dioxide:

```python
# actual coefficients
coeffs_a = [
    8.37304456, -3.70454304, 2.500000, 1.99427042,
    0.62105248, 0.41195293, 1.04028922, 8.327678e-2
    ]
coeffs_theta = [
    3.151630, 6.111900, 6.777080, 11.32384, 27.08792
    ]
coeffs_n = [
    0.89875108, -0.21281985e1, -0.68190320e-1, 0.76355306e-1,
    0.22053253e-3, 0.41541823, 0.71335657, 0.30354234e-3,
    -0.36643143, -0.14407781e-2, -0.89166707e-1, -0.23699887e-1
    ]
```

Through some math, we can find an expression for pressure:
\begin{equation}
P = R T \rho \left[ 1 + \delta \left(\frac{\partial \alpha_{\text{res}}}{\partial \delta} \right)_{\tau} \right]
\end{equation}

Use this expression to estimate the pressure at $T$ = 350 K and $v$ = 0.01 m$^3$/kg, and compare against that obtained from Cantera. We can use our symbolic expression for $\alpha_{\text{res}} (\tau, \delta)$ and take the partial derivative:

```python
# use Cantera fluid to get specific gas constant and critical properties
f = ct.CarbonDioxide()
gas_constant = ct.gas_constant / f.mean_molecular_weight
temp_crit = f.critical_temperature
density_crit = f.critical_density

# conditions of interest
temp = 350
specific_volume = 0.01
density = 1.0 / specific_volume

# take the partial derivative of alpha_res with respect to delta
derivative_alpha_delta = sympy.diff(alpha_res, delta)

# substitute all coefficients
derivative_alpha_delta = derivative_alpha_delta.subs(
    [(n, n_val) for n, n_val in zip(n_vars, coeffs_n)]
    )

def get_pressure(
    temp, specific_vol, fluid, derivative_alpha_delta, tau, delta
    ):
    '''Calculates pressure for reduced Helmholtz equation of state'''
    red_density = (1.0 / specific_vol) / fluid.critical_density
    red_temp_inv = fluid.critical_temperature / temp

    gas_constant = ct.gas_constant / fluid.mean_molecular_weight

    dalpha_ddelta = derivative_alpha_delta.subs(
        [(delta, red_density), (tau, red_temp_inv)]
        )

    pres = (
        gas_constant * temp * (1.0 / specific_vol) *
        (1.0 + red_density * dalpha_ddelta)
        )
    return pres

pres = get_pressure(temp, specific_volume, f, derivative_alpha_delta, tau, delta)

print(f'Pressure: {pres / 1e6: .3f} MPa')

f.TV = temp, specific_volume
print(f'Cantera pressure: {f.P / 1e6: .3f} MPa')
```

    Pressure:  5.464 MPa
    Cantera pressure:  5.475 MPa

Our calculation and that from Cantera agree fairly well! They are not exactly the same because Cantera uses a slightly different formulation for the equation of state.

Let's compare the calculations now for a range of specific volumes and multiple temperatures:

```python
fig, ax = plt.subplots(figsize=(8, 4))

specific_volumes = np.geomspace(0.001, 0.01, num=20)
temperatures = [300, 400, 500]

for temp in temperatures:
    pressures = np.zeros(len(specific_volumes))
    pressures_cantera = np.zeros(len(specific_volumes))

    for idx, spec_vol in enumerate(specific_volumes):
        pressures[idx] = get_pressure(
            temp, spec_vol, f,
            derivative_alpha_delta, tau, delta
            )

        f.TV = temp, spec_vol
        pressures_cantera[idx] = f.P

    ax.loglog(specific_volumes, pressures/1000., 'o', color='blue')
    ax.loglog(specific_volumes, pressures_cantera/1000., color='blue')

bbox_props = dict(boxstyle='round', fc='w', ec='0.3', alpha=0.9)
ax.text(2e-3, 4e3, '300 K', ha='center', va='center', bbox=bbox_props)
ax.text(2e-3, 1.6e4, '400 K', ha='center', va='center', bbox=bbox_props)
ax.text(2e-3, 7e4, '500 K', ha='center', va='center', bbox=bbox_props)

ax.legend(['Reduced Helmholtz', 'Cantera'])
plt.grid(True, which='both')
plt.xlabel('Specific volume (m^3/kg)')
plt.ylabel('Pressure (kPa)')
fig.tight_layout()
plt.show()
```

We can see that the pressure calculated using the reduced Helmholtz equation of state matches closely with that from Cantera, which uses a different but similarly advanced equation of state.


# A more complex example for modeling drug-protein interactions with asymmetric RAF kinase dimerization and two RAF inhibitors

Here, we propose a more complex example of modeling RAF kinases and RAF inhibitors by reproducing a model of asymmetric RAF kinase dimerization and combination of two RAF inhibitors. This model was originally developed in *Kholodenko BN (2015) Drug Resistance Resulting from Kinase Dimerization Is Rationalized by Thermodynamic Factors Describing Allosteric Inhibitor Effects. Cell Rep 12: 1939–1949*. In the paper, the model is shown in *Figure 3* and the corresponding rate derivations are in *Reaction List S3*. While in the original work the cooperative thermodynamic reaction rates were derived manually, here we use energy PySB (via energy BNG) to recreate the same reaction network structure.

First, we define the model:

```python
from pysb import Model, Monomer, Parameter, Expression,  Rule, Observable, Initial, Annotation, EnergyPattern, ANY
from pysb.bng import generate_equations
from pysb.export import export
from pysb.core import as_complex_pattern, ComplexPattern
from sympy import exp, log

Model();
model.name='complex_example_asym_RAF_two_RAFi';

#define RAF kinase with a binding site for RAF (r) and a drug (i) that can be into two configurations when dimerized (R1,R2)
Monomer('R', ['r', 'i', 'state'], {'state':['none', 'R1', 'R2']});
#define first RAF inhibitor with binding site for RAF
Monomer('I1',['r']);
#define second RAF inhibitor with binding site for RAF
Monomer('I2',['r']);

#define the initial conditions for R and I1,I2
Parameter('R_0',0.01);  # uM
Parameter('I1_0',0.0);  # uM
Parameter('I2_0',0.0);  # uM
Initial(R(r=None, i=None, state='none'), R_0);
Initial(I1(r=None), I1_0);
Initial(I2(r=None), I2_0);

#define kinetic parameters and rules for independent binding
#RAF dimerization
#define dissociation constant (kD), forward rate (kf) and distributionr rate (phi) for RAF dimerization
Parameter('kr_1',10);  #/s
Parameter('kf_1',1.0);  #/s/uM
Expression('Kd_1', kr_1 / kf_1);
Parameter('phi_1',1.0); #unitless
#convert kinetic parameters into energies for RAF dimerization
Expression('Gf_1', log(kr_1/kf_1)); #unitless
Expression('Ea0_1',-phi_1*log(kr_1/kf_1)-log(kf_1)); #unitless
# define energy in bond between R and R
EnergyPattern('ep_1',R(r=1)%R(r=1),Gf_1);
#define RAF dimerization reaction with asymmetry of configurations R1 and R2
Rule('R1', R(r=None, state='none')+R(r=None, state='none') | R(r=1, state='R1')%R(r=1, state='R2') , phi_1, Ea0_1, energy=True);

#I1 binding to RAF
#define dissociation constant (kD), forward rate (kf) and distributionr rate (phi) for drug binding to RAF
Parameter('kr_2a',0.1);  #/s
Parameter('kf_2a',1.0);  #/s/uM
Expression('Kd_2a', kr_2a / kf_2a);
Parameter('phi_2a',1.0); #unitless
#convert kinetic parameters into energies for drug binding to RAF
Expression('Gf_2a', log(kr_2a/kf_2a)); #unitless
Expression('Ea0_2a',-phi_2a*log(kr_2a/kf_2a)-log(kf_2a)); #unitless
# define energy in bond between R and I
EnergyPattern('ep_2a',R(i=1)%I1(r=1),Gf_2a);
#define drug binding to RAF reaction
Rule('R2a', R(i=None)+I1(r=None) | R(i=1)%I1(r=1) , phi_2a, Ea0_2a, energy=True);

#I2 binding to RAF
#define dissociation constant (kD), forward rate (kf) and distributionr rate (phi) for drug binding to RAF
Parameter('kr_2b',0.1);  #/s
Parameter('kf_2b',1.0);  #/s/uM
Expression('Kd_2b', kr_2b / kf_2b);
Parameter('phi_2b',1.0); #unitless
#convert kinetic parameters into energies for drug binding to RAF
Expression('Gf_2b', log(kr_2b/kf_2b)); #unitless
Expression('Ea0_2b',-phi_2b*log(kr_2b/kf_2b)-log(kf_2b)); #unitless
# define energy in bond between R and I
EnergyPattern('ep_3',R(i=1)%I2(r=1),Gf_2b);
#define drug binding to RAF reaction
Rule('R2b', R(i=None)+I2(r=None) | R(i=1)%I2(r=1) , phi_2b, Ea0_2b, energy=True);
```

Next, we define the thermodynamic parameters and impose energy patterns to model cooperativity in drug-protein interactions.
The thermodynamic parameters quantify the following:
* *f_a*: The Kd ratio of I1 binding to the free monomer R versus to the promoter R1 in the R1-R2 dimers (yielding R1I1-R2)
* *f_b*: The Kd ratio of I2 binding to the free monomer R versus to the promoter R1 in the R1-R2 dimers (yielding R1I2-R2)
* *g_1a*: The Kd ratio of I1 binding to the protomer R1 versus the promoter R2 in the R1-R2 dimers (yielding R1-R2I1)
* *g_1b*: The Kd ratio of I2 binding to the protomer R1 versus to the promoter R2 in the R1-R2 dimers (yielding R1-R2I2)
* *g_2a*: The Kd ratio of I1 binding to the free monomer R versus to the protomer R1 in the R1-R2I1 dimers (yielding R1I1R2I1)
* *g_2b*: The Kd ratio of I2 binding to the free monomer R versus to the protomer R1 in the R1-R2I2 dimers (yielding R1I2R2I1)
* *g_3a*: The Kd ratio of I1 binding to a free monomer R versus to the promoter R1 in the R1-R2I2 dimer (yielding R1I1-R2I2)
* *g_3b*: The Kd ratio of I2 binding to a free monomer R versus to the promoter R1 in the R1-R2I1 dimer (yielding R1I1-R2I1)

```python
#define thermodynamic factors
Parameter('f_a',1.0);  #unitless
Parameter('f_b',1.0);  #unitless
Parameter('g_1a',1.0);  #unitless
Parameter('g_1b',1.0);  #unitless
Parameter('g_2a',1.0);  #unitless
Parameter('g_2b',1.0);  #unitless
Parameter('g_3a',1.0);  #unitless
Parameter('g_3b',1.0);  #unitless

#convert thermodynamic factors in energies
Expression('Gf_fa',log(f_a)); #unitless
Expression('Gf_fb',log(f_b)); #unitless
Expression('Gf_g1a',log(g_1a)); #unitless
Expression('Gf_g1b',log(g_1b)); #unitless
Expression('Gf_g2a',log(g_2a)); #unitless
Expression('Gf_g2b',log(g_2b)); #unitless
Expression('Gf_g3a',log(g_3a)); #unitless
Expression('Gf_g3b',log(g_3b)); #unitless

#define energy patterns for fa
EnergyPattern('ep_fa_R2R1I1',R(r=1,state='R2')%R(r=1,i=2,state='R1')%I1(r=2), Gf_fa);
#define energy patterns for fb
EnergyPattern('ep_fb_R2R1I2',R(r=1,state='R2')%R(r=1,i=2,state='R1')%I2(r=2), Gf_fb);
#define energy patterns for g1a
Expression('Gf_g1a_fa', Gf_g1a + Gf_fa);
EnergyPattern('ep_g1a_R1R2I1',R(r=1,state='R1')%R(r=1,i=2,state='R2')%I1(r=2), Gf_g1a_fa);
#define energy patterns for g1b
Expression('Gf_g1b_fb', Gf_g1b + Gf_fb);
EnergyPattern('ep_g1b_R1R2I2',R(r=1,state='R1')%R(r=1,i=2,state='R2')%I2(r=2), Gf_g1b_fb);
#define energy pattern for g2a
Expression('Gf_g2a_fa', Gf_g2a - Gf_fa);
EnergyPattern('ep_g2a_I1R1R2I1',I1(r=3)%R(r=1,i=3,state='R1')%R(r=1,i=2,state='R2')%I1(r=2), Gf_g2a_fa);
#define energy pattern for g2b
Expression('Gf_g2b_fb', Gf_g2b - Gf_fb);
EnergyPattern('ep_g2b_I2R1R2I2',I2(r=3)%R(r=1,i=3,state='R1')%R(r=1,i=2,state='R2')%I2(r=2), Gf_g2b_fb);
#define energy pattern for g3a
Expression('Gf_g3a_fa', Gf_g3a - Gf_fa);
EnergyPattern('ep_g3a_I1R1R2I2',I2(r=3)%R(r=1,i=3,state='R2')%R(r=1,i=2,state='R1')%I1(r=2), Gf_g3a_fa);
#define energy pattern for g3b
Expression('Gf_g3b_fb', Gf_g3b - Gf_fb);
EnergyPattern('ep_g3b_I2R1R2I1',I1(r=3)%R(r=1,i=3,state='R2')%R(r=1,i=2,state='R1')%I2(r=2), Gf_g3b_fb);

#define the observable for total kinase activity (proportional to the weighted sum of the concentrations of
#inhibitor free monomers and dimers and partially inhibited dimers: we assume lamba 10-15, p1=p2=0.5, see Section S2.5
#of the original paper)
Parameter('kcat',1.0);
Parameter('plambda',10.0);
Parameter('p1', 0.5);
Parameter('p2', 0.5);
Observable('R_obs', R(i=None, r=None));
Observable('RR_obs', R(i=None, r=1)%R(i=None, r=1));
Observable('R1R2I_obs', R(i=ANY, r=1, state='R1')%R(i=None, r=1, state='R2'));
Observable('R2R1I_obs', R(i=ANY, r=1, state='R2')%R(i=None, r=1, state='R1'));
Observable('IRRI_obs', R(i=ANY, r=1,)%R(i=ANY, r=1));
Expression('R_active', kcat * (R_obs + plambda * ( RR_obs + p1 * (R1R2I_obs) + p2 * (R2R1I_obs) ) ));
```

We generate the kinetic model:

```python
from util_display import display_model_info

# generate the model equations
generate_equations(model)

#display model informations
display_model_info(model)

#save the generated model in PySB and BNG format
generated_model_code = export(model, 'pysb_flat')
with open(model.name+'.py', 'wt') as f:
    f.write(generated_model_code);

generated_model_code = export(model, 'bngl')
with open(model.name+'.bngl', 'wt') as f:
    f.write(generated_model_code);
```

    Model information
    Species: 14
    Parameters: 27
    Expressions: 24
    Observables: 5
    Total Rules: 3
    Energy Rules: 3
    Non-energy Rules: 0
    Energy Patterns: 11
    Reactions: 46

We inspect the species and reaction rates automatically generated by the energy-based model formulation:

```python
from util_display import format_species_reactions, display_table
import pandas as pd
# Prevent pandas from truncating long LaTeX expressions when rendering.
pd.options.display.max_colwidth=None

#obtain dataframe with math latex expression visualization for species and reactions
(speciesdisp, reactionsdisp)=format_species_reactions(model);
#display species with the right style
display_table(speciesdisp, caption='SPECIES');

#display reactions in the same order as Reaction List S3 in original publication
reaction_order=[0,1,2,11,13, 12, 14, 5, 8, 3, 4, 17, 22, 15, 20, 6, 10, 7, 21, 16, 9, 18, 19];
display_table(reactionsdisp.iloc[reaction_order], caption='REACTIONS');
```

SPECIES

        ID	        Pattern

                                \[s_{0}\]
                        R(r=None, i=None, state='none')

                                \[s_{1}\]
                        I1(r=None)

                                \[s_{2}\]
                        I2(r=None)

                                \[s_{3}\]
                        R(r=1, i=None, state='R1') % R(r=1, i=None, state='R2')

                                \[s_{4}\]
                        I1(r=1) % R(r=None, i=1, state='none')

                                \[s_{5}\]
                        I2(r=1) % R(r=None, i=1, state='none')

                                \[s_{6}\]
                        I1(r=1) % R(r=2, i=1, state='R2') % R(r=2, i=None, state='R1')

                                \[s_{7}\]
                        I2(r=1) % R(r=2, i=1, state='R2') % R(r=2, i=None, state='R1')

                                \[s_{8}\]
                        I1(r=1) % R(r=2, i=1, state='R1') % R(r=2, i=None, state='R2')

                                \[s_{9}\]
                        I1(r=1) % I1(r=2) % R(r=3, i=1, state='R1') % R(r=3, i=2, state='R2')

                                \[s_{10}\]
                        I1(r=1) % I2(r=2) % R(r=3, i=1, state='R1') % R(r=3, i=2, state='R2')

                                \[s_{11}\]
                        I2(r=1) % R(r=2, i=1, state='R1') % R(r=2, i=None, state='R2')

                                \[s_{12}\]
                        I1(r=1) % I2(r=2) % R(r=3, i=2, state='R1') % R(r=3, i=1, state='R2')

                                \[s_{13}\]
                        I2(r=1) % I2(r=2) % R(r=3, i=1, state='R1') % R(r=3, i=2, state='R2')

REACTIONS

        ID	        Base rule	        Reaction	        Forward rate ($k$ )	        Backward rate ($kr$ )	        Dissociation constant ($K_d$ )

                                \[R_{0}\]
                        R1
                        $s_{0}$ + $s_{0}$ ⇄ $s_{3}$
                        \[k_{1}\]
                        \[kr_{1}\]
                        \[\frac{kr_{1}}{k_{1}}\]

                                \[R_{1}\]
                        R2a
                        $s_{0}$ + $s_{1}$ ⇄ $s_{4}$
                        \[k_{2a}\]
                        \[kr_{2a}\]
                        \[\frac{kr_{2a}}{k_{2a}}\]

                                \[R_{2}\]
                        R2b
                        $s_{0}$ + $s_{2}$ ⇄ $s_{5}$
                        \[k_{2b}\]
                        \[kr_{2b}\]
                        \[\frac{kr_{2b}}{k_{2b}}\]

                                \[R_{11}\]
                        R2a
                        $s_{1}$ + $s_{3}$ ⇄ $s_{8}$
                        \[f_{a}^{- \phi_{2a}} \cdot k_{2a}\]
                        \[f_{a}^{1 - \phi_{2a}} \cdot kr_{2a}\]
                        \[\frac{f_{a} \cdot kr_{2a}}{k_{2a}}\]

                                \[R_{13}\]
                        R2b
                        $s_{2}$ + $s_{3}$ ⇄ $s_{11}$
                        \[f_{b}^{- \phi_{2b}} \cdot k_{2b}\]
                        \[f_{b}^{1 - \phi_{2b}} \cdot kr_{2b}\]
                        \[\frac{f_{b} \cdot kr_{2b}}{k_{2b}}\]

                                \[R_{12}\]
                        R2a
                        $s_{1}$ + $s_{3}$ ⇄ $s_{6}$
                        \[k_{2a} \cdot \left(\frac{1}{f_{a} \cdot g_{1a}}\right)^{\phi_{2a}}\]
                        \[kr_{2a} \cdot \left(f_{a} \cdot g_{1a}\right)^{1 - \phi_{2a}}\]
                        \[\frac{f_{a} \cdot g_{1a} \cdot kr_{2a}}{k_{2a}}\]

                                \[R_{14}\]
                        R2b
                        $s_{2}$ + $s_{3}$ ⇄ $s_{7}$
                        \[k_{2b} \cdot \left(\frac{1}{f_{b} \cdot g_{1b}}\right)^{\phi_{2b}}\]
                        \[kr_{2b} \cdot \left(f_{b} \cdot g_{1b}\right)^{1 - \phi_{2b}}\]
                        \[\frac{f_{b} \cdot g_{1b} \cdot kr_{2b}}{k_{2b}}\]

                                \[R_{5}\]
                        R1
                        $s_{0}$ + $s_{4}$ ⇄ $s_{8}$
                        \[f_{a}^{- \phi_{1}} \cdot k_{1}\]
                        \[f_{a}^{1 - \phi_{1}} \cdot kr_{1}\]
                        \[\frac{f_{a} \cdot kr_{1}}{k_{1}}\]

                                \[R_{8}\]
                        R1
                        $s_{0}$ + $s_{5}$ ⇄ $s_{11}$
                        \[f_{b}^{- \phi_{1}} \cdot k_{1}\]
                        \[f_{b}^{1 - \phi_{1}} \cdot kr_{1}\]
                        \[\frac{f_{b} \cdot kr_{1}}{k_{1}}\]

                                \[R_{3}\]
                        R1
                        $s_{0}$ + $s_{4}$ ⇄ $s_{6}$
                        \[k_{1} \cdot \left(\frac{1}{f_{a} \cdot g_{1a}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{a} \cdot g_{1a}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{a} \cdot g_{1a} \cdot kr_{1}}{k_{1}}\]

                                \[R_{4}\]
                        R1
                        $s_{0}$ + $s_{5}$ ⇄ $s_{7}$
                        \[k_{1} \cdot \left(\frac{1}{f_{b} \cdot g_{1b}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{b} \cdot g_{1b}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{b} \cdot g_{1b} \cdot kr_{1}}{k_{1}}\]

                                \[R_{17}\]
                        R2a
                        $s_{1}$ + $s_{8}$ ⇄ $s_{9}$
                        \[k_{2a} \cdot \left(\frac{1}{g_{1a} \cdot g_{2a}}\right)^{\phi_{2a}}\]
                        \[kr_{2a} \cdot \left(g_{1a} \cdot g_{2a}\right)^{1 - \phi_{2a}}\]
                        \[\frac{g_{1a} \cdot g_{2a} \cdot kr_{2a}}{k_{2a}}\]

                                \[R_{22}\]
                        R2b
                        $s_{2}$ + $s_{11}$ ⇄ $s_{13}$
                        \[k_{2b} \cdot \left(\frac{1}{g_{1b} \cdot g_{2b}}\right)^{\phi_{2b}}\]
                        \[kr_{2b} \cdot \left(g_{1b} \cdot g_{2b}\right)^{1 - \phi_{2b}}\]
                        \[\frac{g_{1b} \cdot g_{2b} \cdot kr_{2b}}{k_{2b}}\]

                                \[R_{15}\]
                        R2a
                        $s_{1}$ + $s_{6}$ ⇄ $s_{9}$
                        \[g_{2a}^{- \phi_{2a}} \cdot k_{2a}\]
                        \[g_{2a}^{1 - \phi_{2a}} \cdot kr_{2a}\]
                        \[\frac{g_{2a} \cdot kr_{2a}}{k_{2a}}\]

                                \[R_{20}\]
                        R2b
                        $s_{2}$ + $s_{7}$ ⇄ $s_{13}$
                        \[g_{2b}^{- \phi_{2b}} \cdot k_{2b}\]
                        \[g_{2b}^{1 - \phi_{2b}} \cdot kr_{2b}\]
                        \[\frac{g_{2b} \cdot kr_{2b}}{k_{2b}}\]

                                \[R_{6}\]
                        R1
                        $s_{4}$ + $s_{4}$ ⇄ $s_{9}$
                        \[k_{1} \cdot \left(\frac{1}{f_{a} \cdot g_{1a} \cdot g_{2a}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{a} \cdot g_{1a} \cdot g_{2a}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{a} \cdot g_{1a} \cdot g_{2a} \cdot kr_{1}}{k_{1}}\]

                                \[R_{10}\]
                        R1
                        $s_{5}$ + $s_{5}$ ⇄ $s_{13}$
                        \[k_{1} \cdot \left(\frac{1}{f_{b} \cdot g_{1b} \cdot g_{2b}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{b} \cdot g_{1b} \cdot g_{2b}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{b} \cdot g_{1b} \cdot g_{2b} \cdot kr_{1}}{k_{1}}\]

                                \[R_{7}\]
                        R1
                        $s_{4}$ + $s_{5}$ ⇄ $s_{10}$
                        \[k_{1} \cdot \left(\frac{1}{f_{b} \cdot g_{1b} \cdot g_{3a}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{b} \cdot g_{1b} \cdot g_{3a}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{b} \cdot g_{1b} \cdot g_{3a} \cdot kr_{1}}{k_{1}}\]

                                \[R_{21}\]
                        R2b
                        $s_{2}$ + $s_{8}$ ⇄ $s_{10}$
                        \[k_{2b}^{1 - \phi_{2b}} \cdot \left(\frac{f_{a} \cdot k_{2b}}{f_{b} \cdot g_{1b} \cdot g_{3a}}\right)^{\phi_{2b}}\]
                        \[k_{2b} \cdot \left(\frac{kr_{2b}}{k_{2b}}\right)^{\phi_{2b}} \cdot \left(\frac{f_{b} \cdot g_{1b} \cdot g_{3a} \cdot kr_{2b}}{f_{a} \cdot k_{2b}}\right)^{1 - \phi_{2b}}\]
                        \[\frac{f_{b} \cdot g_{1b} \cdot g_{3a} \cdot kr_{2b}}{f_{a} \cdot k_{2b}}\]

                                \[R_{16}\]
                        R2a
                        $s_{1}$ + $s_{7}$ ⇄ $s_{10}$
                        \[g_{3a}^{- \phi_{2a}} \cdot k_{2a}\]
                        \[g_{3a}^{1 - \phi_{2a}} \cdot kr_{2a}\]
                        \[\frac{g_{3a} \cdot kr_{2a}}{k_{2a}}\]

                                \[R_{9}\]
                        R1
                        $s_{4}$ + $s_{5}$ ⇄ $s_{12}$
                        \[k_{1} \cdot \left(\frac{1}{f_{a} \cdot g_{1a} \cdot g_{3b}}\right)^{\phi_{1}}\]
                        \[kr_{1} \cdot \left(f_{a} \cdot g_{1a} \cdot g_{3b}\right)^{1 - \phi_{1}}\]
                        \[\frac{f_{a} \cdot g_{1a} \cdot g_{3b} \cdot kr_{1}}{k_{1}}\]

                                \[R_{18}\]
                        R2a
                        $s_{1}$ + $s_{11}$ ⇄ $s_{12}$
                        \[k_{2a}^{1 - \phi_{2a}} \cdot \left(\frac{f_{b} \cdot k_{2a}}{f_{a} \cdot g_{1a} \cdot g_{3b}}\right)^{\phi_{2a}}\]
                        \[k_{2a} \cdot \left(\frac{kr_{2a}}{k_{2a}}\right)^{\phi_{2a}} \cdot \left(\frac{f_{a} \cdot g_{1a} \cdot g_{3b} \cdot kr_{2a}}{f_{b} \cdot k_{2a}}\right)^{1 - \phi_{2a}}\]
                        \[\frac{f_{a} \cdot g_{1a} \cdot g_{3b} \cdot kr_{2a}}{f_{b} \cdot k_{2a}}\]

                                \[R_{19}\]
                        R2b
                        $s_{2}$ + $s_{6}$ ⇄ $s_{12}$
                        \[g_{3b}^{- \phi_{2b}} \cdot k_{2b}\]
                        \[g_{3b}^{1 - \phi_{2b}} \cdot kr_{2b}\]
                        \[\frac{g_{3b} \cdot kr_{2b}}{k_{2b}}\]

Having generated the model, we can now study the efficacy of RAF inhibitors with different thermodynamic properties using model simulations. First, we set up parameters to reproduce Figures 3B-C and S3 in the original paper which shows the synergy between I1 and I2 with under certain conditions of cooperativity. We simulate the response of the model for each combination of parameters and the selected RAF inhibitor concentrations:

```python
from tqdm.notebook import tqdm, trange
from util_simulation import equilibrate
from pysb.simulator import ScipyOdeSimulator
import numpy as np

#set initial conditions
model.parameters['R_0'].value=1.0;
model.parameters['I1_0'].value=0.0;
model.parameters['I2_0'].value=0.0;

#set kinetic parameters
model.parameters['kf_1'].value=1.0;  #Kd_1= 100
model.parameters['kr_1'].value=100.0;
model.parameters['kf_2a'].value=1.0; #Kd_2a= 1
model.parameters['kr_2a'].value=1.0;
model.parameters['kf_2b'].value=2.0; #Kd_2b= 2
model.parameters['kr_2b'].value=1.0;

#define observables and expression to extract
ext_species=['R_active', 'R_obs', 'RR_obs', 'R1R2I_obs', 'R2R1I_obs', 'IRRI_obs'];
ext_species_names=['RAF active', 'R', 'RR', 'R1R2I', 'R2R1I', 'IRRI'];

#define cooperativity parameters in different scenarios to be simulated
param_names= [ 'f_a', 'f_b', 'g_1a', 'g_1b', 'g_2a', 'g_2b', 'g_3a',  'g_3b'];
param_values=[ [0.01, 0.005,      2,      5,     10,     20,   0.01,     10 ],    #synergistic (Fig. 3C)
               [  1.0,   1.0,   1.0,     1.0,    1.0,    1.0,   1.0,    1.0 ]];   #no cooperativity
params= pd.DataFrame(np.array(param_values), columns=param_names);

#set the dilution range for the RAF inhibitors
I1_doses=np.concatenate((np.array([0]), np.logspace(-2, 1, 25, base=10.0)))
I2_doses=np.concatenate((np.array([0]), np.logspace(-2, 1, 25, base=10.0)))

#define conditions of simulation
doses=pd.merge(pd.Series(I1_doses, name="I1_0"), pd.Series(I1_doses, name="I2_0"), how="cross")
conditions = pd.merge(params, doses, how="cross")

ss_v = pd.DataFrame(
    index=pd.MultiIndex.from_frame(conditions), columns=ext_species, dtype=float
)

#set up the ODE simulator for the model
sim = ScipyOdeSimulator(model);

#for each drug dose combination
for i, r in tqdm(conditions.iterrows(), desc="Simulation progress", total=len(conditions)):
    #simulate the model to steady state
    res = equilibrate(sim, param_values= (r.to_dict()))
    #extract end of simulation for each selected species
    ss_v.loc[tuple(r)] = res.dataframe[ext_species].iloc[-1]
```

    Simulation progress:   0%|          | 0/1352 [00:00<?, ?it/s]

We plot the results as dilution dose response for I1, I2 and I2 with I1 at the fixed IC_50 dose (Fig 3B):

```python
%matplotlib notebook
import matplotlib.pyplot as plt
from matplotlib.colors import DivergingNorm

#calculate IC50 of the drugs
I1_IC50 = model.parameters['kr_2a'].value / model.parameters['kf_2a'].value
I2_IC50 = model.parameters['kr_2b'].value / model.parameters['kf_2b'].value

#for each parameter set plot the dose responses
fig, ax = plt.subplots(1, len(params), sharey=True)
for i in range(len(params)):
    #extract datastructure for that parameter set
    sel_param=tuple(params.iloc[i].values)
    ss_v_param=ss_v.loc[sel_param]
    #extract reference condition at (0,0) drugs
    R_tot = ss_v_param.loc[(0, 0), "R_active"]
    #extract active RAFs for dose responses
    I1_response = ss_v_param.xs(0.0, level="I2_0")["R_active"]
    I2_response = ss_v_param.xs(0.0, level="I1_0")["R_active"]
    I2_I1IC50_response = ss_v_param.xs(I1_IC50, level="I1_0")["R_active"]
    #for I1 only
    ax[i].plot(I1_response.index, I1_response / R_tot, ".-k")
    #for I2 only
    ax[i].plot(I2_response.index, I2_response / R_tot, ".-b")
    #for I2 at fixed I1 dose
    ax[i].plot(I2_I1IC50_response.index, I2_I1IC50_response / R_tot, ".-r");
    ax[i].set_xlabel('Inhibitor dose');
    ax[i].set_ylabel('Normalized Total Activity');
    plt.tight_layout()
```

    <IPython.core.display.Javascript object>

The plot above shows synergy of two drugs that used as single agents do not inhibit RAF signaling but when taken as a combination with a fixed dose of I1 are able to block it. To fully quantify synergy, we show the isobologram analysis of I1 and I2 drug-dose combinations (Fig 3C) and also calculate the degree of synergy according to Bliss independence:

```python
#import synergy package
from synergy.combination import Bliss

#compare synergies among set of cooperative parameters
fig, ax = plt.subplots(len(params), 3)
plt_titles=['Model simulations', 'Bliss null model',  'Bliss excess'];
for i in range(len(params)):

    #extract datastructure for that parameter set
    sel_param=tuple(params.iloc[i].values)
    ss_v_param=ss_v.loc[sel_param]
    #prepare data to use synergy package
    E_sim=ss_v_param['R_active'].to_numpy()/R_tot;
    D1=ss_v_param.index.get_level_values(0).to_numpy();
    D2=ss_v_param.index.get_level_values(1).to_numpy();

    #create and fit the Bliss models
    bliss = Bliss()
    bliss.fit(D1, D2, E_sim);
    #reshape E_sim to matrix
    E_matrix_sim=ss_v_param["R_active"].unstack("I2_0").to_numpy()/R_tot;
    E_matrix_sim=np.rot90(E_matrix_sim);
    #extract bliss reference and bliss excess
    E_mat_bliss_ref=np.rot90(bliss.reference.reshape(len(I1_doses),len(I2_doses)));
    E_mat_bliss_syn=np.rot90(bliss.synergy.reshape(len(I1_doses),len(I2_doses)));
    Emax=np.maximum(E_mat_bliss_ref.max(), E_matrix_sim.max());
    Es=[E_matrix_sim, E_mat_bliss_ref, E_mat_bliss_syn];

    for j in range(len(plt_titles)):
        im=ax[i,j].imshow(Es[j], extent=[0, 1, 0, 1])
        fig.colorbar(im, ax=ax[i,j], fraction=0.046, pad=0.04)
        ax[i,j].set_xticks([])
        ax[i,j].set_yticks([])
        ax[i,j].title.set_text(plt_titles[j])
        if (i==len(params)-1):
            ax[i,j].set_xlabel('Drug 2')
        if (j==0):
            ax[i,j].set_ylabel('Drug 1')

plt.tight_layout()
```

    <IPython.core.display.Javascript object>

These plots show that, with certain cooperativity parameters, there exists dose ranges of drug 1 and 2 that induce synergistic inhibition of RAF signaling (first row). This synergy is not observed if the drugs have no cooperative interactions with RAF dimerization status.

To understand the reason for drug synergy, we plot observables that report the status of R dimerization and drug binding:

```python
#plot each species extract from simulations
fig, ax = plt.subplots(len(params),len(ext_species))

#for each parameter setting
for i in range(len(params)):

    #extract datastructure for that parameter set
    sel_param=tuple(params.iloc[i].values)
    ss_v_param=ss_v.loc[sel_param]

    #for each specie
    for j in range(len(ext_species)):
        E_matrix_sim=ss_v_param[ext_species[j]].unstack("I2_0").to_numpy();
        E_matrix_sim=np.rot90(E_matrix_sim);
        im=ax[i,j].imshow(E_matrix_sim, extent=[0, 1, 0, 1])
        ax[i,j].set_xticks([])
        ax[i,j].set_yticks([])
        ax[i,j].title.set_text(ext_species_names[j])
        if (i==len(params)-1):
            ax[i,j].set_xlabel('Drug 2')
        if (j==0):
            ax[i,j].set_ylabel('Drug 1')

plt.tight_layout()
```

    <IPython.core.display.Javascript object>

These simulations show that in the synergistic case there are drug concentrations of drug 1 and 2 that block both R1 and R2 configurations of RAF dimers at concentrations in which, as single agents, both drugs promoter RAF dimerization and only partially inhibit one protomer in RAF dimers (first row). In absence of cooperativity, the drugs show no synergy: drugs 1 and 2 shut down RAF signaling at the same concentration as single agents (second row).


# 線形代数

## 行列

以下のように縦横に数を並べたものを **行列** と呼ぶ

$$
\boldsymbol{X} = \left(\begin{array}{cc} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{array}\right)
$$

行列の横の並びを **行**、縦の並びを **列** と呼び、$m\times{n}$ 次元の行列は以下のように定義される

$$
\boldsymbol{A} = \left(
\begin{array}{cccc}
a_{11} & a_{12} & \ldots & a_{1n} \\
a_{21} & a_{22} & \ldots & a_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} & a_{m2} & \ldots & a_{mn}
\end{array}
\right)
$$

行列の $i$ 行目、$j$ 行目にあたる成分を $(i, j)$ 成分と呼び、特に混乱がない場合は $a_{21}$ は $(2, 1)$ 成分を意味するものとする

### 行列の演算
行列の和、差、スカラー倍などは、ベクトルのときと同様に、各成分ごとの演算により定義される

$$
\begin{align}
\boldsymbol{A} + \boldsymbol{B} &= \left(
\begin{array}{cccc}
a_{11} + b_{11} & a_{12} + b_{12} & \ldots & a_{1n} + b_{1n} \\
a_{21} + b_{21} & a_{22} + b_{22} & \ldots & a_{2n} + b_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} + b_{m1} & a_{m2} + b_{m2} & \ldots & a_{mn} + b_{mn}
\end{array}
\right) \\
k\boldsymbol{A} &= \left(
\begin{array}{cccc}
ka_{11} & ka_{12} & \ldots & ka_{1n} \\
ka_{21} & ka_{22} & \ldots & ka_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
ka_{m1} & ka_{m2} & \ldots & ka_{mn}
\end{array}
\right) \\
-\boldsymbol{A} &= \left(
\begin{array}{cccc}
-a_{11} & -a_{12} & \ldots & -a_{1n} \\
-a_{21} & -a_{22} & \ldots & -a_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
-a_{m1} & -a_{m2} & \ldots & -a_{mn}
\end{array}
\right) \\
\boldsymbol{A} - \boldsymbol{B} &= \boldsymbol{A} + (-\boldsymbol{B}) = \left(
\begin{array}{cccc}
a_{11} - b_{11} & a_{12} - b_{12} & \ldots & a_{1n} - b_{1n} \\
a_{21} - b_{21} & a_{22} - b_{22} & \ldots & a_{2n} - b_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} - b_{m1} & a_{m2} - b_{m2} & \ldots & a_{mn} - b_{mn}
\end{array}
\right)
\end{align}
$$

上記の通り、行列の和・差が定義されるのは、2つの行列のサイズ（次元数）が一致する場合のみである

次に、行列とベクトルの積を定義する

$$
\boldsymbol{A} = \left(
\begin{array}{cccc}
a_{11} & a_{12} & \ldots & a_{1n} \\
a_{21} & a_{22} & \ldots & a_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1} & a_{m2} & \ldots & a_{mn}
\end{array}
\right), \quad \vec{v} = \left(\begin{array}{c} v_1 \\ v_2 \\ v_n \end{array}\right) \\
\boldsymbol{A}\vec{v} = \left(
\begin{array}{cccc}
a_{11}v_{1} & a_{12}v_{2} & \ldots & a_{1n}v_{n} \\
a_{21}v_{1} & a_{22}v_{2} & \ldots & a_{2n}v_{n} \\
\vdots & \vdots & \ddots & \vdots \\
a_{m1}v_{1} & a_{m2}v_{2} & \ldots & a_{mn}v_{n}
\end{array}
\right)
$$

これはつまり、$\boldsymbol{A}$ の各行をベクトルとみなし、それらのベクトルと $\vec{v}$ の内積を並べたものとなる

上記定義から分かるように、積が定義されるのは行列の行数とベクトルの次元が一致する場合のみである

行列の積の応用として典型的なのは、連立方程式の表記の簡略化である

例えば、次のような連立方程式を考える

$$
\left\{
\begin{align}
& 2x + y + 3z = 7 \\
& x -y + z = 4 \\
& x + 2y - z = -3
\end{align}
\right.
$$

これは、行列を使って以下のように書くことが出来る

$$
\left(
\begin{array}{ccc}
2 & 1 & 3 \\
1 & -1 & 1 \\
1 & 2 & -1
\end{array}
\right) \left(\begin{array}{c} x \\ y \\ z \end{array}\right)
=
\left(\begin{array}{c} 7 \\ 4 \\ -3 \end{array}\right)
$$

同様に行列同士の積も定義できる

行列積 $\boldsymbol{A}\boldsymbol{B}$ が定義できるのは、$\boldsymbol{A}$ の列数と $\boldsymbol{B}$ の行数が等しい場合のみで、以下のように定義される

$$
\boldsymbol{A}\boldsymbol{B} = \left(\begin{array}{cccc} \boldsymbol{A}b_1 & \boldsymbol{A}b_2 & \ldots & \boldsymbol{A}b_l \end{array}\right)
=
\left(
\begin{array}{cccc}
\sum_{k=1}^{n}a_{1k}b_{k1} & \sum_{k=1}^{n}a_{1k}b_{k2} & \ldots & \sum_{k=1}^{n}a_{1k}b_{kl} \\
\sum_{k=1}^{n}a_{2k}b_{k1} & \sum_{k=1}^{n}a_{2k}b_{k2} & \ldots & \sum_{k=1}^{n}a_{2k}b_{kl} \\
\vdots & \vdots & \ddots & \vdots \\
\sum_{k=1}^{n}a_{mk}b_{k1} & \sum_{k=1}^{n}a_{mk}b_{k2} & \ldots & \sum_{k=1}^{n}a_{mk}b_{kl}
\end{array}
\right)
$$

ここで、行列の積については交換法則 $\boldsymbol{A}\boldsymbol{B} = \boldsymbol{B}\boldsymbol{A}$ が成り立たないことに注意

一般には計算結果が一致しないだけでなく $\boldsymbol{A}\boldsymbol{B}$ が定義されているのに $\boldsymbol{B}\boldsymbol{A}$ が定義されないことすらありえる（$\boldsymbol{B}$ の列数と $\boldsymbol{A}$ の行数が一致しない場合）

行列の積については、以下の法則が知られている

$$
\begin{align}
(k\boldsymbol{A})\boldsymbol{B} &= k(\boldsymbol{A}\boldsymbol{B}) \\
(\boldsymbol{A}\boldsymbol{B})\boldsymbol{C} &= \boldsymbol{A}(\boldsymbol{B}\boldsymbol{C}) \quad （結合法則） \\
\boldsymbol{A}(\boldsymbol{B} + \boldsymbol{C}) &= \boldsymbol{A}\boldsymbol{B} + \boldsymbol{A}\boldsymbol{C} \\
(\boldsymbol{A} + \boldsymbol{B})\boldsymbol{C} &= \boldsymbol{A}\boldsymbol{C} + \boldsymbol{B}\boldsymbol{C} \quad （分配法則）
\end{align}
$$

全成分が 0 であるような行列を **ゼロ行列** と呼び、サイズ $m\times{n}$ であるようなゼロ行列は $\boldsymbol{0}_{m,n}$ と表記する

$\boldsymbol{A}$ が $m\times{n}$ 行列であるならば、次の法則が成り立つ

$$
\begin{align}
\boldsymbol{A} + \boldsymbol{0}_{m,n} &= \boldsymbol{0}_{m,n} + \boldsymbol{A} = \boldsymbol{A} \\
\boldsymbol{A}\boldsymbol{0}_{n,l} &= \boldsymbol{0}_{m,l} \\
\boldsymbol{0}_{l,m}\boldsymbol{A} &= \boldsymbol{0}_{l,n}
\end{align}
$$

### 転置
行列 $\boldsymbol{A}$ の **転置** とは、$\boldsymbol{A}$ の行と列を入れ替えたもので $\boldsymbol{A}^T$ と記述する

すなわち $m\times{n}$ 行列である $\boldsymbol{A}$ の転置行列は以下のように定義される

$$
\boldsymbol{A}^T = \left(
\begin{array}{cccc}
a_{11} & a_{21} & \ldots & a_{m1} \\
a_{12} & a_{22} & \ldots & a_{m2} \\
\vdots & \vdots & \ddots & \vdots \\
a_{1n} & a_{2n} & \ldots & a_{mn}
\end{array}
\right)
$$

行列の転置については、次の式が成り立つ

$$
\begin{align}
(\boldsymbol{A}^T)^T &= \boldsymbol{A} \\
(\boldsymbol{A}\boldsymbol{B})^T &= \boldsymbol{B}^T\boldsymbol{A}^T
\end{align}
$$

#### 演習1
行列 $\boldsymbol{A}, \boldsymbol{B}, \boldsymbol{C}, \boldsymbol{D}$ が次の通り与えられている時、

$$
\boldsymbol{A}=\begin{pmatrix}2&1\\-1&1\end{pmatrix},\
\boldsymbol{B}=\begin{pmatrix}1&-1\\1&1\end{pmatrix},\
\boldsymbol{C}=\begin{pmatrix}1&2\\2&1\\-1&2\end{pmatrix},\
\boldsymbol{D}=\begin{pmatrix}1&-1&0\\0&1&2\\0&0&1\end{pmatrix}
$$

以下を求めよ

1. $2\boldsymbol{A}$
2. $\boldsymbol{A}+\boldsymbol{B}$
3. $\boldsymbol{A}-\boldsymbol{B}$
4. $2\boldsymbol{A}-3\boldsymbol{B}$
5. $\boldsymbol{A}\boldsymbol{B}$
6. $\boldsymbol{B}\boldsymbol{C}^T$
7. $\boldsymbol{C}^T\boldsymbol{D}$

#### 演習2
行列 $\boldsymbol{X}$ が $1000\times{100}$ 行列、$\boldsymbol{Y}$ が $100\times{500}$ 行列、$\boldsymbol{Z}$ が $1000\times{500}$ 行列のとき、次の行列のサイズを求めよ

1. $\boldsymbol{X}\boldsymbol{Y}$
2. $(\boldsymbol{X}\boldsymbol{Y}\boldsymbol{Z}^T\boldsymbol{X})^T\boldsymbol{X}$

#### 演習3
行列 $\boldsymbol{A}, \boldsymbol{B}, \boldsymbol{C}$ について、次の式が成り立つことを証明せよ

$$
(\boldsymbol{A}\boldsymbol{B}^T\boldsymbol{C})^T = \boldsymbol{C}^T\boldsymbol{B}\boldsymbol{A}^T
$$

#### 解1

#### 解2

#### 解3

```julia
# 行列 A, B, C, D
A = [2 1; -1 1]; display(A)
B = [1 -1; 1 1]; display(B)
C = [1 2; 2 1; -1 2]; display(C)
D = [1 -1 0; 0 1 2; 0 0 1]; display(D)
```

    2×2 Matrix{Int64}:
      2  1
     -1  1

    2×2 Matrix{Int64}:
     1  -1
     1   1

    3×2 Matrix{Int64}:
      1  2
      2  1
     -1  2

    3×3 Matrix{Int64}:
     1  -1  0
     0   1  2
     0   0  1

```julia
# 2A => [4 2; -2 2]
display(2A)

# A + B => [3 0; 0 2]
display(A + B)

# A - B => [1 2; -2 0]
display(A - B)

# 2A - 3B => [1 5; -5 -1]
display(2A - 3B)

# AB => [3 -1; 0 2]
display(A * B)

# B * C^T => [1 -1; 1 1] * [1 2 -1; 2 1 2] = [-1 1 -3; 3 3 1]
display(B * C')

# C^T * D => [1 2 -1; 2 1 2] * [1 -1 0; 0 1 2; 0 0 1] = [1 1 3; 2 -1 4]
display(C' * D)
```

    2×2 Matrix{Int64}:
      4  2
     -2  2

    2×2 Matrix{Int64}:
     3  0
     0  2

    2×2 Matrix{Int64}:
      1  2
     -2  0

    2×2 Matrix{Int64}:
      1   5
     -5  -1

    2×2 Matrix{Int64}:
     3  -1
     0   2

    2×3 Matrix{Int64}:
     -1  1  -3
      3  3   1

    2×3 Matrix{Int64}:
     1   1  3
     2  -1  4

```julia
# X: 1000x100 行列
X = rand(1000, 100)

# Y: 100x500 行列
Y = rand(100, 500)

# Z: 1000x500 行列
Z = rand(1000, 500)

# XY => 1000x500 行列
println("XY: $(size(X*Y)) 行列")

# (X * Y * Z^T * X)^T * X
# => ((1000x500)(500x1000)(1000x100))^T (1000x100)
# => (100x1000)(1000x100)
# => 100x100 行列
println("(X * Y * Z^T * X)^T * X: $(size((X * Y * Z' * X)' * X)) 行列")
```

    XY: (1000, 500) 行列
    (X * Y * Z^T * X)^T * X: (100, 100) 行列

### 逆行列と連立方程式
- **正方行列**:
    - 行数と列数が等しい行列のこと
    - サイズ $n\times{n}$ の正方行列を特に $n$ 次正方行列と呼ぶ（$n$: 正方行列の次数）
- **対角行列**:
    - **対角成分**（行列の $(i,i)$ 成分）以外がすべて 0 である正方行列
    - 対角成分が左上から $d_1, \ldots, d_n$ であるような対角行列を ${\rm{diag}}(d_1, \ldots, d_n)$ と表記する

$$
{\rm{diag}}(d_1, \ldots, d_n) = \begin{pmatrix} d_1 & & & \\ & d_2 & & \\ & & \ddots & \\ & & & d_n \end{pmatrix}
$$

一般に行列の積では交換法則が成り立たないが、対角行列同士の積の場合は交換法則が成り立つ

$$
\boldsymbol{D}\boldsymbol{E} = \begin{pmatrix} d_1e_1 &&& \\ & d_2e_2 && \\ && \ddots & \\ &&& d_ne_n \end{pmatrix} = \boldsymbol{E}\boldsymbol{D}
$$

対角成分がすべて 1 である対角行列を特に **単位行列** と呼び、サイズ $n\times{n}$ の単位行列を $\boldsymbol{I}_n$ で表す

$m\times{n}$ 行列 $\boldsymbol{A}$ について、単位行列は次の性質を満たす

$$
\boldsymbol{A}\boldsymbol{I}_n = \boldsymbol{I}_m\boldsymbol{A} = \boldsymbol{A}
$$

正方行列 $\boldsymbol{A}$ に対して、もし $\boldsymbol{A}\boldsymbol{X} = \boldsymbol{I}$ となる $\boldsymbol{X}$ が存在する場合、それを **逆行列** と呼び、$\boldsymbol{A}^{-1}$ で表す

$\boldsymbol{A}\boldsymbol{X} = \boldsymbol{I}$ であれば $\boldsymbol{X}\boldsymbol{A} = \boldsymbol{I}$ も成り立つため、逆行列は以下の性質を満たす

$$
\boldsymbol{A}\boldsymbol{A}^{-1} = \boldsymbol{A}^{-1}\boldsymbol{A} = \boldsymbol{I}
$$

ただし、すべての正方行列に対して逆行列が存在するとは限らないため注意が必要である

逆行列が存在する正方行列を特に **正則行列** と呼び、存在しない正方行列を **非正則行列** と呼ぶ

ここで、行列と連立方程式の関係について、以下の連立方程式を例に考えてみる

$$
\left\{
\begin{align}
& 2x + y = 3 \\
& x - 3y = 5
\end{align}
\right.
$$

これを行列で置換すると、以下のように表すことが出来る

$$
\begin{align}
& \boldsymbol{A} = \begin{pmatrix} 2 & 1 \\ 1 & -3 \end{pmatrix},\
\boldsymbol{x} = \begin{pmatrix} x \\ y \end{pmatrix},\
\boldsymbol{b} = \begin{pmatrix} 3 \\ 5 \end{pmatrix} \\
& 連立方程式:\ \boldsymbol{A}\boldsymbol{x} = \boldsymbol{b}
\end{align}
$$

もし $\boldsymbol{A}$ が正則行列であるならば、この式の両辺の左から $\boldsymbol{A}^{-1}$ を掛けることで、未知ベクトル $\boldsymbol{x}$ を求めることが出来る

$$
\boldsymbol{A}^{-1}(\boldsymbol{A}\boldsymbol{x}) = (\boldsymbol{A}^{-1}\boldsymbol{A})\boldsymbol{x} = \boldsymbol{I}\boldsymbol{x} = \boldsymbol{x} \\
\\
\begin{align}
& \boldsymbol{A}\boldsymbol{x} = \boldsymbol{b} \ より、\qquad
\boldsymbol{x} = \boldsymbol{A}^{-1}\boldsymbol{b}
\end{align}
$$

$2\times{2}$ 行列 $\boldsymbol{X} = \begin{pmatrix} a & b \\ c & d \end{pmatrix}$ が逆行列を持つための条件は $ad - bc \neq 0$ で、その時の逆行列は以下の式で表される

$$
\boldsymbol{X}^{-1} = \frac{1}{ad - bc}\begin{pmatrix}d & -b \\ -c & a\end{pmatrix}
$$

したがって、この連立方程式は逆行列を計算して以下のように解くことが出来る

$$
\boldsymbol{A}^{-1} = \frac{1}{2\times(-3) - 1\times{1}}\begin{pmatrix}-3 & -1 \\ -1 & 2\end{pmatrix} = -\frac{1}{7}\begin{pmatrix}-3 & -1 \\ -1 & 2\end{pmatrix} \\
\begin{align}
\\
\boldsymbol{x} = \boldsymbol{A}^{-1}\boldsymbol{b} &= -\frac{1}{7}\begin{pmatrix}-3 & -1 \\ -1 & 2\end{pmatrix} \times \begin{pmatrix}3 \\ 5\end{pmatrix} \\
&= -\frac{1}{7}\begin{pmatrix}(-3)\times{3} + (-1)\times{5} \\ (-1)\times{3} + 2\times{5}\end{pmatrix} \\
&= \begin{pmatrix}2\\-1\end{pmatrix}
\end{align}
$$

以上より、解は $x=2,\ y=1$ となる

### 行列式
2次正方行列 $\begin{pmatrix}a&b \\ c&d\end{pmatrix}$ が逆行列を持たない条件は、$ad - bc = 0$ のときだった

$ad - bc = 0$ は $a:b = c:d$ のときに成り立つが、これは $\begin{pmatrix}a \\ b\end{pmatrix}$ と $\begin{pmatrix}c \\ d\end{pmatrix}$ が平行という条件に等しい

つまり2次正方行列は、1行目と2行目のベクトルが平行の時に逆行列を持たないということである

ここで $ad - bc$ のことを **行列式** と呼び、$\det{\boldsymbol{A}}$ と表記する

行列式は、次数2の場合に限らず一般の正方行列で定義可能だが、本稿では触れないものとする

連立方程式が解を持つかどうかは、行列式が 0 にならないかどうかで判断できる

行列式が 0 になってしまう場合は、連立方程式の係数の正方行列が逆行列を持たない、という条件に等しいため、その方程式は解くことができない

ただし、方程式を解くことができないケースはさらに「解を持たない場合」と「解が無限に存在する場合」に分けられる

解を持たない場合の方程式は、具体的には以下のようなものである

$$
\left\{
\begin{align}
& x + 2y = 3 \\
& 2x + 4y = 4
\end{align}
\right.
$$

$$
\begin{align}
&行列:\ \begin{pmatrix}1&2\\2&4\end{pmatrix}\begin{pmatrix}x \\ y\end{pmatrix} = \begin{pmatrix}3\\4\end{pmatrix} \\
&係数の行列式:\ 1\times{4} - 2\times{4} = 0 \quad \therefore この方程式は解けない
\end{align}
$$

$$
\begin{align}
&方程式の上式の2倍から下式を引く:\ 0x + 0y = 2 \\
&\therefore これを満たす x, y は存在しない
\end{align}
$$

また、解が無限に存在する場合の方程式は以下のようなものである

$$
\left\{
\begin{align}
& x + 2y = 2 \\
& 2x + 4y = 4
\end{align}
\right.
$$

$$
\begin{align}
&行列:\ \begin{pmatrix}1&2\\2&4\end{pmatrix}\begin{pmatrix}x \\ y\end{pmatrix} = \begin{pmatrix}2\\4\end{pmatrix} \\
&係数の行列式:\ 1\times{4} - 2\times{4} = 0 \quad \therefore この方程式は解けない
\end{align}
$$

$$
\begin{align}
&方程式の上式の2倍から下式を引く:\ 0x + 0y = 0 \rightarrow 上式と下式は一致する \\
&\therefore これを満たす x, y は無限に存在する
\end{align}
$$

### 逆行列と線形独立性
2次正方行列では、1行目と2行目に対応するベクトルが平行の時に逆行列を持たない、という法則があった

3次以上の場合まで一般化して考えると、逆行列を持たない場合とは「行列の行をベクトルと見たときにそれらが **線形従属** であるとき」と言える

- **線形従属**:
    - ベクトルの集合の少なくともどれか一つのベクトルを他のベクトルの線形結合で表すことが可能な状態
        - 言い換えると、各ベクトルをスカラー倍して和をとったときに、$\boldsymbol{0}$ になる組み合わせが存在する状態
            - ただし、すべてのスカラーが 0 の場合を除く（すべてのベクトルを 0 倍すれば必ず $\boldsymbol{0}$ になるため）
    - 具体例: $\begin{pmatrix}1&1&2\\2&-1&1\\4&1&5\end{pmatrix}$
        - 1行目: $\boldsymbol{c_1} = (1, 1, 2)$
        - 2行目: $\boldsymbol{c_2} = (2, -1, 1)$
        - 3行目: $\boldsymbol{c_3} = (4, 1, 5)$
        - 線形従属: $k_1\boldsymbol{c_1} + k_2\boldsymbol{c_2} + k_3\boldsymbol{c_3} = 0$ になる $k_1, k_2, k_3$ の組み合わせが存在する（ただし $k_1, k_2, k_3$ の少なくとも1つは 0 でないこと）
            - 今回の場合 $2\boldsymbol{c_1} + \boldsymbol{c_2} + (-1)\boldsymbol{c_3} = 0$ が成り立つため線形従属であると言える
- **線形独立**:
    - ベクトルの集合が線形従属でない状態
        - 言い換えると、各ベクトルをスカラー倍して和をとったときに、$\boldsymbol{0}$ になる組み合わせが存在しない状態
            - ただし、すべてのスカラーが 0 の場合を除く（すべてのベクトルを 0 倍すれば必ず $\boldsymbol{0}$ になるため）

2次正方行列の逆行列は、公式から簡単に求めることが可能だったが、ここで一般の正方行列の逆行列の求め方を考えてみる

準備として、行列の **行基本操作** を定義する

- **行基本操作**:
    1. ある行に定数を掛ける
    2. ある行に定数を掛けたものを他のある行に足す
    3. ある行とある行を交換する

続いて、次の具体例をもとに逆行列を求める

$$
\boldsymbol{A} = \begin{pmatrix}3&1&1\\1&2&1\\0&-1&1\end{pmatrix}
$$

この行列の右側に同じサイズの単位行列を連結し、次のような行列を考える

$$
\begin{pmatrix}\boldsymbol{A} & \boldsymbol{I}\end{pmatrix} = \left(
\begin{array}{ccc|ccc}
3&1&1 & 1&& \\
1&2&1 & &1& \\
0&-1&1 & &&1
\end{array}
\right)
$$

この行列に対して行基本操作を繰り返し、左半分が単位行列になったときに右半分に現れるのが逆行列である

$$
\begin{align}
\left(
\begin{array}{ccc|ccc}
3&1&1 & 1&& \\
1&2&1 & &1& \\
0&-1&1 & &&1
\end{array}
\right) &\rightarrow
\left(
\begin{array}{ccc|ccc}
0&-5&-2 & 1&-3&0 \\
1&2&1 & 0&1&0 \\
0&-1&1 & 0&0&1
\end{array}
\right) \quad（①2行目の-3倍を1行目に加える）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&2&1 & 0&1&0 \\
0&-5&-2 & 1&-3&0 \\
0&-1&1 & 0&0&1
\end{array}
\right) \quad（②1行目と2行目を交換）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&0&3 & 0&1&2 \\
0&0&-7 & 1&-3&-5 \\
0&-1&1 & 0&0&1
\end{array}
\right) \quad（③1行目に3行目の2倍を加え、2行目に3行目の-5倍を加える）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&0&3 & 0&1&2 \\
0&-1&1 & 0&0&1 \\
0&0&-7 & 1&-3&-5
\end{array}
\right) \quad（④2行目と3行目を交換）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&0&3 & 0&1&2 \\
0&1&-1 & 0&0&-1 \\
0&0&-7 & 1&-3&-5
\end{array}
\right) \quad（⑤2行目を-1倍）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&0&0 & \frac{3}{7}&-\frac{2}{7}&-\frac{1}{7} \\
0&1&0 & -\frac{1}{7}&\frac{3}{7}&-\frac{2}{7} \\
0&0&-7 & 1&-3&-5
\end{array}
\right) \quad（⑥1行目に3行目の\frac{3}{7}倍を加え、2行目に3行目の-\frac{1}{7}を加える）\\
&\rightarrow
\left(
\begin{array}{ccc|ccc}
1&0&0 & \frac{3}{7}&-\frac{2}{7}&-\frac{1}{7} \\
0&1&0 & -\frac{1}{7}&\frac{3}{7}&-\frac{2}{7} \\
0&0&1 & -\frac{1}{7}&\frac{3}{7}&\frac{5}{7}
\end{array}
\right) \quad（⑦3行目を-\frac{1}{7}倍）\\
\end{align}
$$

以上より、上記行列の逆行列は $\begin{pmatrix}\frac{3}{7}&-\frac{2}{7}&-\frac{1}{7} \\ \frac{1}{7}&\frac{3}{7}&-\frac{2}{7} \\ -\frac{1}{7}&\frac{3}{7}&\frac{5}{7}\end{pmatrix}$ となる

実際、元の行列と上記逆行列を掛けてみると単位行列になることが確認できる

```julia
# 行列×逆行列（有理数で計算）
[3 1 1; 1 2 1; 0 -1 1] * [3//7 -2//7 -1//7; -1//7 3//7 -2//7; -1//7 3//7 5//7]
```

    3×3 Matrix{Rational{Int64}}:
     1//1  0//1  0//1
     0//1  1//1  0//1
     0//1  0//1  1//1

```julia
# 行列×逆行列（有理数で計算したものを整数に戻す）
Int.([3 1 1; 1 2 1; 0 -1 1] * [3//7 -2//7 -1//7; -1//7 3//7 -2//7; -1//7 3//7 5//7])
```

    3×3 Matrix{Int64}:
     1  0  0
     0  1  0
     0  0  1

上記のような操作を以下の非正則行列 $\boldsymbol{B}$ について行ってみる

$$
\begin{align}
\boldsymbol{B} &= \begin{pmatrix}1&1&&2\\1&-1&1\\3&-1&4\end{pmatrix} \\
\\
\begin{pmatrix}\boldsymbol{B} & \boldsymbol{I}\end{pmatrix} &= \begin{pmatrix}1&1&2&1&&\\1&-1&1&&1&\\3&-1&4&&&1\end{pmatrix}\\
&\rightarrow \begin{pmatrix}1&1&2&1&&\\0&-2&-1&-1&1&\\0&-4&-2&-3&&1\end{pmatrix} \quad (①2行目 += -1\times{1行目},\ 3行目 += -3\times{1行目}) \\
&\rightarrow \begin{pmatrix}1&1&2&1&&\\0&1&\frac{1}{2}&\frac{1}{2}&-\frac{1}{2}&\\0&-4&-2&-3&&1\end{pmatrix} \quad (②2行目 = -\frac{1}{2}\times{2行目}) \\
&\rightarrow \begin{pmatrix}1&0&\frac{5}{2}&\frac{1}{2}&\frac{1}{2}&\\0&1&\frac{1}{2}&\frac{1}{2}&-\frac{1}{2}&\\0&0&0&-1&-2&1\end{pmatrix} \quad (③1行目 += -1\times{2行目},\ 3行目 += 4\times{2行目})
\end{align}
$$

上記の通り、3行目の左半分が $\boldsymbol{0}$ となってしまい、これ以上計算できない

このことは元の行列 $\boldsymbol{B}$ が逆行列を持たないことに対応している

ここで、行基本操作の結果の左半分のみに注目すると、上の2行が $\boldsymbol{0}^T$ に一致せず、3行目が $\boldsymbol{0}^T$ に一致している

このように行基本操作を行った結果、$\boldsymbol{0}^T$ に**ならない**行の数を、その行列の **ランク** と呼ぶ

上記行列 $\boldsymbol{B}$ の場合、ランクは 2 であり、$\mathrm{rank}\boldsymbol{B}=2$ と表す

また、正則行列 $\boldsymbol{A}$ の場合は $\mathrm{rank}\boldsymbol{A}=3$ である

一般に **$n$次正方行列がランク$n$であること** と、その行列が正則であることは必要十分の関係となる

ランクの概念は正方行列に限らず、任意の $m\times{n}$ 行列に対しても存在し、ランクは最大で $\min(m, n)$（$m$ と $n$ のうち小さい方の次元数）を超えないことが知られている

特に $\mathrm{rank}\boldsymbol{A} = \min(m, n)$ である時、行列 $\boldsymbol{A}$ は **フルランク** であると呼ばれる

つまり、行列が正則である条件は、その行列がフルランクであることである

#### ランクの求め方
逆行列を求める必要がなく、行列のランクのみを求めたい場合（行列が正則かどうかを調べたい場合）、以下の簡易的な手順で求めることが出来る

1. $m\times{n}$ 行列 $\boldsymbol{A}$ のランクを求める
2. 任意変数 $i$ を $1～m$ まで増やすながら以下の計算を繰り返す
    - $i$ 行目以降で 0 でない成分が出現する行を選択する（もしすべて 0 であれば終了する）
    - 選択した行の最も左の 0 でない列を $k$ 列目として、$i$ 行目以降で選択した行**以外**の $k$ 列目がすべて 0 となるように行基本変形を行う
3. 終了時 $\boldsymbol{0}^T$ に一致しない行の数が $\boldsymbol{A}$ のランクとなる

上記手順で前述の非正則行列 $\boldsymbol{B}$ のランクを求めてみると、

$$
\begin{align}
\boldsymbol{B} &= \begin{pmatrix}1&1&2\\1&-1&1\\3&-1&4\end{pmatrix} \\
&\rightarrow \begin{pmatrix}1&1&2\\0&-2&-1\\0&-4&-2\end{pmatrix} \quad (①2行目+=-1\times{1行目},\ 3行目+=-3\times{1行目}) \\
&\rightarrow \begin{pmatrix}1&1&2\\0&-2&-1\\0&0&0\end{pmatrix} \quad (②3行目+=3\times{2行目})
\end{align}
$$

より、$\mathrm{rank}\boldsymbol{B} = 2$ であることが分かる

### 前進消去と後退代入
前述のように、行基本操作（行基本変形）を用いてある行列を単位行列に変形することで、その行列の逆行列を求めることが出来る

逆行列を求めることができれば、自動的に連立1次方程式を解くことも可能であり、このようなアルゴリズムを **ガウスの消去法** もしくは **掃き出し法** と呼ぶ

ガウスの消去法は **前進消去** と **後退代入** という2つのステップから構成されている

- **前進消去**:
    - **拡大係数行列** に対して行基本操作（行基本変形）を行い、係数行列の対角成分を 1 に、下三角を 0 に変形し、**階段行列** を作ること
    -
- **後退代入**:
    - 連立1次方程式の求解部分のこと
    - 前進消去により求められた階段行列において、最下段の自明な解からドミノ倒し的に解を求めていく
    -

ガウスの消去法は機械的に連立1次方程式を解くことが出来るため、コンピュータシミュレーションの分野では基本の手法となっている

#### 前進消去の実装
前進消去は以下のアルゴリズムで処理することが出来る

1. 1行目1列目（$a_11$）を **ピボット** と呼ばれる成分として選択する
2. ピボット成分のある行をピボット値で割り、ピボット値が 1 になるように変形する
3. 2行目以降について、ピボット成分のある列（$a_11$ がピボットの場合は 1 列目）が 0 になるように、ピボット成分のある行（$a_11$ がピボットの場合は 1 行目）を定数倍して加算する
4. ピボット成分を +1 行, +1 列ずらして新たなピボットとして選択する
5. 新しいピボット成分に対して 2～4 の操作を繰り返し、最後の行まで処理する

#### 後退代入の実装
階段行列の最下段は係数行列の左側が全て0、右端が1になっている

その更に右隣は定数項であるため、既に解が得られている段となる（自明の解）

最下段の解がわかれば、その1つ上の段の未知数も計算可能になるため、以下の図のようにドミノ倒し的に解を求めることができる

```julia
"""
ガウスの消去法

@params
- A::Matrix{T} 係数行列
- b::Vector{T} 定数ベクトル

@keys
- debug::Boolean デバッグ出力するかどうか

@returns
- x::Vector{T} 解ベクトル
"""
gauss_eliminate = (A, b; debug = false) -> begin
    # 拡大係数行列: 係数行列と定数ベクトルを横に結合
    mat = Rational.(hcat(A, b)) # 有理数計算できるように Matrix{Complex} 型とする
    n = size(mat)[1] # 拡大係数行列の行数

    # 前進消去
    for i = 1:n # 1行目からn行目までループ処理
        pivot = mat[i, i] # a_ii: ピボットとして選択
        mat[i, :] = mat[i, :] // pivot # i行目: ピボット値で割る（ピボット値を 1 にする）

        # 1行目の定数倍を i+1 行目から n 行目まで加算
        for j = i+1 : n
            mat[j, :] = mat[j, :] - mat[j, i] .* mat[i, :] # i列目の値が 0 になるように i行目を定数倍して加算（減産）
        end
    end

    # 前進消去後の拡大係数行列を確認
    if debug
        display(mat)
    end

    # 後退代入
    x = zeros(Float64, n) # n次元 解ベクトル
    A, b = mat[:, 1:end-1], mat[:, end] # 拡大係数行列を係数行列と定数ベクトルに再分離
    for i = n:-1:1 # 最終行から解を求めていく
        x[i] = b[i] # 自明の解
        for j = 1:i
            b[j] = b[j] - A[j, i] * x[i] # 解が決まった列分、定数ベクトルの値を更新
        end
    end

    return x
end
```

    gauss_eliminate

```julia
# 例で挙げていた連立1次方程式を実際に解いてみる
A = [2 1 3 4; 3 2 5 2; 3 4 1 -1; -1 -3 1 3]
b = [2; 12; 4; -1]

gauss_eliminate(A, b; debug=true)
```

    4×5 Matrix{Rational{Int64}}:
     1//1  1//2  3//2    2//1   1//1
     0//1  1//1  1//1   -8//1  18//1
     0//1  0//1  1//1  -13//6  22//3
     0//1  0//1  0//1    1//1  -2//1

    4-element Vector{Float64}:
      1.0
     -1.0
      3.0
     -2.0

```julia

```