def grad( self, field, radial_units ): r""" Returns the gradient of the scalar field in input. The gradient is computed by projecting the radial derivative of the field onto the discontinuous function space specified in :class:`solver.fem`. *Arguments* field the field of which you want to compute the gradient radial_units 'physical' or 'rescaled': the former returns the field's gradient with respect to physical distances (units :math:`{M_p}^{-1}`), the latter returns the gradient with respect to the dimensionless rescaled distances used within the code """ if radial_units=='physical': grad_ = Constant(self.Mn) * field.dx(0) elif radial_units=='rescaled': grad_ = field.dx(0) else: message = "Invalid choice of radial units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) grad_ = project( grad_, self.fem.dS, self.fem.func_degree ) return grad_
def scalar_force( self, field ): r""" Returns the magnitude of the scalar force associated to the input field, per unit mass (units :math:`M_p`): .. math :: F_{\varphi} = \frac{\nabla\varphi}{M_P} if :math:`\varphi` is the input field. *Arguments* field the field associated to the scalar force """ grad = self.grad( field, 'physical' ) force = - grad / Constant(self.fields.Mp) force = project( force, self.fem.dS, self.fem.func_degree ) return force
def strong_residual_form( self, sol, units ): r""" Computes the residual :math:`F` with respect to the strong form of the Poisson equation Eq. :eq:`Eq_Poisson`. In dimensionless in-code units (`units='rescaled'`) it is: .. math:: F = \hat{\nabla}^2 \Phi_N - \frac{\hat{\rho} M_n}{2 {M_P}^2} and in physical units (`units='physical'`): .. math:: F = \nabla^2 \Phi_N - \frac{\rho}{2 {M_P}^2} .. note:: In this function, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. *Parameters* sol the solution with respect to which the weak residual is computed. units `'rescaled'` (for the rescaled units used inside the code) or `'physical'`, for physical units """ Mn, Mp = Constant( self.Mn ), Constant( self.Mp ) if units=='rescaled': resc = 1. elif units=='physical': resc = Mn**2 else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) # define r for use in the computation of the Laplacian r = Expression( 'x[0]', degree=self.fem.func_degree ) f = ( sol.dx(0).dx(0) + Constant(2.) / r * sol.dx(0) ) - 1. / (2 * Mp**2) * self.source.rho * Mn f *= resc F = project( f, self.fem.dS, self.fem.func_degree ) return F
def Qn( self, n, method='derivative', rescale=1., output_rescaled_op=False ): r""" Computes the operators: .. math:: Q_n = \frac{\alpha^n}{M^{3n-1}} \nabla^2( ( \nabla^2\varphi )^n ) associated to a field :math:`\varphi`. In the examples provided within :math:`\varphi\mathrm{enics}`, :math:`\varphi` is the the UV field :math:`\phi` or the IR field :math:`\pi`. The key to computing :math:`Q_n` is obtaining the rescaled operator :math:`\hat{Q}_n \equiv \hat{\nabla}^2 \left((\hat{\nabla}^2 \hat{\varphi})^{n}\right)`. The Laplacian :math:`y\equiv\hat{\nabla}^2\hat{\varphi}` is obtained from the solution to the equation of motion; :math:`\hat{Q}_n` can then be computed in three different ways, specified by the `method` option: * `derivative` (default): the rescaled operator is decomposed as: .. math:: \hat{\nabla}^2( y^{n} ) = n y^{n-1} \hat{\nabla}^2 y + n (n-1) y^{n-2} \hat{\nabla} y \cdot \hat{\nabla} y before being projected on the discontinuous function space specified in the `fem` instance; * `auxiliary`: :math:`w \equiv y^n` is projected onto the discontinuous function space specified in the `fem` instance, then the code solves for the linear system :math:`\hat{Q}_n = \nabla^2(w)`; * `gradient`: for the UV and IR theories supplied as :math:`\varphi\mathrm{enics}` examples, the weak formulation of the equation :math:`\hat{Q}_n = \hat{\nabla}^2( y^n )` is .. math:: \int \hat{Q}_n v \hat{r}^2 d\hat{r} = \int \hat{\nabla}^2 ( y^n ) v \hat{r}^2 d\hat{r} = - \int \hat{\nabla}( y^n ) \hat{\nabla} v \hat{r}^2 d\hat{r} = - n \int y^{n-1} \hat{\nabla}y \hat{\nabla}v \hat{r}^2 d\hat{r} where :math:`v` is a test function, so that the :math:`\hat{Q}_n` operator can be obtained by solving .. math:: \int \hat{Q}_n v \hat{r}^2 d\hat{r} = - n \int y^{n-1} \hat{\nabla}y \hat{\nabla}v \hat{r}^2 d\hat{r} which is the case for the `gradient` option. Further details are given in the `paper <https://arxiv.org/abs/2011.07037>`_ . .. note:: The :math:`Q_n` operators are computed starting from the field's rescaled Laplacian :math:`y\equiv\hat{\nabla}^2\hat{\varphi}`, so the method :func:`solve()` (solving the field's equation of motion) must have been called before calling this method. *Arguments* n order of the operator :math:`Q_n` method `'derivative'`, `'auxiliary'` or `'gradient'` rescale (optional) temporary auxiliary rescaling for :math:`Q_n`, used in tests or to prevent hitting the maximum/minimum representable number output_rescaled_op `True` to obtain the rescaled operator :math:`\hat{Q}_n \equiv \hat{\nabla}^2 \left((\hat{\nabla}^2 \hat{\varphi})^{2n+1}\right)` alongside the physical operator :math:`Q_n`, in a tuple :math:`(\hat{O}_n, O_n)`; `False` otherwise. Default: `False`. """ # copy the Laplacian if self.y is None: message = "The Laplacian doesn't seem to have been computed. Please run solve() first." raise ValueError(message) y = d.Function( self.y.function_space() ) y.assign( self.y ) y.vector()[:] *= rescale # rescale for better precision (undone later) # we can obtain Qn in three ways if method=='derivative': # expand Del( (y^(2n+1) ) and project r = Expression( 'x[0]', degree=self.fem.func_degree ) Qn_1 = n * y**(n-1) * ( y.dx(0).dx(0) + d.Constant(2.) / r * y.dx(0) ) Qn_2 = n * (n-1.) * y**(n-2) * y.dx(0)**2 Qn = Qn_1 + Qn_2 Qn = project( Qn, self.fem.dS, self.fem.func_degree ) elif method=='auxiliary': # project w=y^n and solve Qn = Del(w) w = y**n w = project( w, self.fem.dS, self.fem.func_degree ) Qn_ = d.TrialFunction( self.fem.dS ) v_ = d.TestFunction( self.fem.dS ) r2 = Expression( 'pow(x[0],2)', degree=self.fem.func_degree ) Qn_a = Qn_ * v_ * r2 * dx Qn_L = - inner( grad(w), grad(v_) ) * r2 * dx Qn = d.Function( self.fem.dS ) Qn_pde = d.LinearVariationalProblem( Qn_a, Qn_L, Qn ) Qn_solver = d.LinearVariationalSolver( Qn_pde ) Qn_solver.solve() elif method=='gradient': # expand Del( y^n ) within the weak formulation, then solve the system Qn_ = d.TrialFunction( self.fem.dS ) v_ = d.TestFunction( self.fem.dS ) r2 = Expression( 'pow(x[0],2)', degree=self.fem.func_degree ) Qn_a = Qn_ * v_ * r2 * dx Qn_L = - n * y**(n-1) * inner( grad(y), grad(v_) ) * r2 * dx Qn = d.Function( self.fem.dS ) Qn_pde = d.LinearVariationalProblem( Qn_a, Qn_L, Qn ) Qn_solver = d.LinearVariationalSolver( Qn_pde ) Qn_solver.solve() # for the physical operator, unrescale and multiply by the theory-specific remaining terms log10_Qn_coeff, Qn_oth_coeff = self.fields.log10_Qn_coeff(n), self.fields.Qn_oth_coeff(n) # compute log and then exp to avoid hitting the maximum/minimum representable number log10_R = log10_Qn_coeff + (2*n+2) * log10(self.Mn) + n * ( log10(self.Mf1) - log10( rescale ) ) phys_Qn = d.Function( self.fem.dS ) phys_Qn.vector()[:] = Qn_oth_coeff * 10.**log10_R * Qn.vector()[:] if output_rescaled_op: return Qn, phys_Qn else: return phys_Qn
def On( self, n, method='derivative', rescale=1., output_rescaled_op=False ): r""" Computes the operators: .. math:: O_n = (-1)^{n+1} \frac{2n+2}{2n+1} \binom{3n}{n} \alpha^{2n+1} \lambda^n \nabla^2 \left((\nabla^2 \varphi)^{2n+1}\right) M^{-6n-2} associated to a field :math:`\varphi`. In the examples provided within :math:`\varphi\mathrm{enics}`, :math:`\varphi` is the the UV field :math:`\phi` or the IR field :math:`\pi`. The operators are computed starting from the field's rescaled Laplacian :math:`y\equiv\hat{\nabla}^2\hat{\varphi}`, so the field's equation of motion must have been solved prior to invoking this method. For the method used to compute :math:`\nabla^2 \left((\nabla^2 \varphi)^{2n+1}\right)`, see the documentation of :func:`Qn`. *Arguments* n order of the operator :math:`O_n` method `'derivative'`, `'auxiliary'` or `'gradient'` - see func:`Qn` rescale (optional) temporary auxiliary variable used to rescale the Laplacian during the computation of :math:`\nabla^2 \left((\nabla^2 \varphi)^{2n+1}\right)`, to prevent hitting the maximum/minimum representable number output_rescaled_op `True` to obtain the rescaled operator :math:`\hat{O}_n \equiv \hat{\nabla}^2 \left((\hat{\nabla}^2 \hat{\varphi})^{2n+1}\right)` alongside the physical operator :math:`O_n`, in a tuple :math:`(\hat{O}_n, O_n)`; `False` otherwise. Default: `False`. """ # copy the Laplacian if self.y is None: message = "The Laplacian doesn't seem to have been computed. Please run solve() first." raise ValueError(message) y = d.Function( self.fem.S ) y.assign( self.y ) y.vector()[:] *= rescale # rescale for better precision (undone later) # we can obtain On in three ways if method=='derivative': # expand Del( (y^(2n+1) ) and project r = Expression( 'x[0]', degree=self.fem.func_degree ) On_1 = (2.*n+1.) * y**(2*n) * ( y.dx(0).dx(0) + d.Constant(2.) / r * y.dx(0) ) On_2 = (2.*n+1.) * (2.*n) * y**(2*n-1) * y.dx(0)**2 On = On_1 + On_2 On = project( On, self.fem.dS, self.fem.func_degree ) elif method=='auxiliary': # project W=y^(2n+1) and solve On = Del(W) W = y**(2*n+1) W = project( W, self.fem.dS, self.fem.func_degree ) On_ = d.TrialFunction( self.fem.dS ) v_ = d.TestFunction( self.fem.dS ) r2 = Expression( 'pow(x[0],2)', degree=self.fem.func_degree ) On_a = On_ * v_ * r2 * dx On_L = - inner( grad(W), grad(v_) ) * r2 * dx On = d.Function( self.fem.dS ) On_pde = d.LinearVariationalProblem( On_a, On_L, On ) On_solver = d.LinearVariationalSolver( On_pde ) On_solver.solve() elif method=='gradient': # expand Del( y^(2n+1) ) within the weak formulation, then solve the system On_ = d.TrialFunction( self.fem.dS ) v_ = d.TestFunction( self.fem.dS ) r2 = Expression( 'pow(x[0],2)', degree=self.fem.func_degree ) On_a = On_ * v_ * r2 * dx On_L = - (2*n+1) * y**(2*n) * inner( grad(y), grad(v_) ) * r2 * dx On = d.Function( self.fem.dS ) On_pde = d.LinearVariationalProblem( On_a, On_L, On ) On_solver = d.LinearVariationalSolver( On_pde ) On_solver.solve() # for the physical operator, unrescale and multiply by the theory-specific remaining terms Mn, Mf1 = self.Mn, self.Mf1 log10_On_coeff, On_oth_coeff = self.fields.log10_On_coeff(n), self.fields.On_oth_coeff(n) log10_R = log10_On_coeff + (4*n+4) * log10(Mn) + (2*n+1) * ( log10(Mf1) - log10( rescale ) ) C_On = (-1)**(n+1) * binom(3*n,n) / (2.*n+1.) * On_oth_coeff * 10.**log10_R phys_On = d.Function( self.fem.dS ) phys_On.vector()[:] = C_On * On.vector()[:] if output_rescaled_op: return On, phys_On else: return phys_On
def output_term( self, term='LHS', norm='none', units='rescaled', output_label=False ): r""" Outputs the left- and right-hand side of the Poisson equation Eq. :eq:`Eq_Poisson`. The terms can be output in physical or dimensionless in-code units, by choosing either `units='physical'` or `units='rescaled'`: ============== ============================== =============================================== `units=` `term='LHS'` `term='RHS'` ============== ============================== =============================================== `'rescaled'` :math:`\hat{\nabla}^2\Phi_N` :math:`\frac{1}{2} \frac{\hat{\rho}}{M_P} M_n` `'physical'` :math:`\nabla^2 \Phi_N` :math:`\frac{\rho}{2 {M_P}^2}` ============== ============================== =============================================== where :math:`\Phi_N` is the Newtonian potential, :math:`\rho` the source and :math:`M_P` the Planck mass. .. note:: In this method, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. *Parameters* term choice of `'LHS'` and `'RHS'` for the left- and right-hand side of the Poisson equation norm `'L2'`, `'linf'` or `'none'`. If `'L2'` or `'linf'`: compute the :math:`L_2` or :math:`\ell_{\infty}` norm of the residual; if `'none'`, return the full term over the box - as opposed to its norm. units `rescaled` (default) or `physical`; choice of units for the output output_label if `True`, output a string with a label for the term (which can be used, e.g. in plot legends) """ Mp, Mn = Constant( self.Mp ), Constant( self.Mn ) if units=='rescaled': resc = 1. str_nabla2 = '\\hat{\\nabla}^2' str_rho = '\\frac{1}{2} \\frac{\\hat{\\rho}}{{M_p}^2} M_n' elif units=='physical': resc = self.Mn**2 str_nabla2 = '\\nabla^2' str_rho = '\\frac{\\rho}{2 {M_p}^2}' else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) str_PhiN = '\\Phi_N' # define r for use in the computation of the Laplacian r = Expression( 'x[0]', degree=self.fem.func_degree ) if term=='LHS': term = self.PhiN.dx(0).dx(0) + Constant(2.) / r * self.PhiN.dx(0) label = r"$%s %s$" % ( str_nabla2, str_PhiN ) elif term=='RHS': term = 1. / (2 * Mp**2) * self.source.rho * Mn label = r"$%s$" % str_rho # rescale if needed to get physical units term *= resc term = project( term, self.fem.dS, self.fem.func_degree ) # 'none' = return function, not norm if norm=='none': result = term # from here on return a norm. This nested if is to preserve the structure of the original # built-in FEniCS norm function elif norm=='linf': # infinity norm, i.e. max abs value at vertices result = r2_norm( term.vector(), self.fem.func_degree, norm_type=norm ) else: result = r2_norm( term, self.fem.func_degree, norm_type=norm ) if output_label: return result, label else: return result
def output_term(self, eqn=1, term='LHS', norm='none', units='rescaled', output_label=False): r""" Outputs the different terms in the UV system of equations. The terms can be output in physical or dimensionless in-code units, by choosing either `units='physical'` or `units='rescaled'`. Possible choices of terms are listed in the tables below; recall that :math:`Y\equiv\nabla^2\phi` and :math:`Z\equiv\nabla^2 H`. - `eqn=1`: ============== =============================================================================================== ============== `units=` `term='LHS'` `term='RHS'` ============== =============================================================================================== ============== `'rescaled'` :math:`\hat{Y} - \left(\frac{m}{M_n}\right)^2\hat{\phi} - \frac{M_{f2}}{M_{f1}}\alpha\hat{Z}` :math:`\frac{\hat{\rho}}{M_P}\frac{Mn}{M_{f1}}` `'physical'` :math:`Y -m^2 \phi -\alpha Z` :math:`\frac{\rho}{M_P}` ============== =============================================================================================== ============== ============== ================= ================================================= ============================================= ================= `units=` `term=1` `term=2` `term=3` `term=4` ============== ================= ================================================= ============================================= ================= `'rescaled'` :math:`\hat{Y}` :math:`-\left(\frac{m}{M_n}\right)^2\hat{\phi}` :math:`-\frac{M_{f2}}{M_{f1}}\alpha\hat{Z}` same as `'RHS'` `'physical'` :math:`Y` :math:`-m^2\phi` :math:`-\alpha Z` same as `'RHS'` ============== ================= ================================================= ============================================= ================= - `eqn=2`: ============== =========================================================================================== ========================================================================== `units=` `term='LHS'` `term='RHS'` ============== =========================================================================================== ========================================================================== `'rescaled'` :math:`\hat{Z} -\left(\frac{M}{M_n}\right)^2\hat{H} - \alpha\frac{M_{f1}}{M_{f2}}\hat{Y}` :math:`-\frac{\lambda}{6} \left( \frac{M_{f2}}{M_n} \right)^2 \hat{H}^3` `'physical'` :math:`Z -M^2 H -\alpha Y` :math:`-\frac{\lambda}{6} H^3` ============== =========================================================================================== ========================================================================== ============== ================= ============================================= ============================================= ================= `units=` `term=1` `term=2` `term=3` `term=4` ============== ================= ============================================= ============================================= ================= `'rescaled'` :math:`\hat{Z}` :math:`-\left(\frac{M}{Mn}\right)^2\hat{H}` :math:`-\alpha\frac{M_{f1}}{M_{f2}}\hat{Y}` same as `'RHS'` `'physical'` :math:`Z` :math:`-M^2 H` :math:`-\alpha Y` same as `'RHS'` ============== ================= ============================================= ============================================= ================= Although the right-hand-side (RHS) of the equation of motion for :math:`H` is 0, this choice allows better comparison of the scale of the equation against the residuals. - `eqn=3`: ============== ========================================== ================================== ================= `units=` `term='LHS'` `term=1` `term=2` ============== ========================================== ================================== ================= `'rescaled'` :math:`\hat{\nabla}^2\hat{\phi}-\hat{Y}` :math:`\hat{\nabla}^2\hat{\phi}` :math:`\hat{Y}` `'physical'` :math:`\nabla^2\phi - Y` :math:`\nabla^2\phi` :math:`Y` ============== ========================================== ================================== ================= - `eqn=4`: ============== ======================================= ================================== ================= `units=` `term='LHS'` `term=1` `term=2` ============== ======================================= ================================== ================= `'rescaled'` :math:`\hat{\nabla}^2\hat{H}-\hat{Z}` :math:`\hat{\nabla}^2\hat{H}` :math:`\hat{Z}` `'physical'` :math:`\nabla^2 H - Z` :math:`\nabla^2 H` :math:`Z` ============== ======================================= ================================== ================= Equations (1) and (2) allow to look at the terms in the equations of motion, Eq. (3) and (4) enforce consistency relations :math:`Y = '\nabla^2\phi` and :math:`Z = \nabla^2 H`. .. note:: In this method, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. *Parameters* eqn (`integer`) choice of equation term choice of `LHS`, `RHS` or a number; for `eqn=1,2`, valid terms range from 1 to 4, for `eqn=3,4`, valid terms range from 1 to 2 norm `'L2'`, `'linf'` or `'none'`. If `'L2'` or `'linf'`: compute the :math:`L_2` or :math:`\ell_{\infty}` norm of the residual; if `'none'`, return the full term over the box - as opposed to its norm. units `rescaled` (default) or `physical`; choice of units for the output output_label if `True`, output a string with a label for the term (which can be used, e.g. in plot legends) """ # cast params as constant functions so that, if they are set to 0, FEniCS still understand # what is being integrated m, M, Mp = Constant(self.fields.m), Constant(self.fields.M), Constant( self.fields.Mp) alpha, lam = Constant(self.fields.alpha), Constant(self.fields.lam) Mn, Mf1, Mf2 = Constant(self.Mn), Constant(self.Mf1), Constant( self.Mf2) if units == 'rescaled': resc_13 = 1. resc_24 = 1. str_phi, str_h, str_y, str_z = '\\hat{\\phi}', '\\hat{H}', '\\hat{Y}', '\\hat{Z}' str_nabla2 = '\\hat{\\nabla}^2' str_coup_z = '\\alpha \\frac{M_{f2}}{M_{f1}} \\hat{Z}' str_coup_y = '\\alpha \\frac{M_{f1}}{M_{f2}} \\hat{Y}' str_m2 = '\\left( \\frac{m}{M_n} \\right)^2' str_M2 = '\\left( \\frac{M}{M_n} \\right)^2' str_nl = '\\frac{\\lambda}{6} \\frac{M_{f2}}{M_n} \\hat{H}^3' str_rho = '\\frac{\hat{\\rho}}{M_p}\\frac{M_n}{M_{f1}}' elif units == 'physical': resc_13 = self.Mn**2 * self.Mf1 resc_24 = self.Mn**2 * self.Mf2 str_phi, str_h, str_y, str_z = '\\phi', 'H', 'Y', 'Z' str_nabla2 = '\\nabla^2' str_coup_z = '\\alpha Z' str_coup_y = '\\alpha Y' str_m2 = 'm^2' str_M2 = 'M^2' str_nl = '\\frac{\\lambda}{6} \\hat{H}^3' str_rho = '\\frac{\\rho}{M_p}' else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) # split solution in phi, h, y, z phi, h, y, z = self.phi, self.h, self.y, self.z # define r for use in the computation of the Laplacian r = Expression('x[0]', degree=self.fem.func_degree) if eqn == 1: if term == 'LHS': # I expand manually the Laplacian into 2/r df/dr + d2f/dr2 Term = y - (m / Mn)**2 * phi - alpha * (Mf2 / Mf1) * z label = r"$%s - %s%s - %s$" % (str_y, str_m2, str_phi, str_coup_z) elif term == 'RHS': Term = self.source.rho / Mp * Mn / Mf1 label = r"$%s$" % str_rho elif term == 1: Term = y label = r"$%s$" % str_y elif term == 2: Term = -(m / Mn)**2 * phi label = r"$-%s%s$" % (str_m2, str_phi) elif term == 3: Term = -alpha * (Mf2 / Mf1) * z label = r"$-%s$" % str_coup_z elif term == 4: Term = self.source.rho / Mp * Mn / Mf1 label = r"$%s$" % str_rho # rescale if needed to get physical units Term *= resc_13 elif eqn == 2: if term == 'LHS': Term = z - (M / Mn)**2 * h - alpha * (Mf1 / Mf2) * y label = r"$%s - %s%s -%s$" % (str_z, str_M2, str_h, str_coup_y) elif term == 'RHS': Term = lam / 6. * (Mf2 / Mn)**2 * h**3 label = r"$%s$" % str_nl elif term == 1: Term = z label = r"$%s$" % str_z elif term == 2: Term = -(M / Mn)**2 * h label = r"$%s%s$" % (str_M2, str_h) elif term == 3: Term = -alpha * (Mf1 / Mf2) * y label = r"$-%s$" % str_coup_y elif term == 4: Term = lam / 6. * (Mf2 / Mn)**2 * h**3 label = r"$%s$" % str_nl # rescale if needed to get physical units Term *= resc_24 elif eqn == 3: # consistency of y = Del phi if term == 'LHS': Term = Constant(2.) / r * phi.dx(0) + phi.dx(0).dx(0) - y label = r"$%s%s - %s$" % (str_nabla2, str_phi, str_y) elif term == 1: Term = Constant(2.) / r * phi.dx(0) + phi.dx(0).dx(0) label = r"$%s%s$" % (str_nabla2, str_phi) elif term == 2: Term = y label = r"$%s$" % str_y # rescale if needed to get physical units Term *= resc_13 elif eqn == 4: # consistency of z = Del H if term == 'LHS': Term = Constant(2.) / r * h.dx(0) + h.dx(0).dx(0) - z label = r"$%s%s - %s$" % (str_nabla2, str_h, str_z) elif term == 1: Term = Constant(2.) / r * h.dx(0) + h.dx(0).dx(0) label = r"$%s%s$" % (str_nabla2, str_h) elif term == 2: Term = z label = r"$%s$" % str_z # rescale if needed to get physical units Term *= resc_24 Term_func = project(Term, self.fem.dS, self.fem.func_degree) # 'none' = return function, not norm if norm == 'none': result = Term_func # from here on return a norm. This nested if is to preserve the structure of the original # built-in FEniCS norm function elif norm == 'linf': # infinity norm, i.e. max abs value at vertices result = r2_norm(Term_func.vector(), self.fem.func_degree, norm_type=norm) else: result = r2_norm(Term_func, self.fem.func_degree, norm_type=norm) if output_label: return result, label else: return result
def strong_residual_form(self, sol, units): r""" Computes the residual with respect to the strong form of the equations. The total residual is obtained by summing the residuals of all equations: .. math:: F = F_1 + F_2 + F_3 + F_4 where, in dimensionless in-code units (`units='rescaled'`): .. math:: & F_1(\hat{\phi},\hat{H},\hat{Y},\hat{Z}) = \hat{Y} - \left( \frac{m}{M_n} \right)^2\hat{\phi} - \alpha \frac{M_{f2}}{M_{f1}} \hat{Z} - \frac{\hat{\rho}}{M_p}\frac{M_n}{M_{f1}} & F_2(\hat{\phi},\hat{H},\hat{Y},\hat{Z}) = \hat{Z} - \left( \frac{M}{M_n} \right)^2 \hat{H} - \alpha \frac{M_{f1}}{M_{f2}} \hat{Y} - \frac{\lambda}{6} \left( \frac{M_{f2}}{M_n} \right)^2 \hat{H}^3 & F_3(\hat{\phi},\hat{H},\hat{Y},\hat{Z}) = \hat{\nabla}^2\hat{\phi} - \hat{Y} & F_4(\hat{\phi},\hat{H},\hat{Y},\hat{Z}) = \hat{\nabla}^2\hat{H} - \hat{Z} and in physical units (`units='physical'`): .. math:: & F_1(\phi,H,Y,Z) = Y - m^2 \phi -\alpha H - \frac{\rho}{M_P} & F_2(\phi,H,Y,Z) = Z - M^2 H - \alpha\phi - \frac{\lambda}{6} H^3 & F_3(\phi,H,Y,Z) = \nabla^2\phi - Y & F_4(\phi,H,Y,Z) = \nabla^2 H - Z .. note:: In this function, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. Note that the weak residual in :func:`weak_residual_form` is just the scalar product of the strong residuals by test functions. *Parameters* sol the solution with respect to which the weak residual is computed. units `'rescaled'` (for the rescaled units used inside the code) or `'physical'`, for physical units """ if units == 'rescaled': resc_13 = 1. resc_24 = 1. elif units == 'physical': resc_13 = self.Mn**2 * self.Mf1 resc_24 = self.Mn**2 * self.Mf2 else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) # cast params as constant functions so that, if they are set to 0, # fenics still understand what it is integrating m, M, Mp = Constant(self.fields.m), Constant(self.fields.M), Constant( self.fields.Mp) alpha, lam = Constant(self.fields.alpha), Constant(self.fields.lam) Mn, Mf1, Mf2 = Constant(self.Mn), Constant(self.Mf1), Constant( self.Mf2) # split solution in phi, h, y, z phi, h, y, z = d.split(sol) # initialise residual function F = d.Function(self.dV) # define r for use in the computation of the Laplacian r = Expression('x[0]', degree=self.fem.func_degree) # equation 1 f1 = y - (m / Mn)**2 * phi - alpha * ( Mf2 / Mf1) * z - self.source.rho / Mp * Mn / Mf1 f1 *= resc_13 F1 = project(f1, self.fem.dS, self.fem.func_degree) # equation 2 f2 = z - (M / Mn)**2 * h - alpha * (Mf1 / Mf2) * y - lam / 6. * ( Mf2 / Mn)**2 * h**3 f2 *= resc_24 F2 = project(f2, self.fem.dS, self.fem.func_degree) # equation 3 - I expand manually the Laplacian into 2/r df/dr + d2f/dr2 f3 = Constant(2.) / r * phi.dx(0) + phi.dx(0).dx(0) - y f3 *= resc_13 F3 = project(f3, self.fem.dS, self.fem.func_degree) # equation 4 f4 = Constant(2.) / r * h.dx(0) + h.dx(0).dx(0) - z f4 *= resc_24 F4 = project(f4, self.fem.dS, self.fem.func_degree) # combine equations fa = d.FunctionAssigner( self.dV, [self.fem.dS, self.fem.dS, self.fem.dS, self.fem.dS]) fa.assign(F, [F1, F2, F3, F4]) return F
def output_term(self, eqn=1, term='LHS', norm='none', units='rescaled', output_label=False): r""" Outputs the different terms in the IR system of equations. The terms can be output in physical or dimensionless in-code units, by choosing either `units='physical'` or `units='rescaled'`. Possible choices of terms are listed in the tables below; recall that :math:`Y\equiv\nabla^2\phi` and :math:`W \equiv Y^n`. - `eqn=1`: ============== =========================================== ================================= ================= `units=` `term='LHS'` `term=1` `term=2` ============== =========================================== ================================= ================= `'rescaled'` :math:`\hat{Y} - \hat{\nabla}^2\hat{\pi}` :math:`\hat{\nabla}^2\hat{\pi}` :math:`\hat{Y}` `'physical'` :math:`Y - \nabla^2\pi` :math:`\nabla^2\pi` :math:`Y` ============== =========================================== ================================= ================= - `eqn=2`: ============== ============================= =================== ================= `units=` `term='LHS'` `term=1` `term=2` ============== ============================= =================== ================= `'rescaled'` :math:`\hat{W} - \hat{Y}^n` :math:`\hat{Y}^n` :math:`\hat{W}` `'physical'` :math:`W - Y^n` :math:`Y^n` :math:`W` ============== ============================= =================== ================= - `eqn=3`: ============== ============================================================================================================================================================================ ================================================= `units=` `term='LHS'` `term='RHS'` ============== ============================================================================================================================================================================ ================================================= `'rescaled'` :math:`\hat{Y} -\left(\frac{m}{M_n}\right)^2\hat{\pi} - \epsilon \left( \frac{M_n}{\Lambda} \right)^{3n-1} \left( \frac{M_{f1}}{M_n} \right)^{n-1} \hat{\nabla}^2 \hat{W}` :math:`\frac{\hat{\rho}}{M_P}\frac{Mn}{M_{f1}}` `'physical'` :math:`Y - m^2 \pi - \frac{\epsilon}{\Lambda^{3n-1}} \nabla^2 W` :math:`\frac{\rho}{M_P}` ============== ============================================================================================================================================================================ ================================================= ============== ================= ================================================ ============================================================================================================================= ================= `units=` `term=1` `term=2` `term=3` `term=4` ============== ================= ================================================ ============================================================================================================================= ================= `'rescaled'` :math:`\hat{Y}` :math:`-\left(\frac{m}{M_n}\right)^2\hat{\pi}` :math:`- \epsilon \left( \frac{M_n}{\Lambda} \right)^{3n-1} \left( \frac{M_{f1}}{M_n} \right)^{n-1} \hat{\nabla}^2 \hat{W}` same as `'RHS'` `'physical'` :math:`Y` :math:`-m^2 \pi` :math:`- \frac{\epsilon}{\Lambda^{3n-1}} \nabla^2 W` same as `'RHS'` ============== ================= ================================================ ============================================================================================================================= ================= Equation (3) allows to look at the terms in the equation of motion, Eq. (1) and (2) enforce consistency relations :math:`Y = '\nabla^2\pi` and :math:`W=Y^n`. .. note:: In this method, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. *Parameters* eqn (`integer`) choice of equation term choice of term: for `eqn=1,2`, valid terms range are `'LHS', 1, 2`; for `eqn=3`, valid terms are `'LHS', 'RHS'` and integers from 1 to 4 norm `'L2'`, `'linf'` or `'none'`. If `'L2'` or `'linf'`: compute the :math:`L_2` or :math:`\ell_{\infty}` norm of the residual; if `'none'`, return the full term over the box - as opposed to its norm. units `rescaled` (default) or `physical`; choice of units for the output output_label if `True`, output a string with a label for the term (which can be used, e.g. in plot legends) """ # cast params as constant functions so that, if they are set to 0, FEniCS still understand # what is being integrated m, Lambda, Mp = Constant(self.fields.m), Constant( self.fields.Lambda), Constant(self.fields.Mp) epsilon = Constant(self.fields.epsilon) Mn, Mf1 = Constant(self.Mn), Constant(self.Mf1) n = self.fields.n if units == 'rescaled': resc_1, resc_2, resc_3 = 1., 1., 1. str_pi, str_w, str_y = '\\hat{\pi}', '\\hat{W}', '\\hat{Y}' str_nabla2 = '\\hat{\\nabla}^2' str_m2 = '\\left( \\frac{m}{M_n} \\right)^2' str_nl = '\\epsilon \\left( \\frac{M_n}{\\Lambda} \\right)^{%d} \\left( \\frac{M_{f1}}{M_n} \\right)^{%d} \\hat{\\nabla}^2 \\hat{W}' % ( 3 * n - 1, n - 1) str_rho = '\\frac{\hat{\\rho}}{M_p}\\frac{M_n}{M_{f1}}' elif units == 'physical': resc_1 = self.Mn**2 * self.Mf1 resc_2 = (self.Mn**2 * self.Mf1)**self.fields.n resc_3 = self.Mn**2 * self.Mf1 #print('********************************************************************************************************************') #print(' WARNING: numbers in equation 2 may hit the minimum representable number, consider using rescaled units instead') #print('********************************************************************************************************************') str_pi, str_w, str_y = '\\pi', 'W', 'Y' str_nabla2 = '\\nabla^2' str_m2 = 'm^2' str_nl = '\\frac{\\epsilon}{\\Lambda^{%d}} \\nabla^2 W' % (3 * n - 1) str_rho = '\\frac{\\rho}{M_p}' else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) pi, w, y = self.pi, self.w, self.y # define r for use in the computation of the Laplacian r = Expression('x[0]', degree=self.fem.func_degree) if eqn == 1: if term == 'LHS': Term = y - (pi.dx(0).dx(0) + Constant(2.) / r * pi.dx(0)) label = r"$%s - %s%s$" % (str_y, str_nabla2, str_pi) elif term == 1: Term = (pi.dx(0).dx(0) + Constant(2.) / r * pi.dx(0)) label = r"$%s%s$" % (str_nabla2, str_pi) elif term == 2: Term = y label = r"$%s$" % str_y # rescale if needed to get physical units Term *= resc_1 elif eqn == 2: if term == 'LHS': Term = w - y**n label = r"$%s - %s^{%d}$" % (str_w, str_y, n) elif term == 1: Term = y**n label = r"$%s^{%d}$" % (str_y, n) elif term == 2: Term = w label = r"$%s$" % str_w # rescale if needed to get physical units Term *= resc_2 elif eqn == 3: if term == 'LHS': Term = y - ( m/Mn )**2 * pi - \ epsilon * ( Mn / Lambda )**(3*n-1) * ( Mf1 / Mn )**(n-1) * ( w.dx(0).dx(0) + Constant(2.)/r * w.dx(0) ) label = r"$%s - %s%s - %s$" % (str_y, str_m2, str_pi, str_nl) elif term == 'RHS': Term = self.source.rho / Mp * Mn / Mf1 label = r"$%s$" % str_rho elif term == 1: Term = y label = r"$%s$" % str_y elif term == 2: Term = -(m / Mn)**2 * pi label = r"$-%s %s$" % (str_m2, str_pi) elif term == 3: Term = -epsilon * (Mn / Lambda)**(3 * n - 1) * (Mf1 / Mn)**( n - 1) * (w.dx(0).dx(0) + Constant(2.) / r * w.dx(0)) label = r"$-%s$" % str_nl elif term == 4: Term = self.source.rho / Mp * Mn / Mf1 label = r"$%s$" % str_rho # rescale if needed to get physical units Term *= resc_3 Term = project(Term, self.fem.dS, self.fem.func_degree) # 'none' = return function, not norm if norm == 'none': result = Term # from here on return a norm. This nested if is to preserve the structure of the original # built-in FEniCS norm function elif norm == 'linf': # infinity norm, i.e. max abs value at vertices result = r2_norm(Term.vector(), self.fem.func_degree, norm_type=norm) else: result = r2_norm(Term, self.fem.func_degree, norm_type=norm) if output_label: return result, label else: return result
def strong_residual_form(self, sol, units): r""" Computes the residual with respect to the strong form of the equations. The total residual is obtained by summing the residuals of all equations: .. math:: F = F_1 + F_2 + F_3 where, in dimensionless in-code units (`units='rescaled'`): .. math:: & F_1(\hat{\pi},\hat{W},\hat{Y}) = \hat{\nabla}^2 \hat{\pi} - \hat{Y} & F_2(\hat{\pi},\hat{W},\hat{Y}) = \hat{W} - \hat{Y}^n & F_3(\hat{\pi},\hat{W},\hat{Y}) = \hat{Y} - \left( \frac{m}{M_n} \right)^2 \hat{\pi} - \epsilon \left( \frac{M_n}{\Lambda} \right)^{3n-1} \left(\frac{M_{f1}}{M_n}\right)^{n-1} \hat{\nabla}^2 \hat{W} - \frac{\hat{\rho}}{M_P} \frac{M_n}{M_{f1}} and in physical units (`units='physical'`): .. math:: & F_1(\pi,W,Y) = \nabla^2\pi - Y & F_2(\pi,W,Y) = W - Y^n & F_3(\pi,W,Y) = Y - m^2 \pi - \frac{\epsilon}{\Lambda^{3n-1}} \nabla^2 W - \frac{\rho}{M_P} .. note:: In this function, the Laplacian :math:`\hat{\nabla}^2` is obtained by projecting :math:`\frac{\partial^2}{\partial\hat{r}^2} + 2\frac{\partial}{\partial\hat{r}}`. As such, it should not be used with interpolating polynomials of degree less than 2. Note that the weak residual in :func:`weak_residual_form` is just the scalar product of the strong residuals by test functions. *Parameters* sol the solution with respect to which the weak residual is computed. units `'rescaled'` (for the rescaled units used inside the code) or `'physical'`, for physical units """ if units == 'rescaled': resc_1, resc_2, resc_3 = 1., 1., 1. elif units == 'physical': resc_1 = self.Mn**2 * self.Mf1 resc_2 = (self.Mn**2 * self.Mf1)**self.fields.n resc_3 = self.Mn**2 * self.Mf1 else: message = "Invalid choice of units: valid choices are 'physical' or 'rescaled'." raise ValueError(message) # cast params as constant functions so that, if they are set to 0, FEniCS still understand # what is being integrated m, Lambda, Mp = Constant(self.fields.m), Constant( self.fields.Lambda), Constant(self.fields.Mp) epsilon = Constant(self.fields.epsilon) Mn, Mf1 = Constant(self.Mn), Constant(self.Mf1) n = self.fields.n # split solution into pi, w, y pi, w, y = d.split(sol) # initialise residual function F = d.Function(self.dV) # define r for use in the computation of the Laplacian r = Expression('x[0]', degree=self.fem.func_degree) # equation 1 f1 = pi.dx(0).dx(0) + Constant(2.) / r * pi.dx(0) - y f1 *= Constant(resc_1) F1 = project(f1, self.fem.dS, self.fem.func_degree) # equation 2 f2 = w - y**n f2 *= Constant(resc_2) F2 = project(f2, self.fem.dS, self.fem.func_degree) # equation 3 f3 = y - ( m/Mn )**2 * pi \ - epsilon * ( Mn / Lambda )**(3*n-1) * ( Mf1 / Mn )**(n-1) * ( w.dx(0).dx(0) + Constant(2.)/r * w.dx(0) ) \ - self.source.rho / Mp * Mn / Mf1 f3 *= Constant(resc_3) F3 = project(f3, self.fem.dS, self.fem.func_degree) # combine equations fa = d.FunctionAssigner(self.dV, [self.fem.dS, self.fem.dS, self.fem.dS]) fa.assign(F, [F1, F2, F3]) return F
def NL_initial_guess(self, y_method='vector'): r""" Obtains an initial guess for the Galileon equation of motion by assuming the nonlinear term is dominant, i.e.: .. math:: -\frac{\epsilon}{\Lambda^{3n-1}}\nabla^2(\nabla^2\pi^n) \approx \frac{\rho}{M_P} The initial guess is computed by first solving the Poisson equation: .. math:: -\hat{\nabla}\hat{W} =\left( \frac{\Lambda}{M_n} \right)^{3n-1} \left(\frac{M_n}{M_{f1}}\right)^n \frac{\hat{\rho}}{\epsilon M_P} and then obtaining :math:`\hat{Y}=\sqrt[n]{\hat{W}}` through one of three methods explained below. Finally, :math:`\hat{\pi}` is computed by solving the Poisson equation .. math:: \hat{\nabla}\hat{\pi} = \hat{Y}. The main methods to obtain :math:`\hat{Y}` from :math:`\hat{Z}` are interpolation and projection: the standard FEniCS implementation for both can be chosen by setting `y\_method='interpolate'` and `y\_method='project'`. A third method (`y\_method='vector'`, default), formally identical to interpolation, consists in assigning :math:`\hat{Y}`'s value at all nodes through e.g.: .. code-block:: python y.vector().set_local( np.sqrt( np.abs( w.vector().get_local() ) ) ) However, because of differences in the implementations of :math:`\sqrt[n]{\cdot}` called by the two methods, the latter generally gives better results compared to `'interpolate'`. *Arguments* y_method `'vector'` (default), `'interpolate'` or `'project'` """ # cast params as constant functions so that, if they are set to 0, FEniCS still understand # what is being integrated m, Lambda, Mp = Constant(self.fields.m), Constant( self.fields.Lambda), Constant(self.fields.Mp) epsilon = Constant(self.fields.epsilon) Mn, Mf1 = Constant(self.Mn), Constant(self.Mf1) n = self.fields.n # get the boundary conditions for w only def boundary(x): return self.fem.mesh.r_max - x[0] < d.DOLFIN_EPS wD = Constant(0.) w_Dirichlet_bc = d.DirichletBC(self.fem.S, wD, boundary, method='pointwise') # define trial and test function w_ = d.TrialFunction(self.fem.S) v_ = d.TestFunction(self.fem.S) # for the measure r2 = Expression('pow(x[0],2)', degree=self.fem.func_degree) # bilinear and linear forms w_a = inner(grad(w_), grad(v_)) * r2 * dx w_L = (Lambda / Mn)**(3 * n - 1) * ( Mn / Mf1)**n / epsilon * self.source.rho / Mp * v_ * r2 * dx # define a function for the solution w = d.Function(self.fem.S) # solve w_pde = d.LinearVariationalProblem(w_a, w_L, w, w_Dirichlet_bc) w_solver = d.LinearVariationalSolver(w_pde) print('Getting NL initial guess...') w_solver.solve() # now we have w. we can obtain y by projection or interpolation if y_method == 'interpolate': # I use the functions sqrt and cbrt because they're more precise than pow(w,1/n) if n == 2: code = "sqrt(fabs(w))" elif n == 3: code = "cbrt(w)" else: if n % 2 == 0: # even power code = "pow(fabs(w),1./n)" else: # odd power code = "pow(w,1./n)" y_expression = Expression(code, w=w, n=n, degree=self.fem.func_degree) y = d.interpolate(y_expression, self.fem.S) elif y_method == 'vector': # this should formally be identical to 'interpolate', but it's a workaround to this # potential FEniCS bug which occurs in the previous code block: # https://bitbucket.org/fenics-project/dolfin/issues/1079/interpolated-expression-gives-wrong-result y = d.Function(self.fem.S) if n == 2: y.vector().set_local(np.sqrt(np.abs(w.vector().get_local()))) elif n == 3: y.vector().set_local(np.cbrt(np.abs(w.vector().get_local()))) else: if n % 2 == 0: # even power y.vector().set_local( np.abs(w.vector().get_local())**(1. / self.fields.n)) else: # odd power y.vector().set_local( w.vector().get_local()**(1. / self.fields.n)) elif y_method == 'project': y = w**(1. / n) y = project(y, self.fem.S, self.fem.func_degree) # we obtain pi by solving Del pi = y piD = Constant(0.) pi_Dirichlet_bc = d.DirichletBC(self.fem.S, piD, boundary, method='pointwise') pi_ = d.TrialFunction(self.fem.S) v_ = d.TestFunction(self.fem.S) pi_a = -inner(grad(pi_), grad(v_)) * r2 * dx pi_L = y * v_ * r2 * dx pi = d.Function(self.fem.S) pi_pde = d.LinearVariationalProblem(pi_a, pi_L, pi, pi_Dirichlet_bc) pi_solver = d.LinearVariationalSolver(pi_pde) pi_solver.solve() # now let's pack pi, w, y into a single function guess = d.Function(self.V) fa = d.FunctionAssigner(self.V, [self.fem.S, self.fem.S, self.fem.S]) fa.assign(guess, [pi, w, y]) return guess
def KG_initial_guess(self): r""" Obtains an initial guess for the Galileon equation of motion by assuming the nonlinear term is subdominant, i.e.: .. math:: \Box\pi - m^2\pi \approx \frac{\rho}{Mp} The initial guess is computed by first solving the system of equations: .. math:: & \hat{\nabla}^2\hat{\pi} = \hat{Y} \\ & \hat{Y} - \left( \frac{m}{M_n} \right)^2\pi = \frac{\hat{\rho}}{M_p} and then obtaining :math:`\hat{W}=\hat{Y}^n` by projection. """ # define a function space for (pi, y) only piy_E = d.MixedElement([self.fem.Pn, self.fem.Pn]) piy_V = d.FunctionSpace(self.fem.mesh.mesh, piy_E) # get the boundary conditions for pi and y only piD, yD = Constant(0.), Constant(0.) def boundary(x): return self.fem.mesh.r_max - x[0] < d.DOLFIN_EPS bc_pi = d.DirichletBC(piy_V.sub(0), piD, boundary, method='pointwise') bc_y = d.DirichletBC(piy_V.sub(1), yD, boundary, method='pointwise') Dirichlet_bc = [bc_pi, bc_y] # Trial functions for pi and y u = d.TrialFunction(piy_V) pi, y = d.split(u) # test functions for the two equations v1, v3 = d.TestFunctions(piy_V) # cast params as constant functions so that, if they are set to 0, FEniCS still understand # what is being integrated m, Mp, Mn, Mf1 = Constant(self.fields.m), Constant( self.fields.Mp), Constant(self.Mn), Constant(self.Mf1) n = self.fields.n # r^2 r2 = Expression('pow(x[0],2)', degree=self.fem.func_degree) # bilinear form a1 = -inner(grad(pi), grad(v1)) * r2 * dx - y * v1 * r2 * dx a3 = y * v3 * r2 * dx - (m / Mn)**2 * pi * v3 * r2 * dx a = a1 + a3 # linear form (L1=0) L3 = self.source.rho / Mp * Mn / Mf1 * v3 * r2 * dx L = L3 # solve system sol = d.Function(piy_V) pde = d.LinearVariationalProblem(a, L, sol, Dirichlet_bc) solver = d.LinearVariationalSolver(pde) print('Getting KG initial guess...') solver.solve() # split solution into pi and y - cast as independent functions, not components of a vector function pi, y = sol.split(deepcopy=True) # obtain w by projecting y**n w = y**n w = project(w, self.fem.S, self.fem.func_degree) # and now pack pi, w, y into one function... guess = d.Function(self.V) # this syntax is because, even though pi and y are effectively defined on fem.S, from fenics point # of view, they are obtained as splits of a function fa = d.FunctionAssigner( self.V, [pi.function_space(), self.fem.S, y.function_space()]) fa.assign(guess, [pi, w, y]) return guess