diff --git a/apps/tutorials/codegen/01_CodegenHeatEquation.cpp b/apps/tutorials/codegen/01_CodegenHeatEquation.cpp
index 361ebf76434fa47388b6fe896f7f9458c3b72929..8f425ca008f8b4609977b50573943ef5d2c53d56 100644
--- a/apps/tutorials/codegen/01_CodegenHeatEquation.cpp
+++ b/apps/tutorials/codegen/01_CodegenHeatEquation.cpp
@@ -67,7 +67,7 @@ void initDirichletBoundaryNorth(shared_ptr< StructuredBlockForest > blocks, Bloc
          {
             const Vector3< real_t > p = blocks->getBlockLocalCellCenter(*block, *cell);
             //  Set the dirichlet boundary to f(x) = 1 + sin(x) * x^2
-            real_t v          = real_c(1.0 + std::sin(2 * math::pi * p[0]) * p[0] * p[0]);
+            real_t v          = real_c(2000.0 + 1500 * std::sin(2 * math::pi * p[0]) * p[0] * p[0]);
             u->get(*cell)     = v;
             u_tmp->get(*cell) = v;
          }
@@ -103,7 +103,7 @@ int main(int argc, char** argv)
 
    WALBERLA_CHECK_FLOAT_EQUAL(dx, dy);
 
-   const real_t dt    = real_c(1e-4);
+   const real_t dt    = real_c(1);
    const real_t kappa = real_c(1.0);
 
    ///////////////////////////
@@ -120,8 +120,9 @@ int main(int argc, char** argv)
    /// FIELDS ///
    //////////////
 
-   BlockDataID uFieldId    = field::addToStorage< ScalarField >(blocks, "u", real_c(0.0), field::fzyx, uint_c(1));
-   BlockDataID uTmpFieldId = field::addToStorage< ScalarField >(blocks, "u_tmp", real_c(0.0), field::fzyx, uint_c(1));
+   BlockDataID uFieldId    = field::addToStorage< ScalarField >(blocks, "u", real_c(2000.0), field::fzyx, uint_c(1));
+   BlockDataID uTmpFieldId = field::addToStorage< ScalarField >(blocks, "u_tmp", real_c(2000.0), field::fzyx, uint_c(1));
+   BlockDataID kappa_outFieldId    = field::addToStorage< ScalarField >(blocks, "kappa_out", real_c(0.0), field::fzyx, uint_c(1));
 
    /////////////////////
    /// COMMUNICATION ///
@@ -153,13 +154,16 @@ int main(int argc, char** argv)
    SweepTimeloop timeloop(blocks, uint_c(2e4));
 
    timeloop.add() << BeforeFunction(commScheme, "Communication") << BeforeFunction(neumann, "Neumann Boundaries")
-                  << Sweep(pystencils::HeatEquationKernel(uFieldId, uTmpFieldId, dt, dx, kappa), "HeatEquationKernel")
+                  << Sweep(pystencils::HeatEquationKernel(kappa_outFieldId, uFieldId, uTmpFieldId, dt, dx, kappa), "HeatEquationKernel")
                   << AfterFunction([blocks, uFieldId, uTmpFieldId]() { swapFields(*blocks, uFieldId, uTmpFieldId); },
                                    "Swap");
 
    auto vtkWriter = field::createVTKOutput< ScalarField, float >( uFieldId, *blocks, "temperature", uint_c(200), uint_c(0) );
    vtkWriter();
    timeloop.addFuncAfterTimeStep(vtkWriter, "VTK");
+   auto vtkWriter1 = field::createVTKOutput< ScalarField, float >( kappa_outFieldId, *blocks, "kappa", uint_c(200), uint_c(0) );
+   vtkWriter1();
+   timeloop.addFuncAfterTimeStep(vtkWriter1, "VTK1");
 
    timeloop.run();
 
diff --git a/apps/tutorials/codegen/HeatEquationKernel.py b/apps/tutorials/codegen/HeatEquationKernel.py
index 024940a38dc1f75e748db4c1987800a9a85cb178..9c418fc977b34f24344619c04e40d565a2210980 100644
--- a/apps/tutorials/codegen/HeatEquationKernel.py
+++ b/apps/tutorials/codegen/HeatEquationKernel.py
@@ -1,27 +1,278 @@
+import sys
+sys.path.append('/local/ca00xebo/repos/walberla')
+import numpy as np
 import sympy as sp
 import pystencils as ps
 from pystencils_walberla import CodeGeneration, generate_sweep
+from dataclasses import dataclass
+from pystencils.typing import PointerType
+import src.materials.alloys.Ti6Al4V as ti
+from src.materials.get_parameters import get_parameters
+from src.materials.alloys.Ti6Al4V import Ti6Al4V_parameters
 
+'''
+def interpolate(x, xmin, xmax, ymin, ymax):
+    """
+    Linear interpolation between two points: y = y_1 + ((y2 - y1) / (x2 - x1)) * (x - x1)
+    """
+    return (ymin * (xmax - x) + ymax * (x - xmin)) / (xmax - xmin)
+
+
+def interpolate_property(T, base_temperature, base_values):  # TODO
+    """
+    Interpolates properties based on temperature for both symbolic and numeric cases.
+
+    :param T: Temperature value, either symbolic or numeric.
+    :param base_temperature: List or array of base temperatures.
+    :param base_values: List or array of property values corresponding to base temperatures.
+    """
+    # Ensure base_temperature and base_values are lists or arrays
+    if not isinstance(base_temperature, (list, np.ndarray)) or not isinstance(base_values, (list, np.ndarray)):
+        raise TypeError("base_temperature and base_values must be of type list or np.ndarray")
+    # Convert to lists if they are numpy arrays
+    if isinstance(base_temperature, np.ndarray):
+        base_temperature = base_temperature.tolist()
+    if isinstance(base_values, np.ndarray):
+        base_values = base_values.tolist()
+    # Check if base_temperature and base_values have the same length
+    if len(base_temperature) != len(base_values):
+        raise ValueError("base_temperature and base_values must have the same length")
+    # assert len(base_temperature) == len(base_values), "base_temperature and base_values must have the same length"
+
+    if isinstance(T, sp.Expr):  # Symbolic expression
+        base_temperature = [sp.Float(t) for t in base_temperature]
+        base_values = [sp.Float(val) for val in base_values]
+        conditions = [
+            (base_values[0], T < base_temperature[0]),
+            (base_values[-1], T >= base_temperature[-1])]
+        for i in range(len(base_temperature) - 1):
+            interp_expr = (base_values[i] +
+                           (base_values[i+1] - base_values[i]) / (base_temperature[i+1] - base_temperature[i]) *
+                           (T - base_temperature[i]))
+            conditions.append((interp_expr, sp.And(T >= base_temperature[i], T < base_temperature[i + 1])))
+        return sp.Piecewise(*conditions)
+    else:  # Numeric expression
+        return np.interp(T, base_temperature, base_values)
+
+
+def density_by_thermal_expansion(T, base_temperature, base_density, thermal_expansion_coefficient):
+    """
+    Calculate density based on thermal expansion: rho_0 / (1 + tec * (T - T_0))
+
+    :param T: Temperature value.
+    :param base_temperature: Reference temperature.
+    :param base_density: Density at reference temperature.
+    :param thermal_expansion_coefficient: Thermal expansion coefficient.
+    """
+    return base_density / (1 + thermal_expansion_coefficient * (T - base_temperature)) ** 3
+
+
+def thermal_diffusivity_func(heat_conductivity, density, specific_heat_capacity):
+    """
+    Calculate thermal diffusivity: k(T) / (rho(T) * c_p(T))
+
+    :param heat_conductivity: Thermal conductivity.
+    :param density: Density.
+    :param specific_heat_capacity: Specific heat capacity.
+    """
+    return heat_conductivity / (density * specific_heat_capacity)
+
+
+def density_lookup(T, temperature_array, density_array):
+    """
+    Look up density based on temperature using interpolation.
+
+    :param T: Temperature value.
+    :param temperature_array: Array of temperatures.
+    :param density_array: Array of densities.
+    """
+    if isinstance(T, sp.Expr):  # Symbolic expression
+        conditions = [
+            (density_array[0], T < temperature_array[0]),
+            (density_array[-1], T >= temperature_array[-1])
+        ]
+        for i in range(len(temperature_array) - 1):
+            interp_expr = (density_array[i] +
+                           (density_array[i+1] - density_array[i]) / (temperature_array[i+1] - temperature_array[i]) *
+                           (T - temperature_array[i]))
+            conditions.append((interp_expr, sp.And(T >= temperature_array[i], T < temperature_array[i + 1])))
+        return sp.Piecewise(*conditions)
+    else:  # Numeric expression
+        return np.interp(T, temperature_array, density_array)
+
+
+def get_arguments(T, temperatures, args):
+    """
+    Recursively get arguments for parameter functions.
+
+    :param T: Temperature value.
+    :param temperatures: Solidus and liquidus temperatures.
+    :param args: Arguments to be processed.
+    """
+    converted_args = []
+    for arg in args:
+        if isinstance(arg, (Solidus_Liquidus_Parameter, Functional_Parameter)):
+            converted_args.append(get_parameters(T, temperatures, arg))
+        else:
+            converted_args.append(arg)
+    return converted_args
+
+
+def get_parameters(T, temperatures, parameter):
+    """
+    Resolve parameters based on temperature and parameter type.
+
+    :param T: Temperature value.
+    :param temperatures: Solidus and liquidus temperatures.
+    :param parameter: Parameter to be resolved.
+    """
+    if isinstance(parameter, Solidus_Liquidus_Parameter):
+        return sp.Piecewise(
+            (parameter.sol, T <= temperatures.sol),
+            (parameter.liq, T >= temperatures.liq),
+            (interpolate(T, temperatures.sol, temperatures.liq, parameter.sol, parameter.liq), True)
+        )
+    elif isinstance(parameter, Functional_Parameter):
+        resolved_args = [get_parameters(T, temperatures, arg) for arg in parameter.args]
+        if parameter.func == 'thermal_expansion':
+            return density_by_thermal_expansion(T, *resolved_args)  # *get_parameters(T, temperatures, parameter.args)
+        elif parameter.func == 'interpolation':
+            return interpolate_property(T, *resolved_args)
+        elif parameter.func == 'interpolation_in_array':  # TODO
+            T_base, T_incr, data_array, data_symbol = resolved_args
+            idx = sp.floor((T - T_base) / T_incr)
+            mod = (T - T_base) - idx * T_incr  # mod replaces (T - T_base) % T_incr
+            return sp.Piecewise(
+                (data_array[0], T < T_base),
+                (data_array[-1], T >= T_base + (len(data_array) - 1) * T_incr),
+                (((T_incr - mod) * data_symbol[idx] + (mod * data_symbol[idx + 1])) / T_incr, True)
+            )
+        elif parameter.func == 'lookup':
+            return density_lookup(T, *resolved_args)
+        elif parameter.func == 'thermal_diffusivity':
+            return thermal_diffusivity_func(*resolved_args)
+        else:
+            raise ValueError("No known format in the dict: parameter = ", parameter)
+    else:
+        return parameter  # assume everything which is not a dict is a float, int, etc.
+
+
+# Material Data Class
+@dataclass
+class Ti6Al4V:                                          # 90% Ti, 6% Al, 4% V
+    latent_heat =                           290000      # m²/s²
+    temperature_solidus_1 =                 1876.0      # k         Ref [1]
+    temperature_solidus_2 =                 1880.0      # k         Ref [2]
+    temperature_liquidus =                  1928.0      # k         Ref [2]
+    density_room =                          4430.0      # kg/m³     Ref [5]
+    thermal_expansion_coefficient =         10e-6       # /k        Ref[6]
+    heat_capacity_solidus =                 700.0       # J/kg-k
+    heat_capacity_liquidus =                1126.0      # J/kg-k
+    heat_conductivity_solidus =             21.58       # W/m-k
+    heat_conductivity_liquidus =            33.60       # W/m-k
+    density_temp_array = [1878.0, 1884.2, 1894.7, 1905.3, 1915.8, 1926.3, 1928.0]
+    density_data_array = [4236.3, 4234.9, 4233.6, 4232.3, 4230.9, 4229.7, 4227.0]
+    density_temp_base =                     1878.0
+    density_temp_incr =                     8.0
+    # thermal_diffusivity_solidus = 727.7
+    # thermal_diffusivity_liquidus = 708.6
+    # electrical_resistivity = [1, 2, 5, 10, 20, 50, 100]
+    # electrical_resistivity_temperatures = [800, 900, 1200, 1500, 1700, 1900, 2000]
+    # Define temperature and density arrays
+    density_temp_array_lookup = np.array([1878.0, 1884.2, 1894.7, 1905.3, 1915.8, 1926.3, 1928.0])
+    density_data_array_lookup = np.array([4236.3, 4234.9, 4233.6, 4232.3, 4230.9, 4229.7, 4227.0])
+
+
+@dataclass
+class Constants:
+    temperature_room =                      298.15          # k
+    N_a =                                   6.022141e23     # /mol
+    u =                                     1.660538e-27    # kg
+
+
+# Instantiate the Ti6Al4V class
+ti6al4v = Ti6Al4V()
+
+Ti6Al4V_latent_heat = ti6al4v.latent_heat
+Ti6Al4V_thermal_expansion_coefficient = ti6al4v.thermal_expansion_coefficient
+
+
+@dataclass
+class Solidus_Liquidus_Parameter:
+    sol: float
+    liq: float
+
+
+@dataclass
+class Functional_Parameter:
+    func: str
+    args: list
+
+
+# Parameters for Ti6Al4V
+Ti6Al4V_temperatures = Solidus_Liquidus_Parameter(
+    sol=0.5 * (ti6al4v.temperature_solidus_1 + ti6al4v.temperature_solidus_2),
+    liq=ti6al4v.temperature_liquidus)
+
+Ti6Al4V_heat_conductivity = Solidus_Liquidus_Parameter(
+    sol=ti6al4v.heat_conductivity_solidus,
+    liq=ti6al4v.heat_conductivity_liquidus)
+
+Ti6Al4V_specific_heat_capacity = Solidus_Liquidus_Parameter(
+    sol=ti6al4v.heat_capacity_solidus,
+    liq=ti6al4v.heat_capacity_liquidus)
+
+Ti6Al4V_density_tec = Functional_Parameter(
+    func='thermal_expansion',
+    args=[Constants.temperature_room, ti6al4v.density_room, ti6al4v.thermal_expansion_coefficient])
+
+Ti6Al4V_density_int = Functional_Parameter(
+    func='interpolation',
+    args=[ti6al4v.density_temp_array, ti6al4v.density_data_array])
+
+Ti6Al4V_density_in_array = Functional_Parameter(
+    func='interpolation_in_array',  # TODO
+    args=[ti6al4v.density_temp_base, ti6al4v.density_temp_incr, ti6al4v.density_data_array,
+          sp.IndexedBase(ps.TypedSymbol('density_data_symbol', PointerType(sp.Basic('float32'))),  # PointerType(create_type(np.float32)) PointerType(base_type=float32) PointerType(sp.Basic('float32'))
+                         shape=len(ti6al4v.density_data_array))])
+
+Ti6Al4V_density_lookup = Functional_Parameter(
+    func='lookup',
+    args=[ti6al4v.density_temp_array_lookup, ti6al4v.density_data_array_lookup])
+
+Ti6Al4V_thermal_diffusivity = Functional_Parameter(
+    func='thermal_diffusivity',
+    args=[Ti6Al4V_heat_conductivity, Ti6Al4V_density_lookup, Ti6Al4V_specific_heat_capacity]
+    # solidus=ti6al4v.thermal_diffusivity_solidus,
+    # liquidus=ti6al4v.thermal_diffusivity_liquidus
+)
+'''
 with CodeGeneration() as ctx:
     data_type = "float64" if ctx.double_accuracy else "float32"
 
     u, u_tmp = ps.fields(f"u, u_tmp: {data_type}[2D]", layout='fzyx')
-    kappa = sp.Symbol("kappa")
+    thermal_diffusivity = sp.Symbol("thermal_diffusivity")
+    thermal_diffusivity_out = ps.fields(f"thermal_diffusivity_out: {data_type}[2D]", layout='fzyx')
     dx = sp.Symbol("dx")
     dt = sp.Symbol("dt")
-    heat_pde = ps.fd.transient(u) - kappa * (ps.fd.diff(u, 0, 0) + ps.fd.diff(u, 1, 1))
+    heat_pde = ps.fd.transient(u) - thermal_diffusivity * (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()
 
+    thermal_diffusivity_expr = get_parameters(u.center(), Ti6Al4V_parameters.Ti6Al4V_temperatures, Ti6Al4V_parameters.Ti6Al4V_thermal_diffusivity)
 
-    @ps.kernel
-    def update():
-        u_tmp.center @= heat_pde_discretized
-
-
-    ac = ps.AssignmentCollection(update)
+    ac = ps.AssignmentCollection(
+        subexpressions=[
+            ps.Assignment(thermal_diffusivity, thermal_diffusivity_expr)
+        ],
+        main_assignments=[
+            ps.Assignment(u_tmp.center(), heat_pde_discretized),
+            ps.Assignment(thermal_diffusivity_out.center(), thermal_diffusivity)
+        ])
     ac = ps.simp.simplifications.add_subexpressions_for_divisions(ac)
+    # ac = ps.simp.simplifications.add_subexpressions_for_field_reads(ac)
+    # ac = ps.simp.sympy_cse(ac)
 
-    generate_sweep(ctx, 'HeatEquationKernel', ac)
+    generate_sweep(ctx, 'HeatEquationKernel', ac, varying_parameters=((data_type, str(thermal_diffusivity)),))