# Creating Your First Material Simulation

This tutorial guides you through creating a basic heat equation simulation using
[MaterForge](https://i10git.cs.fau.de/rahil.doshi/materforge) and
[pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/).
It builds upon the existing [waLBerla tutorial for code generation](https://walberla.net/doxygen/tutorial_codegen01.html),
adding dependency-driven material property handling with MaterForge.

## Prerequisites

Before starting, ensure you have:
- Completed the [Getting Started](getting_started.md) tutorial
- Installed pystencils and [pystencilssfg](https://pycodegen.pages.i10git.cs.fau.de/pystencils-sfg/)
- Basic understanding of heat transfer and the heat equation
- Created the `simple_steel.yaml` file from the [Getting Started](getting_started.md) tutorial

---

## Overview

We will create a 2D heat equation simulation where material properties are driven by the
field variable `u` (representing temperature). MaterForge expresses all dependency-driven
properties as SymPy expressions in a plain `sp.Symbol`. These are then substituted with
the pystencils field accessor at the point of use in the assignment collection.

---

## Step 1: Set Up the Simulation Framework

```python
import sympy as sp
import pystencils as ps
from pystencilssfg import SourceFileGenerator
from materforge.parsing.api import create_material
from materforge.algorithms.piecewise_inverter import PiecewiseInverter

with SourceFileGenerator() as sfg:
    data_type = "float64"

    # Define the field variable and output field
    u, u_tmp = ps.fields(f"u, u_tmp: {data_type}[2D]", layout='fzyx')
    thermal_diffusivity_field = ps.fields(
        f"thermal_diffusivity_field: {data_type}[2D]", layout='fzyx'
    )
```

---

## Step 2: Create the Material

Always create the material with a plain `sp.Symbol`. pystencils field accessors such as
`u.center()` are not valid `sp.Symbol` instances and will raise `TypeError`. Substitute
`u.center()` later, at the point of use in the assignment collection.


```python
    # Create material with a plain sp.Symbol
    T = sp.Symbol('T')
    material = create_material("simple_steel.yaml", dependency=T)

    # material.thermal_diffusivity is a SymPy Piecewise expression in T.
    # T is substituted with u.center() when building the assignment below.
```

---

## Step 3: Set Up the Heat Equation

```python
    dx = sp.Symbol("dx")
    dt = sp.Symbol("dt")
    thermal_diffusivity_sym = sp.Symbol("thermal_diffusivity")

    heat_pde = (
        ps.fd.transient(u)
        - thermal_diffusivity_sym * (ps.fd.diff(u, 0, 0) + ps.fd.diff(u, 1, 1))
    )

    discretize = ps.fd.Discretization2ndOrder(dx=dx, dt=dt)
    heat_pde_discretized = discretize(heat_pde)
    heat_pde_discretized = (
        heat_pde_discretized.args[1] + heat_pde_discretized.args[0].simplify()
    )
```

---

## Step 4: Build the Assignment Collection and Generate Code

Substitute `T` with `u.center()` when assigning the property expression. This is the
single, explicit coupling point between MaterForge and pystencils.


```python
    from sfg_walberla import Sweep

    subexp = [
        ps.Assignment(thermal_diffusivity_sym,
            material.thermal_diffusivity.subs(T, u.center()),),
    ]

    ac = ps.AssignmentCollection(
        subexpressions=subexp,
        main_assignments=[
            ps.Assignment(u_tmp.center(), heat_pde_discretized),
            ps.Assignment(thermal_diffusivity_field.center(), thermal_diffusivity_sym),
        ],
    )

    sweep = Sweep("HeatEquationKernelWithMaterial", ac)
    sfg.generate(sweep)
```

---

## Step 5: Property Inversion (Optional)

If your simulation requires recovering the dependency value from a property value
(e.g. recovering temperature from energy density), use `PiecewiseInverter`.
This works for any piecewise-linear property - not just energy density.

```python
import sympy as sp
from materforge.parsing.api import create_material
from materforge.algorithms.piecewise_inverter import PiecewiseInverter

T = sp.Symbol('T')
material = create_material("simple_steel.yaml", dependency=T)

if hasattr(material, 'energy_density'):
    E = sp.Symbol('E')
    # input_symbol and output_symbol must be sp.Symbol instances, not strings
    inverse = PiecewiseInverter.create_inverse(material.energy_density, T, E)

    # Evaluate: given energy density, recover temperature
    energy_value = 1.5e9   # J/m^3
    recovered_T = float(inverse.subs(E, energy_value))
    print(f"Recovered temperature: {recovered_T:.2f}")
```

Note: `PiecewiseInverter` requires the property to use `degree: 1` regression.
Higher-degree segments cannot be inverted.

---

## Complete Example

```python
import sympy as sp
import pystencils as ps
from pystencilssfg import SourceFileGenerator
from sfg_walberla import Sweep

from materforge.parsing.api import create_material
from materforge.algorithms.piecewise_inverter import PiecewiseInverter

with SourceFileGenerator() as sfg:
    data_type = "float64"

    u, u_tmp = ps.fields(f"u, u_tmp: {data_type}[2D]", layout='fzyx')
    thermal_diffusivity_field = ps.fields(
        f"thermal_diffusivity_field: {data_type}[2D]", layout='fzyx'
    )
    thermal_diffusivity_sym = sp.Symbol("thermal_diffusivity")
    dx, dt = sp.Symbol("dx"), sp.Symbol("dt")

    # Discretise heat equation
    heat_pde = (
        ps.fd.transient(u)
        - thermal_diffusivity_sym * (ps.fd.diff(u, 0, 0) + ps.fd.diff(u, 1, 1))
    )
    discretize = ps.fd.Discretization2ndOrder(dx=dx, dt=dt)
    heat_pde_discretized = discretize(heat_pde)
    heat_pde_discretized = (
        heat_pde_discretized.args[1] + heat_pde_discretized.args[0].simplify()
    )

    # Load material with a plain sp.Symbol - never pass u.center() to create_material()
    T = sp.Symbol('T')
    material = create_material("simple_steel.yaml", dependency=T)

    # Optional: build inverse for energy-to-temperature recovery
    if hasattr(material, 'energy_density'):
        E = sp.Symbol('E')
        inverse = PiecewiseInverter.create_inverse(material.energy_density, T, E)

    # Substitute T -> u.center() at the assignment level
    subexp = [
        ps.Assignment(thermal_diffusivity_sym,
            material.thermal_diffusivity.subs(T, u.center()),),
    ]

    ac = ps.AssignmentCollection(
        subexpressions=subexp,
        main_assignments=[
            ps.Assignment(u_tmp.center(), heat_pde_discretized),
            ps.Assignment(thermal_diffusivity_field.center(), thermal_diffusivity_sym),
        ],
    )

    sweep = Sweep("HeatEquationKernelWithMaterial", ac)
    sfg.generate(sweep)
```

---

## Next Steps

- Learn about [property inversion](../how-to/property_inversion.md)
- Explore more complex [material properties](../explanation/material_properties.md)
- Read the [API reference](../reference/api/material.md) for advanced usage
- See the original [waLBerla tutorial](https://walberla.net/doxygen/tutorial_codegen01.html)
  for details on compiling and running the generated code
