From 2fe0a87ac42a844be513c80ccdd952efd21cfbc6 Mon Sep 17 00:00:00 2001
From: Martin Bauer <martin.bauer@fau.de>
Date: Fri, 10 Aug 2018 11:24:10 +0100
Subject: [PATCH] N phase model working

-> test_numerical_1D notebook
---
 phasefield/analytical.py     | 45 +++++++++++++++++++++++++++++----
 phasefield/kerneleqs.py      | 37 +++++++++++++++++++++++----
 phasefield/phasefieldstep.py | 48 +++++++++++++++++++++++++++++-------
 phasefield/scenarios.py      | 24 +++++-------------
 4 files changed, 117 insertions(+), 37 deletions(-)

diff --git a/phasefield/analytical.py b/phasefield/analytical.py
index 17e2da56..5966bb6c 100644
--- a/phasefield/analytical.py
+++ b/phasefield/analytical.py
@@ -1,5 +1,6 @@
 import sympy as sp
 from collections import defaultdict
+from functools import partial
 
 from pystencils.sympyextensions import multidimensional_sum as multi_sum, normalize_product, prod
 from pystencils.fd import functional_derivative, expand_diff_linear, Diff, expand_diff_full
@@ -300,22 +301,56 @@ def pressure_tensor_interface_component(free_energy, order_parameters, dim, a, b
     return result
 
 
-def pressure_tensor_from_free_energy(free_energy, order_parameters, dim):
+def pressure_tensor_from_free_energy(free_energy, order_parameters, dim, transformation_matrix=None,
+                                     include_bulk=True, include_interface=True):
+
+    def transform(f, matrix, from_symbols, to_symbols):
+        transformed_vals = matrix * sp.Matrix(to_symbols)
+        substitutions = {a: b for a, b in zip(from_symbols, transformed_vals)}
+        return f.subs(substitutions)
+
+    c = sp.symbols("C_:{}".format(len(order_parameters)))
+
+    if transformation_matrix:
+        phi_to_c = partial(transform, matrix=transformation_matrix, from_symbols=order_parameters, to_symbols=c)
+        free_energy = expand_diff_full(phi_to_c(free_energy))
+        op = c
+    else:
+        op = order_parameters
+
     def get_entry(i, j):
-        p_if = pressure_tensor_interface_component(free_energy, order_parameters, dim, i, j)
-        p_b = pressure_tensor_bulk_component(free_energy, order_parameters) if i == j else 0
+        p_if = pressure_tensor_interface_component(free_energy, op, dim, i, j) if include_interface else 0
+        if include_bulk:
+            p_b = pressure_tensor_bulk_component(free_energy, op) if i == j else 0
+        else:
+            p_b = 0
         return sp.expand(p_if + p_b)
 
-    return sp.Matrix(dim, dim, get_entry)
+    result = sp.Matrix(dim, dim, get_entry)
 
+    if transformation_matrix:
+        c_to_phi = partial(transform, matrix=transformation_matrix.inv(), from_symbols=c, to_symbols=order_parameters)
+        result = result.applyfunc(lambda e: expand_diff_full(c_to_phi(e)))
 
-def force_from_pressure_tensor(pressure_tensor, functions=None):
+    return result
+
+
+def force_from_pressure_tensor(pressure_tensor, functions=None, pbs=None):
     assert len(pressure_tensor.shape) == 2 and pressure_tensor.shape[0] == pressure_tensor.shape[1]
     dim = pressure_tensor.shape[0]
 
     def force_component(b):
         r = -sum(Diff(pressure_tensor[a, b], a) for a in range(dim))
         r = expand_diff_full(r, functions=functions)
+
+        if pbs is not None:
+            r += 2 * Diff(pbs, b) * pbs
+
         return r
 
     return sp.Matrix([force_component(b) for b in range(dim)])
+
+
+def pressure_tensor_bulk_sqrt_term(free_energy, order_parameters, density, c_s_sq=sp.Rational(1, 3)):
+    pbs = sp.sqrt(density*c_s_sq - pressure_tensor_bulk_component(free_energy, order_parameters))
+    return pbs
diff --git a/phasefield/kerneleqs.py b/phasefield/kerneleqs.py
index 0ba3b866..65b8b607 100644
--- a/phasefield/kerneleqs.py
+++ b/phasefield/kerneleqs.py
@@ -2,7 +2,8 @@ import sympy as sp
 from pystencils import Assignment
 from pystencils.fd import Discretization2ndOrder
 from lbmpy.phasefield.analytical import chemical_potentials_from_free_energy, substitute_laplacian_by_sum, \
-    force_from_phi_and_mu, symmetric_tensor_linearization, pressure_tensor_from_free_energy, force_from_pressure_tensor
+    force_from_phi_and_mu, symmetric_tensor_linearization, pressure_tensor_from_free_energy, force_from_pressure_tensor, \
+    pressure_tensor_bulk_sqrt_term
 
 
 # ---------------------------------- Kernels to compute force ----------------------------------------------------------
@@ -28,9 +29,12 @@ def force_kernel_using_mu(force_field, phi_field, mu_field, dx=1):
                        discretize(f_i)).expand() for i, f_i in enumerate(force)]
 
 
