Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
r""" Differential Forms
Let `U` and `M` be two differentiable manifolds. Given a positive integer `p` and a differentiable map `\Phi: U \rightarrow M`, a *differential form of degree* `p`, or `p`-*form*, *along* `U` *with values on* `M` is a field along `U` of alternating multilinear forms of degree `p` in the tangent spaces to `M`. The standard case of a differential form *on* a differentiable manifold corresponds to `U = M` and `\Phi = \mathrm{Id}_M`. Other common cases are `\Phi` being an immersion and `\Phi` being a curve in `M` (`U` is then an open interval of `\RR`).
Two classes implement differential forms, depending whether the manifold `M` is parallelizable:
* :class:`DiffFormParal` when `M` is parallelizable * :class:`DiffForm` when `M` is not assumed parallelizable.
AUTHORS:
- Eric Gourgoulhon, Michal Bejger (2013, 2014): initial version - Joris Vankerschaver (2010): developed a previous class, ``DifferentialForm`` (cf. :trac:`24444`), which inspired the storage of the non-zero components as a dictionary whose keys are the indices. - Travis Scrimshaw (2016): review tweaks
REFERENCES:
- [KN1963]_ - [Lee2013]_
"""
#****************************************************************************** # Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr> # Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl> # Copyright (C) 2010 Joris Vankerschaver <joris.vankerschaver@gmail.com> # Copyright (C) 2016 Travis Scrimshaw <tscrimsh@umn.edu> # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #******************************************************************************
r""" Differential form with values on a generic (i.e. a priori not parallelizable) differentiable manifold.
Given a differentiable manifold `U`, a differentiable map `\Phi: U \rightarrow M` to a differentiable manifold `M` and a positive integer `p`, a *differential form of degree* `p` (or `p`-*form*) *along* `U` *with values on* `M\supset\Phi(U)` is a differentiable map
.. MATH::
a:\ U \longrightarrow T^{(0,p)}M
(`T^{(0,p)}M` being the tensor bundle of type `(0,p)` over `M`) such that
.. MATH::
\forall x \in U,\quad a(x) \in \Lambda^p(T_{\Phi(x)}^* M) ,
where `T_{\Phi(x)}^* M` is the dual of the tangent space to `M` at `\Phi(x)` and `\Lambda^p` stands for the exterior power of degree `p` (cf. :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerDualFreeModule`). In other words, `a(x)` is an alternating multilinear form of degree `p` of the tangent vector space `T_{\Phi(x)} M`.
The standard case of a differential form *on* a manifold `M` corresponds to `U = M` and `\Phi = \mathrm{Id}_M`. Other common cases are `\Phi` being an immersion and `\Phi` being a curve in `M` (`U` is then an open interval of `\RR`).
.. NOTE::
If `M` is parallelizable, the class :class:`DiffFormParal` must be used instead.
INPUT:
- ``vector_field_module`` -- module `\mathfrak{X}(U,\Phi)` of vector fields along `U` with values on `M` via the map `\Phi` - ``degree`` -- the degree of the differential form (i.e. its tensor rank) - ``name`` -- (default: ``None``) name given to the differential form - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the differential form; if none is provided, the LaTeX symbol is set to ``name``
EXAMPLES:
Differential form of degree 2 on a non-parallelizable 2-dimensional manifold::
sage: M = Manifold(2, 'M') sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # M is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() sage: xy_to_uv = c_xy.transition_map(c_uv, (x+y, x-y), intersection_name='W', ....: restrictions1= x>0, restrictions2= u+v>0) sage: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) sage: eU = c_xy.frame() ; eV = c_uv.frame() sage: a = M.diff_form(2, name='a') ; a 2-form a on the 2-dimensional differentiable manifold M sage: a.parent() Module Omega^2(M) of 2-forms on the 2-dimensional differentiable manifold M sage: a.degree() 2
Setting the components of ``a``::
sage: a[eU,0,1] = x*y^2 + 2*x sage: a.add_comp_by_continuation(eV, W, c_uv) sage: a.display(eU) a = (x*y^2 + 2*x) dx/\dy sage: a.display(eV) a = (-1/16*u^3 + 1/16*u*v^2 - 1/16*v^3 + 1/16*(u^2 - 8)*v - 1/2*u) du/\dv
A 1-form on ``M``::
sage: a = M.one_form('a') ; a 1-form a on the 2-dimensional differentiable manifold M sage: a.parent() Module Omega^1(M) of 1-forms on the 2-dimensional differentiable manifold M sage: a.degree() 1
Setting the components of the 1-form in a consistent way::
sage: a[eU,:] = [-y, x] sage: a.add_comp_by_continuation(eV, W, c_uv) sage: a.display(eU) a = -y dx + x dy sage: a.display(eV) a = 1/2*v du - 1/2*u dv
The exterior derivative of the 1-form is a 2-form::
sage: da = a.exterior_derivative() ; da 2-form da on the 2-dimensional differentiable manifold M sage: da.display(eU) da = 2 dx/\dy sage: da.display(eV) da = -du/\dv
Another 1-form::
sage: b = M.one_form('b') sage: b[eU,:] = [1+x*y, x^2] sage: b.add_comp_by_continuation(eV, W, c_uv)
Adding two 1-forms results in another 1-form::
sage: s = a + b ; s 1-form a+b on the 2-dimensional differentiable manifold M sage: s.display(eU) a+b = ((x - 1)*y + 1) dx + (x^2 + x) dy sage: s.display(eV) a+b = (1/4*u^2 + 1/4*(u + 2)*v + 1/2) du + (-1/4*u*v - 1/4*v^2 - 1/2*u + 1/2) dv
The exterior product of two 1-forms is a 2-form::
sage: s = a.wedge(b) ; s 2-form a/\b on the 2-dimensional differentiable manifold M sage: s.display(eU) a/\b = (-2*x^2*y - x) dx/\dy sage: s.display(eV) a/\b = (1/8*u^3 - 1/8*u*v^2 - 1/8*v^3 + 1/8*(u^2 + 2)*v + 1/4*u) du/\dv
Multiplying a 1-form by a scalar field results in another 1-form::
sage: f = M.scalar_field({c_xy: (x+y)^2, c_uv: u^2}, name='f') sage: s = f*a ; s 1-form on the 2-dimensional differentiable manifold M sage: s.display(eU) (-x^2*y - 2*x*y^2 - y^3) dx + (x^3 + 2*x^2*y + x*y^2) dy sage: s.display(eV) 1/2*u^2*v du - 1/2*u^3 dv
.. RUBRIC:: Examples with SymPy as the symbolic engine
From now on, we ask that all symbolic calculus on manifold `M` are performed by SymPy::
sage: M.set_calculus_method('sympy')
We define a 2-form `a` as above::
sage: a = M.diff_form(2, name='a') sage: a[eU,0,1] = x*y^2 + 2*x sage: a.add_comp_by_continuation(eV, W, c_uv) sage: a.display(eU) a = (x*y**2 + 2*x) dx/\dy sage: a.display(eV) a = (-u**3/16 + u**2*v/16 + u*v**2/16 - u/2 - v**3/16 - v/2) du/\dv
A 1-form on ``M``::
sage: a = M.one_form('a') sage: a[eU,:] = [-y, x] sage: a.add_comp_by_continuation(eV, W, c_uv) sage: a.display(eU) a = -y dx + x dy sage: a.display(eV) a = v/2 du - u/2 dv
The exterior derivative of ``a``::
sage: da = a.exterior_derivative() sage: da.display(eU) da = 2 dx/\dy sage: da.display(eV) da = -du/\dv
Another 1-form::
sage: b = M.one_form('b') sage: b[eU,:] = [1+x*y, x^2] sage: b.add_comp_by_continuation(eV, W, c_uv)
Adding two 1-forms::
sage: s = a + b sage: s.display(eU) a+b = (x*y - y + 1) dx + x*(x + 1) dy sage: s.display(eV) a+b = (u**2/4 + u*v/4 + v/2 + 1/2) du + (-u*v/4 - u/2 - v**2/4 + 1/2) dv
The exterior product of two 1-forms::
sage: s = a.wedge(b) sage: s.display(eU) a/\b = -x*(2*x*y + 1) dx/\dy sage: s.display(eV) a/\b = (u**3/8 + u**2*v/8 - u*v**2/8 + u/4 - v**3/8 + v/4) du/\dv
Multiplying a 1-form by a scalar field::
sage: f = M.scalar_field({c_xy: (x+y)^2, c_uv: u^2}, name='f') sage: s = f*a sage: s.display(eU) -y*(x**2 + 2*x*y + y**2) dx + x*(x**2 + 2*x*y + y**2) dy sage: s.display(eV) u**2*v/2 du - u**3/2 dv
""" r""" Construct a differential form.
TESTS:
Construction via ``parent.element_class``, and not via a direct call to ``DiffForm`, to fit with the category framework::
sage: M = Manifold(2, 'M') sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # M is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() sage: xy_to_uv = c_xy.transition_map(c_uv, (x+y, x-y), ....: intersection_name='W', restrictions1= x>0, ....: restrictions2= u+v>0) sage: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) sage: e_xy = c_xy.frame() ; e_uv = c_uv.frame() sage: A = M.diff_form_module(2) sage: XM = M.vector_field_module() sage: a = A.element_class(XM, 2, name='a'); a 2-form a on the 2-dimensional differentiable manifold M sage: a[e_xy,0,1] = x+y sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: TestSuite(a).run(skip='_test_pickling')
Construction with ``DifferentiableManifold.diff_form``::
sage: a1 = M.diff_form(2, name='a'); a1 2-form a on the 2-dimensional differentiable manifold M sage: type(a1) == type(a) True sage: a1.parent() is a.parent() True
.. TODO::
Fix ``_test_pickling`` (in the superclass :class:`TensorField`).
""" latex_name=latex_name, antisym=range(degree), parent=vector_field_module.dual_exterior_power(degree))
r""" String representation of ``self``.
TESTS::
sage: M = Manifold(3, 'M') sage: a = M.diff_form(2, name='a') sage: a._repr_() '2-form a on the 3-dimensional differentiable manifold M' sage: repr(a) # indirect doctest '2-form a on the 3-dimensional differentiable manifold M' sage: a # indirect doctest 2-form a on the 3-dimensional differentiable manifold M sage: b = M.diff_form(2) sage: b._repr_() '2-form on the 3-dimensional differentiable manifold M'
"""
r""" Create an instance of the same class, of the same degree and on the same domain.
TESTS::
sage: M = Manifold(3, 'M') sage: a = M.diff_form(2, name='a') sage: a1 = a._new_instance(); a1 2-form on the 3-dimensional differentiable manifold M sage: type(a1) == type(a) True sage: a1.parent() is a.parent() True
"""
r""" Delete the derived quantities.
TESTS::
sage: M = Manifold(3, 'M') sage: a = M.diff_form(2, name='a') sage: a._del_derived()
"""
def exterior_derivative(self): r""" Compute the exterior derivative of ``self``.
OUTPUT:
- instance of :class:`DiffForm` representing the exterior derivative of the differential form
EXAMPLES:
Exterior derivative of a 1-form on the 2-sphere::
sage: M = Manifold(2, 'M') # the 2-dimensional sphere S^2 sage: U = M.open_subset('U') # complement of the North pole sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole sage: V = M.open_subset('V') # complement of the South pole sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole sage: M.declare_union(U,V) # S^2 is the union of U and V sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)), ....: intersection_name='W', restrictions1= x^2+y^2!=0, ....: restrictions2= u^2+v^2!=0) sage: uv_to_xy = xy_to_uv.inverse() sage: e_xy = c_xy.frame(); e_uv = c_uv.frame()
The 1-form::
sage: a = M.diff_form(1, name='a') sage: a[e_xy,:] = -y^2, x^2 sage: a.add_comp_by_continuation(e_uv, U.intersection(V), c_uv) sage: a.display(e_xy) a = -y^2 dx + x^2 dy sage: a.display(e_uv) a = -(2*u^3*v - u^2*v^2 + v^4)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du + (u^4 - u^2*v^2 + 2*u*v^3)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) dv
Its exterior derivative::
sage: da = a.exterior_derivative(); da 2-form da on the 2-dimensional differentiable manifold M sage: da.display(e_xy) da = (2*x + 2*y) dx/\dy sage: da.display(e_uv) da = -2*(u + v)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) du/\dv
The result is cached, i.e. is not recomputed unless ``a`` is changed::
sage: a.exterior_derivative() is da True
Instead of invoking the method :meth:`exterior_derivative`, one may use the global function :func:`~sage.manifolds.utilities.exterior_derivative` or its alias :func:`~sage.manifolds.utilities.xder`::
sage: from sage.manifolds.utilities import xder sage: xder(a) is a.exterior_derivative() True
Let us check Cartan's identity::
sage: v = M.vector_field(name='v') sage: v[e_xy, :] = -y, x sage: v.add_comp_by_continuation(e_uv, U.intersection(V), c_uv) sage: a.lie_der(v) == v.contract(xder(a)) + xder(a(v)) # long time True
""" format_unop_latex) latex_name=rlname)
r""" Exterior product with another differential form.
INPUT:
- ``other`` -- another differential form (on the same manifold)
OUTPUT:
- instance of :class:`DiffForm` representing the exterior product ``self/\other``
EXAMPLES:
Exterior product of two 1-forms on the 2-sphere::
sage: M = Manifold(2, 'S^2', start_index=1) # the 2-dimensional sphere S^2 sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # S^2 is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() # stereographic coord. (North and South) sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)), ....: intersection_name='W', restrictions1= x^2+y^2!=0, ....: restrictions2= u^2+v^2!=0) sage: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) # The complement of the two poles sage: e_xy = c_xy.frame() ; e_uv = c_uv.frame() sage: a = M.diff_form(1, name='a') sage: a[e_xy,:] = y, x sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: b = M.diff_form(1, name='b') sage: b[e_xy,:] = x^2 + y^2, y sage: b.add_comp_by_continuation(e_uv, W, c_uv) sage: c = a.wedge(b); c 2-form a/\b on the 2-dimensional differentiable manifold S^2 sage: c.display(e_xy) a/\b = (-x^3 - (x - 1)*y^2) dx/\dy sage: c.display(e_uv) a/\b = -(v^2 - u)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du/\dv
""" raise ValueError("incompatible ambient domains for exterior product") elif other._domain.is_subset(self._domain): if not other._ambient_domain.is_subset(self._ambient_domain): raise ValueError("incompatible ambient domains for exterior product") # call of the FreeModuleAltForm version: return FreeModuleAltForm.wedge(self_r, other_r) # otherwise, the result is created here: sname = '(' + sname + ')' oname = '(' + oname + ')' slname = '(' + slname + ')' olname = '(' + olname + ')' subcodomain=ambient_dom_resu) latex_name=resu_latex_name) other_r._restrictions[dom])
r""" Return the degree of ``self``.
OUTPUT:
- integer `p` such that the differential form is a `p`-form
EXAMPLES::
sage: M = Manifold(3, 'M') sage: a = M.diff_form(2); a 2-form on the 3-dimensional differentiable manifold M sage: a.degree() 2 sage: b = M.diff_form(1); b 1-form on the 3-dimensional differentiable manifold M sage: b.degree() 1
"""
r""" Compute the Hodge dual of the differential form with respect to some metric.
If the differential form is a `p`-form `A`, its *Hodge dual* with respect to a pseudo-Riemannian metric `g` is the `(n-p)`-form `*A` defined by
.. MATH::
*A_{i_1\ldots i_{n-p}} = \frac{1}{p!} A_{k_1\ldots k_p} \epsilon^{k_1\ldots k_p}_{\qquad\ i_1\ldots i_{n-p}}
where `n` is the manifold's dimension, `\epsilon` is the volume `n`-form associated with `g` (see :meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.volume_form`) and the indices `k_1,\ldots, k_p` are raised with `g`.
INPUT:
- ``metric``: a pseudo-Riemannian metric defined on the same manifold as the current differential form; must be an instance of :class:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric`
OUTPUT:
- the `(n-p)`-form `*A`
EXAMPLES:
Hodge dual of a 1-form on the 2-sphere equipped with the standard metric: we first construct `\mathbb{S}^2` and its metric `g`::
sage: M = Manifold(2, 'S^2', start_index=1) sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # S^2 is the union of U and V sage: c_xy.<x,y> = U.chart() ; c_uv.<u,v> = V.chart() # stereographic coord. (North and South) sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)), ....: intersection_name='W', restrictions1= x^2+y^2!=0, ....: restrictions2= u^2+v^2!=0) sage: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) # The complement of the two poles sage: eU = c_xy.frame() ; eV = c_uv.frame() sage: g = M.metric('g') sage: g[eU,1,1], g[eU,2,2] = 4/(1+x^2+y^2)^2, 4/(1+x^2+y^2)^2 sage: g[eV,1,1], g[eV,2,2] = 4/(1+u^2+v^2)^2, 4/(1+u^2+v^2)^2
Then we construct the 1-form and take its Hodge dual w.r.t. `g`::
sage: a = M.one_form(name='a') sage: a[eU,:] = -y, x sage: a.add_comp_by_continuation(eV, W, c_uv) sage: a.display(eU) a = -y dx + x dy sage: a.display(eV) a = -v/(u^4 + 2*u^2*v^2 + v^4) du + u/(u^4 + 2*u^2*v^2 + v^4) dv sage: sa = a.hodge_dual(g); sa 1-form *a on the 2-dimensional differentiable manifold S^2 sage: sa.display(eU) *a = -x dx - y dy sage: sa.display(eV) *a = -u/(u^4 + 2*u^2*v^2 + v^4) du - v/(u^4 + 2*u^2*v^2 + v^4) dv
Instead of calling the method :meth:`hodge_dual` on the differential form, one can invoke the method :meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.hodge_star` of the metric::
sage: a.hodge_dual(g) == g.hodge_star(a) True
For a 1-form and a Riemannian metric in dimension 2, the Hodge dual applied twice is minus the identity::
sage: ssa = sa.hodge_dual(g); ssa 1-form **a on the 2-dimensional differentiable manifold S^2 sage: ssa == -a True
The Hodge dual of the metric volume 2-form is the constant scalar field 1 (considered as a 0-form)::
sage: eps = g.volume_form(); eps 2-form eps_g on the 2-dimensional differentiable manifold S^2 sage: eps.display(eU) eps_g = 4/(x^4 + y^4 + 2*(x^2 + 1)*y^2 + 2*x^2 + 1) dx/\dy sage: eps.display(eV) eps_g = 4/(u^4 + v^4 + 2*(u^2 + 1)*v^2 + 2*u^2 + 1) du/\dv sage: seps = eps.hodge_dual(g); seps Scalar field *eps_g on the 2-dimensional differentiable manifold S^2 sage: seps.display() *eps_g: S^2 --> R on U: (x, y) |--> 1 on V: (u, v) |--> 1
"""
r""" Interior product with a multivector field.
If ``self`` is a differential form `A` of degree `p` and `B` is a multivector field of degree `q\geq p` on the same manifold, the interior product of `A` by `B` is the multivector field `\iota_A B` of degree `q-p` defined by
.. MATH::
(\iota_A B)^{i_1\ldots i_{q-p}} = A_{k_1\ldots k_p} B^{k_1\ldots k_p i_1\ldots i_{q-p}}
.. NOTE::
``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.manifolds.differentiable.tensorfield.TensorField.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.manifolds.differentiable.tensorfield.TensorField.contract`
INPUT:
- ``qvect`` -- multivector field `B` (instance of :class:`~sage.manifolds.differentiable.multivectorfield.MultivectorField`); the degree of `B` must be at least equal to the degree of ``self``
OUTPUT:
- scalar field (case `p=q`) or :class:`~sage.manifolds.differentiable.multivectorfield.MultivectorField` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self``
.. SEEALSO::
:meth:`~sage.manifolds.differentiable.multivectorfield.MultivectorField.interior_product` for the interior product of a multivector field with a differential form
EXAMPLES:
Interior product of a 1-form with a 2-vector field on the 2-sphere::
sage: M = Manifold(2, 'S^2', start_index=1) # the sphere S^2 sage: U = M.open_subset('U') ; V = M.open_subset('V') sage: M.declare_union(U,V) # S^2 is the union of U and V sage: c_xy.<x,y> = U.chart() # stereographic coord. North sage: c_uv.<u,v> = V.chart() # stereographic coord. South sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)), ....: intersection_name='W', restrictions1= x^2+y^2!=0, ....: restrictions2= u^2+v^2!=0) sage: uv_to_xy = xy_to_uv.inverse() sage: W = U.intersection(V) # The complement of the two poles sage: e_xy = c_xy.frame() ; e_uv = c_uv.frame() sage: a = M.one_form(name='a') sage: a[e_xy,:] = y, x sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: b = M.multivector_field(2, name='b') sage: b[e_xy,1,2] = x*y sage: b.add_comp_by_continuation(e_uv, W, c_uv) sage: s = a.interior_product(b); s Vector field i_a b on the 2-dimensional differentiable manifold S^2 sage: s.display(e_xy) i_a b = -x^2*y d/dx + x*y^2 d/dy sage: s.display(e_uv) i_a b = (u^4*v - 3*u^2*v^3)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) d/du + (3*u^3*v^2 - u*v^4)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) d/dv sage: s == a.contract(b) True
Interior product of a 2-form with a 2-vector field::
sage: a = M.diff_form(2, name='a') sage: a[e_xy,1,2] = 4/(x^2+y^2+1)^2 # the standard area 2-form sage: a.add_comp_by_continuation(e_uv, W, c_uv) sage: s = a.interior_product(b); s Scalar field i_a b on the 2-dimensional differentiable manifold S^2 sage: s.display() i_a b: S^2 --> R on U: (x, y) |--> 8*x*y/(x^4 + y^4 + 2*(x^2 + 1)*y^2 + 2*x^2 + 1) on V: (u, v) |--> 8*u*v/(u^4 + v^4 + 2*(u^2 + 1)*v^2 + 2*u^2 + 1)
Some checks::
sage: s == a.contract(0, 1, b, 0, 1) True sage: s.restrict(U) == 2 * a[[e_xy,1,2]] * b[[e_xy,1,2]] True sage: s.restrict(V) == 2 * a[[e_uv,1,2]] * b[[e_uv,1,2]] True
""" raise ValueError("incompatible ambient domains for interior " + "product") elif qvect._domain.is_subset(self._domain): if not qvect._ambient_domain.is_subset(self._ambient_domain): raise ValueError("incompatible ambient domains for interior " + "product") # call of the AlternatingContrTensor version: return AlternatingContrTensor.interior_product(self_r, qvect_r) # Otherwise, the result is created here: # Name of the result sname = '(' + sname + ')' oname = '(' + oname + ')' olname = r'\left(' + olname + r'\right)' # Domain and computation of the result subcodomain=ambient_dom_resu) name=resu_name, latex_name=resu_latex_name) self_r._restrictions[dom].interior_product( qvect_r._restrictions[dom]) # been initialized resu.restrict(chart.domain()).coord_function(chart)
#******************************************************************************
r""" Differential form with values on a parallelizable manifold.
Given a differentiable manifold `U`, a differentiable map `\Phi: U \rightarrow M` to a parallelizable manifold `M` and a positive integer `p`, a *differential form of degree* `p` (or `p`-*form*) *along* `U` *with values on* `M\supset\Phi(U)` is a differentiable map
.. MATH::
a:\ U \longrightarrow T^{(0,p)}M
(`T^{(0,p)}M` being the tensor bundle of type `(0,p)` over `M`) such that
.. MATH::
\forall x \in U,\quad a(x) \in \Lambda^p(T_{\Phi(x)}^* M) ,
where `T_{\Phi(x)}^* M` is the dual of the tangent space to `M` at `\Phi(x)` and `\Lambda^p` stands for the exterior power of degree `p` (cf. :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerDualFreeModule`). In other words, `a(x)` is an alternating multilinear form of degree `p` of the tangent vector space `T_{\Phi(x)} M`.
The standard case of a differential form *on* a manifold `M` corresponds to `U = M` and `\Phi = \mathrm{Id}_M`. Other common cases are `\Phi` being an immersion and `\Phi` being a curve in `M` (`U` is then an open interval of `\RR`).
.. NOTE::
If `M` is not parallelizable, the class :class:`DiffForm` must be used instead.
INPUT:
- ``vector_field_module`` -- free module `\mathfrak{X}(U,\Phi)` of vector fields along `U` with values on `M` via the map `\Phi` - ``degree`` -- the degree of the differential form (i.e. its tensor rank) - ``name`` -- (default: ``None``) name given to the differential form - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the differential form; if none is provided, the LaTeX symbol is set to ``name``
EXAMPLES:
A 2-form on a 4-dimensional manifold::
sage: M = Manifold(4, 'M') sage: c_txyz.<t,x,y,z> = M.chart() sage: a = M.diff_form(2, 'a') ; a 2-form a on the 4-dimensional differentiable manifold M sage: a.parent() Free module Omega^2(M) of 2-forms on the 4-dimensional differentiable manifold M
A differential form is a tensor field of purely covariant type::
sage: a.tensor_type() (0, 2)
It is antisymmetric, its components being :class:`~sage.tensor.modules.comp.CompFullyAntiSym`::
sage: a.symmetries() no symmetry; antisymmetry: (0, 1) sage: a[0,1] = 2 sage: a[1,0] -2 sage: a.comp() Fully antisymmetric 2-indices components w.r.t. Coordinate frame (M, (d/dt,d/dx,d/dy,d/dz)) sage: type(a.comp()) <class 'sage.tensor.modules.comp.CompFullyAntiSym'>
Setting a component with repeated indices to a non-zero value results in an error::
sage: a[1,1] = 3 Traceback (most recent call last): ... ValueError: by antisymmetry, the component cannot have a nonzero value for the indices (1, 1) sage: a[1,1] = 0 # OK, albeit useless sage: a[1,2] = 3 # OK
The expansion of a differential form with respect to a given coframe is displayed via the method :meth:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm.display`::
sage: a.display() # expansion with respect to the default coframe (dt, dx, dy, dz) a = 2 dt/\dx + 3 dx/\dy sage: latex(a.display()) # output for the notebook a = 2 \mathrm{d} t\wedge \mathrm{d} x + 3 \mathrm{d} x\wedge \mathrm{d} y
Differential forms can be added or subtracted::
sage: b = M.diff_form(2) sage: b[0,1], b[0,2], b[0,3] = (1,2,3) sage: s = a + b ; s 2-form on the 4-dimensional differentiable manifold M sage: a[:], b[:], s[:] ( [ 0 2 0 0] [ 0 1 2 3] [ 0 3 2 3] [-2 0 3 0] [-1 0 0 0] [-3 0 3 0] [ 0 -3 0 0] [-2 0 0 0] [-2 -3 0 0] [ 0 0 0 0], [-3 0 0 0], [-3 0 0 0] ) sage: s = a - b ; s 2-form on the 4-dimensional differentiable manifold M sage: s[:] [ 0 1 -2 -3] [-1 0 3 0] [ 2 -3 0 0] [ 3 0 0 0]
An example of 3-form is the volume element on `\RR^3` in Cartesian coordinates::
sage: M = Manifold(3, 'R3', '\RR^3', start_index=1) sage: c_cart.<x,y,z> = M.chart() sage: eps = M.diff_form(3, 'epsilon', r'\epsilon') sage: eps[1,2,3] = 1 # the only independent component sage: eps[:] # all the components are set from the previous line: [[[0, 0, 0], [0, 0, 1], [0, -1, 0]], [[0, 0, -1], [0, 0, 0], [1, 0, 0]], [[0, 1, 0], [-1, 0, 0], [0, 0, 0]]] sage: eps.display() epsilon = dx/\dy/\dz
Spherical components of the volume element from the tensorial change-of-frame formula::
sage: c_spher.<r,th,ph> = M.chart(r'r:[0,+oo) th:[0,pi]:\theta ph:[0,2*pi):\phi') sage: spher_to_cart = c_spher.transition_map(c_cart, ....: [r*sin(th)*cos(ph), r*sin(th)*sin(ph), r*cos(th)]) sage: cart_to_spher = spher_to_cart.set_inverse(sqrt(x^2+y^2+z^2), ....: atan2(sqrt(x^2+y^2),z), atan2(y, x)) sage: eps.comp(c_spher.frame()) # computation of the components in the spherical frame Fully antisymmetric 3-indices components w.r.t. Coordinate frame (R3, (d/dr,d/dth,d/dph)) sage: eps.comp(c_spher.frame())[1,2,3, c_spher] r^2*sin(th) sage: eps.display(c_spher.frame()) epsilon = sqrt(x^2 + y^2 + z^2)*sqrt(x^2 + y^2) dr/\dth/\dph sage: eps.display(c_spher.frame(), c_spher) epsilon = r^2*sin(th) dr/\dth/\dph
The exterior product of two differential forms is performed via the method :meth:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm.wedge`::
sage: a = M.one_form('A') sage: a[:] = (x*y*z, -z*x, y*z) sage: b = M.one_form('B') sage: b[:] = (cos(z), sin(x), cos(y)) sage: ab = a.wedge(b) ; ab 2-form A/\B on the 3-dimensional differentiable manifold R3 sage: ab[:] [ 0 x*y*z*sin(x) + x*z*cos(z) x*y*z*cos(y) - y*z*cos(z)] [-x*y*z*sin(x) - x*z*cos(z) 0 -(x*cos(y) + y*sin(x))*z] [-x*y*z*cos(y) + y*z*cos(z) (x*cos(y) + y*sin(x))*z 0] sage: ab.display() A/\B = (x*y*z*sin(x) + x*z*cos(z)) dx/\dy + (x*y*z*cos(y) - y*z*cos(z)) dx/\dz - (x*cos(y) + y*sin(x))*z dy/\dz
Let us check the formula relating the exterior product to the tensor product for 1-forms::
sage: a.wedge(b) == a*b - b*a True
The tensor product of a 1-form and a 2-form is not a 3-form but a tensor field of type `(0,3)` with less symmetries::
sage: c = a*ab ; c Tensor field A*(A/\B) of type (0,3) on the 3-dimensional differentiable manifold R3 sage: c.symmetries() # the antisymmetry is only w.r.t. the last 2 arguments: no symmetry; antisymmetry: (1, 2) sage: d = ab*a ; d Tensor field (A/\B)*A of type (0,3) on the 3-dimensional differentiable manifold R3 sage: d.symmetries() # the antisymmetry is only w.r.t. the first 2 arguments: no symmetry; antisymmetry: (0, 1)
The exterior derivative of a differential form is obtained by means of the :meth:`exterior_derivative`::
sage: da = a.exterior_derivative() ; da 2-form dA on the 3-dimensional differentiable manifold R3 sage: da.display() dA = -(x + 1)*z dx/\dy - x*y dx/\dz + (x + z) dy/\dz sage: db = b.exterior_derivative() ; db 2-form dB on the 3-dimensional differentiable manifold R3 sage: db.display() dB = cos(x) dx/\dy + sin(z) dx/\dz - sin(y) dy/\dz sage: dab = ab.exterior_derivative() ; dab 3-form d(A/\B) on the 3-dimensional differentiable manifold R3
As a 3-form over a 3-dimensional manifold, ``d(A/\B)`` is necessarily proportional to the volume 3-form::
sage: dab == dab[[1,2,3]]/eps[[1,2,3]]*eps True
We may also check that the classical anti-derivation formula is fulfilled::
sage: dab == da.wedge(b) - a.wedge(db) True
The Lie derivative of a 2-form is a 2-form::
sage: v = M.vector_field('v') sage: v[:] = (y*z, -x*z, x*y) sage: ab.lie_der(v) # long time 2-form on the 3-dimensional differentiable manifold R3
Let us check Cartan formula, which expresses the Lie derivative in terms of exterior derivatives::
sage: ab.lie_der(v) == (v.contract(ab.exterior_derivative()) # long time ....: + v.contract(ab).exterior_derivative()) True
A 1-form on a `\RR^3`::
sage: om = M.one_form('omega', r'\omega') ; om 1-form omega on the 3-dimensional differentiable manifold R3
A 1-form is of course a differential form::
sage: isinstance(om, sage.manifolds.differentiable.diff_form.DiffFormParal) True sage: om.parent() Free module Omega^1(R3) of 1-forms on the 3-dimensional differentiable manifold R3 sage: om.tensor_type() (0, 1)
Setting the components with respect to the manifold's default frame::
sage: om[:] = (2*z, x, x-y) sage: om[:] [2*z, x, x - y] sage: om.display() omega = 2*z dx + x dy + (x - y) dz
A 1-form acts on vector fields::
sage: v = M.vector_field('V') sage: v[:] = (x, 2*y, 3*z) sage: om(v) Scalar field omega(V) on the 3-dimensional differentiable manifold R3 sage: om(v).display() omega(V): R3 --> R (x, y, z) |--> 2*x*y + (5*x - 3*y)*z (r, th, ph) |--> 2*r^2*cos(ph)*sin(ph)*sin(th)^2 + r^2*(5*cos(ph) - 3*sin(ph))*cos(th)*sin(th) sage: latex(om(v)) \omega\left(V\right)
The tensor product of two 1-forms is a tensor field of type `(0,2)`::
sage: a = M.one_form('A') sage: a[:] = (1, 2, 3) sage: b = M.one_form('B') sage: b[:] = (6, 5, 4) sage: c = a*b ; c Tensor field A*B of type (0,2) on the 3-dimensional differentiable manifold R3 sage: c[:] [ 6 5 4] [12 10 8] [18 15 12] sage: c.symmetries() # c has no symmetries: no symmetry; no antisymmetry
""" latex_name=None): r""" Construct a differential form.
TESTS:
Construction via ``parent.element_class``, and not via a direct call to ``DiffFormParal``, to fit with the category framework::
sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() # makes M parallelizable sage: A = M.diff_form_module(2) sage: XM = M.vector_field_module() sage: a = A.element_class(XM, 2, name='a'); a 2-form a on the 2-dimensional differentiable manifold M sage: a[0,1] = x*y sage: TestSuite(a).run()
Construction via ``DifferentiableManifold.diff_form``::
sage: a1 = M.diff_form(2, name='a'); a1 2-form a on the 2-dimensional differentiable manifold M sage: type(a1) == type(a) True sage: a1.parent() is a.parent() True
""" name=name, latex_name=latex_name) # TensorFieldParal attributes: # initialization of derived quantities:
r""" String representation of ``self``.
TESTS::
sage: M = Manifold(3, 'M') sage: X.<x,y,z> = M.chart() # makes M parallelizable sage: a = M.diff_form(2, name='a') sage: a._repr_() '2-form a on the 3-dimensional differentiable manifold M' sage: repr(a) # indirect doctest '2-form a on the 3-dimensional differentiable manifold M' sage: a # indirect doctest 2-form a on the 3-dimensional differentiable manifold M sage: b = M.diff_form(2) sage: b._repr_() '2-form on the 3-dimensional differentiable manifold M'
"""
r""" Create an instance of the same class, of the same degree and on the same domain.
TESTS::
sage: M = Manifold(3, 'M') sage: X.<x,y,z> = M.chart() # makes M parallelizable sage: a = M.diff_form(2, name='a') sage: a1 = a._new_instance(); a1 2-form on the 3-dimensional differentiable manifold M sage: type(a1) == type(a) True sage: a1.parent() is a.parent() True
"""
# This method is needed to redirect to the correct class (TensorFieldParal) r""" Initialize the derived quantities of ``self``.
TESTS::
sage: M = Manifold(3, 'M') sage: X.<x,y,z> = M.chart() # makes M parallelizable sage: a = M.diff_form(2, name='a') sage: a._init_derived()
"""
r""" Delete the derived quantities.
INPUT:
- ``del_restrictions`` -- (default: ``True``) determines whether the restrictions of ``self`` to subdomains are deleted
TESTS::
sage: M = Manifold(3, 'M') sage: X.<x,y,z> = M.chart() # makes M parallelizable sage: a = M.diff_form(2, name='a') sage: a._del_derived()
"""
r""" Redefinition of :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.__call__` to allow for domain treatment.
TESTS::
sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: a = M.diff_form(2, name='a') sage: a[0,1] = x*y sage: a.display() a = x*y dx/\dy sage: u = M.vector_field(name='u') sage: u[:] = [1+x, 2-y] sage: v = M.vector_field(name='v') sage: v[:] = [-y, x] sage: s = a.__call__(u,v); s Scalar field a(u,v) on the 2-dimensional differentiable manifold M sage: s.display() a(u,v): M --> R (x, y) |--> -x*y^3 + 2*x*y^2 + (x^3 + x^2)*y sage: s == a[[0,1]]*(u[[0]]*v[[1]] - u[[1]]*v[[0]]) True sage: s == a(u,v) # indirect doctest True
"""
def exterior_derivative(self): r""" Compute the exterior derivative of ``self``.
OUTPUT:
- a :class:`DiffFormParal` representing the exterior derivative of the differential form
EXAMPLES:
Exterior derivative of a 1-form on a 4-dimensional manifold::
sage: M = Manifold(4, 'M') sage: c_txyz.<t,x,y,z> = M.chart() sage: a = M.one_form('A') sage: a[:] = (t*x*y*z, z*y**2, x*z**2, x**2 + y**2) sage: da = a.exterior_derivative() ; da 2-form dA on the 4-dimensional differentiable manifold M sage: da.display() dA = -t*y*z dt/\dx - t*x*z dt/\dy - t*x*y dt/\dz + (-2*y*z + z^2) dx/\dy + (-y^2 + 2*x) dx/\dz + (-2*x*z + 2*y) dy/\dz sage: latex(da) \mathrm{d}A
The result is cached, i.e. is not recomputed unless ``a`` is changed::
sage: a.exterior_derivative() is da True
Instead of invoking the method :meth:`exterior_derivative`, one may use the global function :func:`~sage.manifolds.utilities.exterior_derivative` or its alias :func:`~sage.manifolds.utilities.xder`::
sage: from sage.manifolds.utilities import xder sage: xder(a) is a.exterior_derivative() True
The exterior derivative is nilpotent::
sage: dda = da.exterior_derivative() ; dda 3-form ddA on the 4-dimensional differentiable manifold M sage: dda.display() ddA = 0 sage: dda == 0 True
Let us check Cartan's identity::
sage: v = M.vector_field(name='v') sage: v[:] = -y, x, t, z sage: a.lie_der(v) == v.contract(xder(a)) + xder(a(v)) # long time True
""" format_unop_latex) name=rname, latex_name=rlname) # 1/ List of all coordinate frames in which the components of self # are known # A coordinate frame is searched, at the price of a change of # frame, privileging the frame of the domain's default chart dom = self._domain def_coordf = dom._def_chart._frame for frame in self._components: if (frame, def_coordf) in dom._frame_changes: self.comp(def_coordf, from_basis=frame) coord_frames = [def_coordf] break if not coord_frames: for chart in dom._atlas: if chart != dom._def_chart: # the case def_chart is # treated above coordf = chart._frame for frame in self._components: if (frame, coordf) in dom._frame_changes: self.comp(coordf, from_basis=frame) coord_frames[coordf] break if coord_frames: break # 2/ The computation: self._tensor_rank+1, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) # all indices are different val.coord_function(chart).diff(i).scalar_field()
r""" Exterior product of ``self`` with another differential form.
INPUT:
- ``other`` -- another differential form
OUTPUT:
- instance of :class:`DiffFormParal` representing the exterior product ``self/\other``
EXAMPLES:
Exterior product of a 1-form and a 2-form on a 3-dimensional manifold::
sage: M = Manifold(3, 'M', start_index=1) sage: X.<x,y,z> = M.chart() sage: a = M.one_form(name='a') sage: a[:] = [2, 1+x, y*z] sage: b = M.diff_form(2, name='b') sage: b[1,2], b[1,3], b[2,3] = y^2, z+x, z^2 sage: a.display() a = 2 dx + (x + 1) dy + y*z dz sage: b.display() b = y^2 dx/\dy + (x + z) dx/\dz + z^2 dy/\dz sage: s = a.wedge(b); s 3-form a/\b on the 3-dimensional differentiable manifold M sage: s.display() a/\b = (-x^2 + (y^3 - x - 1)*z + 2*z^2 - x) dx/\dy/\dz
Check::
sage: s[1,2,3] == a[1]*b[2,3] + a[2]*b[3,1] + a[3]*b[1,2] True
""" raise ValueError("incompatible ambient domains for exterior " + "product") elif other._domain.is_subset(self._domain): if not other._ambient_domain.is_subset(self._ambient_domain): raise ValueError("incompatible ambient domains for exterior " + "product")
r""" Compute the Hodge dual of the differential form with respect to some metric.
If the differential form is a `p`-form `A`, its *Hodge dual* with respect to a pseudo-Riemannian metric `g` is the `(n-p)`-form `*A` defined by
.. MATH::
*A_{i_1\ldots i_{n-p}} = \frac{1}{p!} A_{k_1\ldots k_p} \epsilon^{k_1\ldots k_p}_{\qquad\ i_1\ldots i_{n-p}}
where `n` is the manifold's dimension, `\epsilon` is the volume `n`-form associated with `g` (see :meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.volume_form`) and the indices `k_1,\ldots, k_p` are raised with `g`.
INPUT:
- ``metric``: a pseudo-Riemannian metric defined on the same manifold as the current differential form; must be an instance of :class:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric`
OUTPUT:
- the `(n-p)`-form `*A`
EXAMPLES:
Hodge dual of a 1-form in the Euclidean space `R^3`::
sage: M = Manifold(3, 'M', start_index=1) sage: X.<x,y,z> = M.chart() sage: g = M.metric('g') # the Euclidean metric sage: g[1,1], g[2,2], g[3,3] = 1, 1, 1 sage: a = M.one_form('A') sage: var('Ax Ay Az') (Ax, Ay, Az) sage: a[:] = (Ax, Ay, Az) sage: sa = a.hodge_dual(g) ; sa 2-form *A on the 3-dimensional differentiable manifold M sage: sa.display() *A = Az dx/\dy - Ay dx/\dz + Ax dy/\dz sage: ssa = sa.hodge_dual(g) ; ssa 1-form **A on the 3-dimensional differentiable manifold M sage: ssa.display() **A = Ax dx + Ay dy + Az dz sage: ssa == a # must hold for a Riemannian metric in dimension 3 True
Instead of calling the method :meth:`hodge_dual` on the differential form, one can invoke the method :meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.hodge_star` of the metric::
sage: a.hodge_dual(g) == g.hodge_star(a) True
See the documentation of :meth:`~sage.manifolds.differentiable.metric.PseudoRiemannianMetric.hodge_star` for more examples.
"""
r""" Interior product with a multivector field.
If ``self`` is a differential form `A` of degree `p` and `B` is a multivector field of degree `q\geq p` on the same manifold, the interior product of `A` by `B` is the multivector field `\iota_A B` of degree `q-p` defined by
.. MATH::
(\iota_A B)^{i_1\ldots i_{q-p}} = A_{k_1\ldots k_p} B^{k_1\ldots k_p i_1\ldots i_{q-p}}
.. NOTE::
``A.interior_product(B)`` yields the same result as ``A.contract(0,..., p-1, B, 0,..., p-1)`` (cf. :meth:`~sage.manifolds.differentiable.tensorfield_paral.TensorFieldParal.contract`), but ``interior_product`` is more efficient, the alternating character of `A` being not used to reduce the computation in :meth:`~sage.manifolds.differentiable.tensorfield_paral.TensorFieldParal.contract`
INPUT:
- ``qvect`` -- multivector field `B` (instance of :class:`~sage.manifolds.differentiable.multivectorfield.MultivectorFieldParal`); the degree of `B` must be at least equal to the degree of ``self``
OUTPUT:
- scalar field (case `p=q`) or :class:`~sage.manifolds.differentiable.multivectorfield.MultivectorFieldParal` (case `p<q`) representing the interior product `\iota_A B`, where `A` is ``self``
.. SEEALSO::
:meth:`~sage.manifolds.differentiable.multivectorfield.MultivectorFieldParal.interior_product` for the interior product of a multivector field with a differential form
EXAMPLES:
Interior product of a 1-form with a 2-vector field on a 3-dimensional manifold::
sage: M = Manifold(3, 'M', start_index=1) sage: X.<x,y,z> = M.chart() sage: a = M.one_form(name='a') sage: a[:] = [2, 1+x, y*z] sage: b = M.multivector_field(2, name='b') sage: b[1,2], b[1,3], b[2,3] = y^2, z+x, -z^2 sage: s = a.interior_product(b); s Vector field i_a b on the 3-dimensional differentiable manifold M sage: s.display() i_a b = (-(x + 1)*y^2 - x*y*z - y*z^2) d/dx + (y*z^3 + 2*y^2) d/dy + (-(x + 1)*z^2 + 2*x + 2*z) d/dz sage: s == a.contract(b) True
Interior product of a 2-form with a 2-vector field::
sage: a = M.diff_form(2, name='a') sage: a[1,2], a[1,3], a[2,3] = x*y, -3, z sage: s = a.interior_product(b); s Scalar field i_a b on the 3-dimensional differentiable manifold M sage: s.display() i_a b: M --> R (x, y, z) |--> 2*x*y^3 - 2*z^3 - 6*x - 6*z sage: s == a.contract(0,1,b,0,1) True
""" raise ValueError("incompatible ambient domains for interior " + "product") elif qvect._domain.is_subset(self._domain): if not qvect._ambient_domain.is_subset(self._ambient_domain): raise ValueError("incompatible ambient domains for interior " + "product") |