# Set up your first equations#

This is a jupyter notebook serving as a quick/first interactive tutorial for the usage of *phyem*.

To let Python know where to find the *phyem* package, we need to add the dir that contains the package to the searching path of Python unless it is already in a system path.

```
[1]:
```

```
import sys
ph_dir = '../../../../../' # the path to dir that containing the phyem package.
sys.path.append(ph_dir)
import phyem as ph # import the phyem package
ph.config._set_matplot_block(False)
```

We now set the dimensions of the embedding space to be 3.

```
[2]:
```

```
ph.config.set_embedding_space_dim(3)
```

Initialize an abstract manifold by

```
[3]:
```

```
manifold = ph.manifold(3)
```

Then an abstract mesh is built upon manifold `manifold`

.

```
[4]:
```

```
mesh = ph.mesh(manifold)
```

Using function `list_meshes`

of *phyem* can list all existing meshes in the current console.

```
[5]:
```

```
ph.list_meshes()
```

```
Existing meshes:
---------------- symbolic - <manifold> -------------------------
\mathfrak{M} | <Manifold \mathcal{M} at 0x0000015D6154C1A0>
```

where we can see the symbolic representation of the mesh and the manifold on which it is built. If we render the symbolic representation of the mesh with an equation environment, we see \(\mathfrak{M}\).

We then can set spaces upon this mesh. For example, we set up spaces \(\Lambda^{(0)}(\mathcal{M})\), \(\Lambda^{(1)}(\mathcal{M})\), \(\Lambda^{(2)}(\mathcal{M})\), \(\Lambda^{(3)}(\mathcal{M})\), i.e., the Hilbert spaces of scalar valued 0- to 3-forms, by

```
[6]:
```

```
ph.space.set_mesh(mesh)
O0 = ph.space.new('Lambda', 0) # Lambda is the indicator for (standard) scalar valued form spaces.
O1 = ph.space.new('Lambda', 1)
O2 = ph.space.new('Lambda', 2)
O3 = ph.space.new('Lambda', 3)
ph.list_spaces() # we can also list all existing spaces
```

```
Implemented spaces:
abbreviation - description
Lambda | scalar valued k-form space
bundle | bundle valued k-form space
bundle-diagonal | diagonal bundle valued k-form space
Existing spaces:
On mesh \mathfrak{M}
0: \widetilde\Lambda^{(0)}(\mathcal{M})
1: \widetilde\Lambda^{(1)}(\mathcal{M})
2: \widetilde\Lambda^{(2)}(\mathcal{M})
3: \widetilde\Lambda^{(3)}(\mathcal{M})
```

where we see first a list of all implemented spaces and then the existing spaces till this moment.

A form is just a instance of such space. So we make forms from spaces by calling method `make_form`

which takes two arguments representing the symbolic representation and the linguistic representation of the form. These forms are the root forms.

```
[7]:
```

```
w = O1.make_form(r'\omega^1', "vorticity1")
u = O2.make_form(r'u^2', r"velocity2")
f = O2.make_form(r'f^2', r"body-force")
P = O3.make_form(r'P^3', r"total-pressure3")
ph.list_forms() # this will generate a table in a separete figure showing.
```

```
[7]:
```

```
<Figure size 1000x500 with 1 Axes>
```

where we have used function `list_forms`

to visualize/list the exsiting forms.

When it is the first time to invoke

matplotlibandlatex, it may be very slow since there are large amount of interplays among the packages. Be patient. Things become much better later on.

We can also visualize a particular form by calling its `pr`

method. For example,

```
[8]:
```

```
u.pr()
```

```
[8]:
```

```
<Figure size 1200x600 with 1 Axes>
```

Furthermore, we can use these root forms to build other forms through operators like \(\wedge\), \(\star\), \(\mathrm{d}^\ast\), \(\mathrm{d}\), \(\partial_t\) and so on.

```
[9]:
```

```
dsP = ph.codifferential(P)
dsu = ph.codifferential(u)
du = ph.d(u)
du_dt = ph.time_derivative(u)
# ph.list_forms(locals())
```

Now, if you try `ph.list_forms()`

which does not restrict the range of `list_forms`

function to the local environment, the outputs are different.

```
[10]:
```

```
ph.list_forms()
```

```
[10]:
```

```
<Figure size 1000x500 with 1 Axes>
```

Basically, we see the `id`

and then *symbolic representation* = *linguistic representation* of all forms.

With forms we can construct equations (usually **partial differential equations, PDEs**) through function `ph.pde`

.

```
[11]:
```

```
exp1 = [
'dudt - dsP = f',
'w = dsu',
'du = 0',
]
itp = {
'dudt': du_dt,
'dsP': dsP,
'f': f,
'w': w,
'dsu': dsu,
'du': du,
}
pde1 = ph.pde(exp1, itp)
pde1.unknowns = [u, w, P]
```

where we send an expression (`exp1`

) and an interpreter (`itp`

) to `ph.pde`

to initialize an equation object named `pde1`

. You can see that in `exp1`

we use string to represent the variables, terms and operators. The interpreter, `itp`

, inteprets the string representations and thus `ph.pde`

knows to use correct ingredients.

You can avoid defining the interpreter manually by use the built-in function `locals`

. For example,

```
[12]:
```

```
exp2 = [
'du_dt - dsP = f',
'w = dsu',
'du = 0',
]
pde2 = ph.pde(exp2, locals())
pde2.unknowns = [u, w, P]
```

In this way, you lose the freedom of naming the terms in the expression because `locals()`

gives a dictionary whose keys are exactly the vraible names in the local environment. See `'dudt'`

in `exp1`

and `du_dt`

in `exp2`

.

After constructing our equations, we may want to have a close look at it to check if any mistakes we made. We can call method `pr`

, standing for *print representation*, to do that. For example,

```
[13]:
```

```
pde2.pr(indexing=False)
```

```
[13]:
```

```
<Figure size 800x600 with 1 Axes>
```