-def pressure_tensor_kernel(free_energy, order_parameters, phi_field, pressure_tensor_field, dx=1):
+def pressure_tensor_kernel(free_energy, order_parameters, phi_field, pressure_tensor_field,
+                           transformation_matrix=None, dx=1):
     dim = phi_field.spatial_dimensions
-    p = pressure_tensor_from_free_energy(free_energy, order_parameters, dim)
+
+    p = pressure_tensor_from_free_energy(free_energy, order_parameters, dim, transformation_matrix)
+
     p = p.subs({op: phi_field(i) for i, op in enumerate(order_parameters)})
     index_map = symmetric_tensor_linearization(dim)
     discretize = Discretization2ndOrder(dx=dx)
@@ -41,12 +45,35 @@ def pressure_tensor_kernel(free_energy, order_parameters, phi_field, pressure_te
     return eqs
 
 
-def force_kernel_using_pressure_tensor(force_field, pressure_tensor_field, extra_force=None, dx=1):
+def pressure_tensor_kernel_pbs(free_energy, order_parameters,
+                               phi_field, pressure_tensor_field, pbs_field, density_field,
+                               transformation_matrix=None, dx=1):
+    dim = phi_field.spatial_dimensions
+
+    p = pressure_tensor_from_free_energy(free_energy, order_parameters, dim, transformation_matrix, include_bulk=False)
+    assert transformation_matrix is None
+
+    p = p.subs({op: phi_field(i) for i, op in enumerate(order_parameters)})
+    index_map = symmetric_tensor_linearization(dim)
+    discretize = Discretization2ndOrder(dx=dx)
+    eqs = []
+    for index, lin_index in index_map.items():
+        eq = Assignment(pressure_tensor_field(lin_index), discretize(p[index]).expand())
+        eqs.append(eq)
+
+    pbs = Assignment(pbs_field.center,
+                     discretize(pressure_tensor_bulk_sqrt_term(free_energy, order_parameters, density_field.center)))
+
+    eqs.append(pbs)
+    return eqs
+
+
+def force_kernel_using_pressure_tensor(force_field, pressure_tensor_field, extra_force=None, pbs=None, dx=1):
     dim = force_field.spatial_dimensions
     index_map = symmetric_tensor_linearization(dim)
 
     p = sp.Matrix(dim, dim, lambda i, j: pressure_tensor_field(index_map[i, j] if i < j else index_map[j, i]))
-    f = force_from_pressure_tensor(p)
+    f = force_from_pressure_tensor(p, pbs=pbs)
     if extra_force:
         f += extra_force
     discretize = Discretization2ndOrder(dx=dx)
diff --git a/phasefield/phasefieldstep.py b/phasefield/phasefieldstep.py
index 7dc7cd1e..a5f48169 100644
--- a/phasefield/phasefieldstep.py
+++ b/phasefield/phasefieldstep.py
@@ -6,7 +6,7 @@ import numpy as np
 from lbmpy.lbstep import LatticeBoltzmannStep
 from lbmpy.phasefield.cahn_hilliard_lbm import cahn_hilliard_lb_method
 from lbmpy.phasefield.kerneleqs import mu_kernel, CahnHilliardFDStep, pressure_tensor_kernel, \
-    force_kernel_using_pressure_tensor
+    force_kernel_using_pressure_tensor, pressure_tensor_kernel_pbs
 from pystencils import create_kernel, create_data_handling
 from lbmpy.phasefield.analytical import chemical_potentials_from_free_energy, symmetric_tensor_linearization
 from pystencils.boundaries.boundaryhandling import FlagInterface
@@ -19,11 +19,17 @@ class PhaseFieldStep:
 
     def __init__(self, free_energy, order_parameters, domain_size=None, data_handling=None,
                  name='pf', hydro_lbm_parameters=MappingProxyType({}),
-                 hydro_dynamic_relaxation_rate=1.0, cahn_hilliard_relaxation_rates=1.0, density_order_parameter=None,
+                 hydro_dynamic_relaxation_rate=1.0,
+                 cahn_hilliard_relaxation_rates=1.0,
+                 cahn_hilliard_gammas=1,
+                 density_order_parameter=None,
                  optimization=None, kernel_params=MappingProxyType({}),
                  dx=1, dt=1, solve_cahn_hilliard_with_finite_differences=False,
-                 order_parameter_force=None, concentration_to_order_parameters=None,
-                 order_parameters_to_concentrations=None, homogeneous_neumann_boundaries=False):
+                 order_parameter_force=None,
+                 transformation_matrix=None,
+                 concentration_to_order_parameters=None,
+                 order_parameters_to_concentrations=None,
+                 homogeneous_neumann_boundaries=False):
 
         if optimization is None:
             optimization = {'openmp': False, 'target': 'cpu'}
@@ -34,8 +40,17 @@ class PhaseFieldStep:
             data_handling = create_data_handling(domain_size, periodicity=True, parallel=False)
 
         self.free_energy = free_energy
+
         self.concentration_to_order_parameter = concentration_to_order_parameters
         self.order_parameters_to_concentrations = order_parameters_to_concentrations
+        if transformation_matrix:
+            op_transformation = np.array(transformation_matrix).astype(float)
+            op_transformation_inv = np.array(transformation_matrix.inv()).astype(float)
+            axes = ([-1], [1])
+            if self.concentration_to_order_parameter is None:
+                self.concentration_to_order_parameter = lambda c: np.tensordot(c, op_transformation, axes=axes)
+            if self.order_parameters_to_concentrations is None:
+                self.order_parameters_to_concentrations = lambda p: np.tensordot(p, op_transformation_inv, axes=axes)
 
         self.chemical_potentials = chemical_potentials_from_free_energy(free_energy, order_parameters)
 
@@ -71,8 +86,7 @@ class PhaseFieldStep:
         if homogeneous_neumann_boundaries:
             def apply_neumann_boundaries(eqs):
                 fields = [data_handling.fields[self.phi_field_name],
-                          data_handling.fields[self.pressure_tensor_field_name],
-                          ]
+                          data_handling.fields[self.pressure_tensor_field_name]]
                 flag_field = data_handling.fields[self.flag_interface.flag_field_name]
                 return add_neumann_boundary(eqs, fields, flag_field, "neumann_flag", inverse_flag=False)
         else:
@@ -82,8 +96,19 @@ class PhaseFieldStep:
         # μ and pressure tensor update
         self.phi_sync = data_handling.synchronization_function([self.phi_field_name], target=target)
         self.mu_eqs = mu_kernel(F, phi, self.phi_field, self.mu_field, dx)
-        self.pressure_tensor_eqs = pressure_tensor_kernel(self.free_energy, order_parameters,
-                                                          self.phi_field, self.pressure_tensor_field, dx)
+
+        pbs = False
+        if pbs:
+            self.pbs_field_name = name + "_pbs"
+            self.pbs_field = dh.add_array(self.pbs_field_name, gpu=gpu)
+            self.pressure_tensor_eqs = pressure_tensor_kernel_pbs(self.free_energy, order_parameters, self.phi_field,
+                                                                  self.pressure_tensor_field, self.pbs_field, dx=dx,
+                                                                  density_field=None) # TODO get current density! not last one
+            # TODO call post-run on hydro-lbm before computing pbs to store the latest density
+        else:
+            self.pressure_tensor_eqs = pressure_tensor_kernel(self.free_energy, order_parameters,
+                                                              self.phi_field, self.pressure_tensor_field, dx=dx,
+                                                              transformation_matrix=transformation_matrix)
         mu_and_pressure_tensor_eqs = self.mu_eqs + self.pressure_tensor_eqs
         mu_and_pressure_tensor_eqs = apply_neumann_boundaries(mu_and_pressure_tensor_eqs)
         self.mu_and_pressure_tensor_kernel = create_kernel(sympy_cse_on_assignment_list(mu_and_pressure_tensor_eqs),
@@ -126,6 +151,9 @@ class PhaseFieldStep:
         if not hasattr(cahn_hilliard_relaxation_rates, '__len__'):
             cahn_hilliard_relaxation_rates = [cahn_hilliard_relaxation_rates] * len(order_parameters)
 
+        if not hasattr(cahn_hilliard_gammas, '__len__'):
+            cahn_hilliard_gammas = [cahn_hilliard_gammas] * len(order_parameters)
+
         self.cahn_hilliard_steps = []
 
         if solve_cahn_hilliard_with_finite_differences:
@@ -141,8 +169,10 @@ class PhaseFieldStep:
                 if op == density_order_parameter:
                     continue
 
+                print("CH Gamma", cahn_hilliard_gammas[i])
                 ch_method = cahn_hilliard_lb_method(self.hydro_lbm_step.method.stencil, self.mu_field(i),
-                                                    relaxation_rate=cahn_hilliard_relaxation_rates[i])
+                                                    relaxation_rate=cahn_hilliard_relaxation_rates[i],
+                                                    gamma=cahn_hilliard_gammas[i])
                 ch_step = LatticeBoltzmannStep(data_handling=data_handling, relaxation_rate=1, lb_method=ch_method,
                                                velocity_input_array_name=self.vel_field.name,
                                                density_data_name=self.phi_field.name,
diff --git a/phasefield/scenarios.py b/phasefield/scenarios.py
index 041ee747..851446d1 100644
--- a/phasefield/scenarios.py
+++ b/phasefield/scenarios.py
@@ -10,29 +10,21 @@ def create_three_phase_model(alpha=1, kappa=(0.015, 0.015, 0.015), include_rho=T
                   sp.Symbol("kappa_0"): kappa[0],
                   sp.Symbol("kappa_1"): kappa[1],
                   sp.Symbol("kappa_2"): kappa[2]}
+    if 'cahn_hilliard_gammas' not in kwargs:
+        kwargs['cahn_hilliard_gammas'] = [1, 1, 1/3]
+
     if include_rho:
         order_parameters = sp.symbols("rho phi psi")
         free_energy, transformation_matrix = free_energy_functional_3_phases(order_parameters)
         free_energy = free_energy.subs(parameters)
-        op_transformation = np.array(transformation_matrix).astype(float)
-        op_transformation_inv = np.array(transformation_matrix.inv()).astype(float)
-
-        def concentration_to_order_parameters(c):
-            phi = np.tensordot(c, op_transformation, axes=([-1], [1]))
-            return phi
-
         return PhaseFieldStep(free_energy, order_parameters, density_order_parameter=order_parameters[0],
-                              concentration_to_order_parameters=concentration_to_order_parameters,
-                              order_parameters_to_concentrations=lambda phi: np.tensordot(phi, op_transformation_inv,
-                                                                                          axes=([-1], [1])),
-                              **kwargs)
+                              transformation_matrix=transformation_matrix, **kwargs)
     else:
         order_parameters = sp.symbols("phi psi")
         free_energy, transformation_matrix = free_energy_functional_3_phases((1,) + order_parameters)
         free_energy = free_energy.subs(parameters)
         op_transformation = transformation_matrix.copy()
-        op_transformation.row_del(0)  # rho is assumed to be 1 - is not required
-        op_transformation = np.array(op_transformation).astype(float)
+        op_transformation.row_del(0)  # ρ is assumed to be 1 - is not required
         reverse = transformation_matrix.inv() * sp.Matrix(sp.symbols("rho phi psi"))
         op_transformation_inv = sp.lambdify(sp.symbols("phi psi"), reverse.subs(sp.Symbol("rho"), 1))
 
@@ -41,12 +33,8 @@ def create_three_phase_model(alpha=1, kappa=(0.015, 0.015, 0.015), include_rho=T
             transformed = op_transformation_inv(phi[..., 0], phi[..., 1])
             return np.moveaxis(transformed[:, 0], 0, -1)
 
-        def concentration_to_order_parameters(c):
-            phi = np.tensordot(c, op_transformation, axes=([-1], [1]))
-            return phi
-
         return PhaseFieldStep(free_energy, order_parameters, density_order_parameter=None,
-                              concentration_to_order_parameters=concentration_to_order_parameters,
+                              transformation_matrix=transformation_matrix,
                               order_parameters_to_concentrations=order_parameters_to_concentrations,
                               **kwargs)
 
-- 
GitLab