diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 8df2dc3d..5704a992 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -44,11 +44,22 @@ jobs: run: | pip install --upgrade pip setuptools wheel + - name: Install Graphviz + run: | + sudo apt-get update + sudo apt-get install --yes graphviz + - name: Install project and documentation dependencies run: | pip install .[docs] pip freeze + - name: Run documentation examples + run: | + for example in doc/examples/*.py; do + python "$example" + done + - name: Make the Sphinx documentation run: >- python -m sphinx -W --keep-going diff --git a/CHANGELOG.md b/CHANGELOG.md index 5926d401..19a37036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file. ### Added +- #194: Add `sympde.api` as the supported flat public API. It contains the objects used by PSYDAC, IGA-Python, GeLaTo and the SymPDE documentation. +- #194: Add example scripts to the documentation, which are run by the documentation workflow. +- #194: Draw expression trees in the documentation with Graphviz, generated from the current code at build time. - #106: Accept the connectivity of a `Domain` as an iterable of `Interface` objects, in addition to a `Connectivity` object. - [DEVELOPER] Add a `CHANGELOG.md` file listing changes between different versions. - [DEVELOPER] Add Python version 3.14 to the testing workflow. @@ -15,7 +18,10 @@ All notable changes to this project will be documented in this file. ### Changed -- Convert the README to Markdown and expand the installation, testing, and documentation-build instructions. +- #190: Convert the README to Markdown and expand the installation, testing, and documentation-build instructions. +- #194: Use explicit internal imports and load optional dependencies only when needed. +- #194: Remove the leading underscore from `partial_derivatives`, `logical_partial_derivatives` (`sympde.topology.derivatives`) and `split_test_function` (`sympde.expr.evaluation`), and add them to `sympde.api`. +- #194: List only public names in the `__all__` of every module. - #106: Require SymPy 1.12 or newer and support maintained SymPy releases below 1.15. - #106: Update SymPDE's symbolic objects for modern SymPy's structural arguments, traversal, substitution, and reconstruction requirements. - #106: Order the parameters of `Domain` like its `args` (`name`, `interiors`, `boundaries`, `mapping`, `logical_domain`, `connectivity`) and allow passing them positionally. `dim` is now keyword-only. @@ -23,6 +29,9 @@ All notable changes to this project will be documented in this file. - #106: Store only `axis` and `weight` in the `args` of the 1D forms (`Mass`, `Stiffness`, `Advection`, `AdvectionT`, `Bilaplacian`). Their name is now a class attribute. - [DEVELOPER] Modernize the package metadata and consolidate the pytest and coverage configuration in `pyproject.toml`. - [DEVELOPER] Modernize the Sphinx configuration, API-documentation generation, and API docstring markup. +- [DEVELOPER] Remove the leading underscore from the module-level names used by other SymPDE modules, and rename `_coeffs_registery` to `coefficient_types`. +- [DEVELOPER] Modernize the package metadata and consolidate the pytest and coverage configuration in `pyproject.toml`. +- [DEVELOPER] Modernize the Sphinx configuration, API-documentation generation, and API docstring markup. - [DEVELOPER] Test the minimum supported SymPy version in a dedicated CI job. ### Fixed @@ -32,10 +41,20 @@ All notable changes to this project will be documented in this file. - #106: Preserve symbol assumptions and free-symbol information with modern SymPy. - #106: Keep same-named functions from different function spaces distinct during symbolic simplification. - #106: Fix form-linearity checks involving integral sums with modern SymPy. +- #194: Fix plotting of unmapped two-dimensional domains with `plot_2d()`. +- #106: Preserve symbol assumptions and free-symbol information with modern SymPy. +- #106: Keep same-named functions from different function spaces distinct during symbolic simplification. +- #106: Fix form-linearity checks involving integral sums with modern SymPy. - #106: Raise a `ValueError` when a `Domain` is created without `interiors` or `dim`, instead of failing with a `TypeError` when only a connectivity is given. +- #191: Fix the rendering of the `Domain.join()` API documentation. +- #166: Restore warning-free documentation builds, including bibliography and mathematical notation support. ### Removed +- #194: Remove the re-exports of public objects from the package and subpackage initializers. Replace imports such as `from sympde.topology import Square` with `from sympde.api import Square`. `from sympde import __version__` keeps working. +- #194: Remove the `sympde.old_sympy_utilities` module. Use `sympy.utilities.iterables.is_sequence` and Python's `metaclass` keyword instead of its `is_sequence` and `with_metaclass`. +- #194: Remove the outdated Jupyter notebooks and the Binder badge. The example scripts in the documentation replace them. +- #194: Remove the unused `_is_op_test_function` from `sympde.calculus.core`. - [DEVELOPER] Remove obsolete CI configuration, the standalone pytest configuration, and the legacy test runner script. ### Deprecated diff --git a/README.md b/README.md index 52f973c8..2a932258 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # SymPDE [![CI status](https://github.com/pyccel/sympde/actions/workflows/testing.yml/badge.svg?branch=master&event=push)](https://github.com/pyccel/sympde/actions/workflows/testing.yml) -[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/pyccel/sympde/master) [![Documentation Status](https://github.com/pyccel/sympde/actions/workflows/documentation.yml/badge.svg)](https://github.com/pyccel/sympde/actions/workflows/documentation.yml) **SymPDE** is a symbolic calculus library for partial differential equations and variational forms. @@ -68,6 +67,17 @@ To check out a specific branch, tag, or commit named ``, run `git checkout pip install --editable ".[test]" ``` +### Public API + +User-facing objects can be imported from the flat, explicit API without +knowing which internal module defines them: + +```python +from sympde.api import Cube, Mapping, Constant +from sympde.api import ScalarFunctionSpace, VectorFunctionSpace, elements_of +from sympde.api import BilinearForm, LinearForm, Norm +``` + ### Running the tests The complete test suite can be run from any directory with: @@ -77,7 +87,9 @@ pytest -n auto --dist loadgroup --pyargs sympde -ra ``` The documentation dependencies are installed separately, and the HTML pages -are built with warnings treated as errors: +are built with warnings treated as errors. Drawing the expression trees also +requires [Graphviz](https://graphviz.org) (for example `sudo apt install +graphviz` on Ubuntu): ```bash pip install --editable ".[docs]" diff --git a/doc/advanced.rst b/doc/advanced.rst index 1f4c96a5..98d01d6d 100644 --- a/doc/advanced.rst +++ b/doc/advanced.rst @@ -1,39 +1,34 @@ Advanced expression manipulation ******************************** -In this section, we shall present different different features for manipulating expressions such us finding the Kronecker representation for bilinear forms. +Tensor-product representation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Tensorization -^^^^^^^^^^^^^ +``TensorExpr`` lowers an integrated form and expresses it through one- +dimensional symbolic factors. This is useful to inspect the Kronecker +structure consumed by tensor-product discretizations. -Assume we have the following bilinear form for Laplace +.. literalinclude:: examples/tensorization.py + :language: python -.. code-block:: python +The example is a complete script, which the documentation workflow runs +before Sphinx builds the page. - from sympde import grad, dot - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - from sympde.core import tensorize +Logical expressions +^^^^^^^^^^^^^^^^^^^ - V = FunctionSpace('V', ldim=2) - U = FunctionSpace('U', ldim=2) +``LogicalExpr`` pulls expressions on a mapped physical domain back to its +logical domain. The analytical-mapping example demonstrates symbolic and +numerical use of this transformation: - v = TestFunction(V, name='v') - u = TestFunction(U, name='u') +.. literalinclude:: examples/analytical_mappings.py + :language: python - a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) - print(tensorize(a)) +Nonlinear forms +^^^^^^^^^^^^^^^ -the result is then +``linearize`` differentiates a nonlinear residual with respect to a field and +returns its symbolic Jacobian form: -.. code-block:: python - - >>> Mass(v1,u1)xMass(v0,u0) + Mass(v1,u1)xStiffness(v0,u0) + Stiffness(v1,u1)xMass(v0,u0) - -which is what we expect, when splitting the integrals with respect to the two directions. -Printing the corresponding latex code gives - -.. math:: - - {\int_{0}^{1} u_{1} v_{1} dy}\otimes {\int_{0}^{1} u_{0} v_{0} dx} + {\int_{0}^{1} u_{1} v_{1} dy}\otimes {\int_{0}^{1} u_{0}^\prime v_{0}^\prime dx} + {\int_{0}^{1} u_{1}^\prime v_{1}^\prime dy}\otimes {\int_{0}^{1} u_{0} v_{0} dx} +.. literalinclude:: examples/nonlinear_poisson.py + :language: python diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 00000000..8352f4a0 --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,19 @@ +Public API +========== + +The :mod:`sympde.api` package provides the supported flat namespace for +user-facing objects. For example: + +.. code-block:: python + + from sympde.api import Cube, Mapping, Constant + from sympde.api import ScalarFunctionSpace, elements_of + from sympde.api import BilinearForm, LinearForm, Norm + +The complete namespace is listed below. These objects are also documented and +indexed at their defining modules, so this page does not create duplicate +cross-reference targets. + +.. automodule:: sympde.api + :members: + :no-index: diff --git a/doc/conf.py b/doc/conf.py index 24786ce0..370977ce 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,8 +1,11 @@ """Sphinx configuration for the SymPDE documentation.""" +import runpy from pathlib import Path +from docutils.statemachine import StringList from sphinx.ext.apidoc import main as sphinx_apidoc +from sphinx.ext.graphviz import Graphviz from sympde import __version__ @@ -44,6 +47,7 @@ 'sphinx.ext.todo', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', + 'sphinx.ext.graphviz', 'sphinxcontrib.bibtex', ] @@ -182,6 +186,9 @@ bibtex_bibfiles = ['math/refs_feec.bib'] +# Draw graphs as SVG, so that they scale with the page +graphviz_output_format = 'svg' + # -- APIDOC ---------------------------------------------- doc_dir = Path(__file__).resolve().parent @@ -197,4 +204,26 @@ str(doc_dir.parent / 'sympde'), ] apidoc_args.extend(str(path) for path in (doc_dir.parent / 'sympde').glob('*/tests')) +apidoc_args.append(str(doc_dir.parent / 'sympde' / 'api')) sphinx_apidoc(apidoc_args) + + +# -- Expression trees ------------------------------------ +# Run the example and draw its graphs with Graphviz. They are generated at +# build time, so they always match the code. +trees = runpy.run_path(str(doc_dir / 'examples' / 'expression_trees.py')) + + +class ExpressionTree(Graphviz): + """Draw a graph of the expression-tree example, given by its name.""" + required_arguments = 1 + optional_arguments = 0 + + def run(self): + graph = trees['graphs'][self.arguments.pop()] + self.content = StringList(graph.splitlines()) + return super().run() + + +def setup(app): + app.add_directive('expression-tree', ExpressionTree) diff --git a/doc/examples/analytical_mappings.py b/doc/examples/analytical_mappings.py new file mode 100644 index 00000000..858b7587 --- /dev/null +++ b/doc/examples/analytical_mappings.py @@ -0,0 +1,47 @@ +"""Work with built-in and user-defined analytical mappings.""" + +import numpy as np +from sympy import pi, sin, symbols + +from sympde.api import CollelaMapping2D, Constant, Domain, LogicalExpr, Mapping + + +# Inspect a built-in mapping symbolically. +mapping = CollelaMapping2D('M', dim=2) +mapped_domain = mapping(Domain('Omega', dim=2)) +x1, x2 = symbols('x1, x2', real=True) +eps, k1, k2 = [Constant(name) for name in ('eps', 'k1', 'k2')] + +expected_x = 2.0 * ( + x1 + eps * sin(2.0 * pi * k1 * x1) * sin(2.0 * pi * k2 * x2) +) - 1.0 +assert LogicalExpr(mapping[0], mapped_domain) == expected_x + + +# Fix its parameters and evaluate it numerically. +numerical_mapping = CollelaMapping2D( + 'M_numeric', + dim=2, + eps=0.1, + k1=1.0, + k2=1.0, +).get_callable_mapping() +np.testing.assert_allclose(numerical_mapping(0.25, 0.5), (-0.5, 0.0)) + + +# Define a custom mapping by specifying its coordinate expressions. +class ComplexSquareMapping(Mapping): + """ + Map (x1, x2) to (x, y) such that x + i*y = A * (x1 + i*x2)**2. + + Expanding the square gives x = A * (x1**2 - x2**2) and y = 2 * A * x1 * x2. + """ + + _expressions = { + 'x': 'A * (x1**2 - x2**2)', + 'y': '2 * A * x1 * x2', + } + + +custom_mapping = ComplexSquareMapping('F', dim=2, A=1.0).get_callable_mapping() +np.testing.assert_allclose(custom_mapping(0.5, 0.25), (0.1875, 0.25)) diff --git a/doc/examples/differential_calculus.py b/doc/examples/differential_calculus.py new file mode 100644 index 00000000..a7e54cd9 --- /dev/null +++ b/doc/examples/differential_calculus.py @@ -0,0 +1,40 @@ +"""Verify representative symbolic vector-calculus identities.""" + +from sympy import expand + +from sympde.api import Constant, Domain, ScalarFunctionSpace, VectorFunctionSpace +from sympde.api import curl, div, elements_of, grad, laplace + + +domain = Domain('Omega', dim=3) +scalar_space = ScalarFunctionSpace('V', domain) +vector_space = VectorFunctionSpace('W', domain) + +alpha, beta = [Constant(name) for name in ('alpha', 'beta')] +f, g, h = elements_of(scalar_space, names='f, g, h') +field_f, field_g, field_h = elements_of(vector_space, names='F, G, H') + +# Gradient linearity and product rules. +assert grad(f + g) == grad(f) + grad(g) +assert grad(alpha * f + beta * g) == alpha * grad(f) + beta * grad(g) +assert grad(f * g) == f * grad(g) + g * grad(f) +assert grad(f / g) == -f * grad(g) / g**2 + grad(f) / g +assert expand(grad(f * g * h)) == ( + f * g * grad(h) + f * h * grad(g) + g * h * grad(f) +) + +# Linearity of curl, Laplace, and divergence operators. +assert curl(field_f + field_g) == curl(field_f) + curl(field_g) +assert curl(alpha * field_f + beta * field_g) == ( + alpha * curl(field_f) + beta * curl(field_g) +) +assert laplace(f + g) == laplace(f) + laplace(g) +assert laplace(alpha * f + beta * g) == alpha * laplace(f) + beta * laplace(g) +assert div(field_f + field_g) == div(field_f) + div(field_g) +assert div(alpha * field_f + beta * field_g) == ( + alpha * div(field_f) + beta * div(field_g) +) + +# Exact-sequence identities. +assert curl(grad(h)) == 0 +assert div(curl(field_h)) == 0 diff --git a/doc/examples/expression_trees.py b/doc/examples/expression_trees.py new file mode 100644 index 00000000..67fd3cd8 --- /dev/null +++ b/doc/examples/expression_trees.py @@ -0,0 +1,34 @@ +"""Write the expression trees of symbolic expressions in the DOT language.""" + +from sympy.printing.dot import dotprint + +from sympde.api import Domain, ScalarFunctionSpace, div, dx, dy, elements_of, grad + + +domain = Domain('Omega', dim=2) +space = ScalarFunctionSpace('V', domain) +u, v = elements_of(space, names='u, v') + + +# [operator-start] +def L(w): + return -div(grad(w)) + w + + +operator_graph = dotprint(L(u)) +# [operator-end] + + +# [derivative-start] +expr = dx(dy(u * v)) +assert expr == u * dx(dy(v)) + v * dx(dy(u)) + dx(u) * dy(v) + dx(v) * dy(u) + +derivative_graph = dotprint(expr) +# [derivative-end] + + +# Graphs drawn in the documentation, by file name +graphs = { + 'operator': operator_graph, + 'derivative': derivative_graph, +} diff --git a/doc/examples/exterior_calculus.py b/doc/examples/exterior_calculus.py new file mode 100644 index 00000000..8e2e1579 --- /dev/null +++ b/doc/examples/exterior_calculus.py @@ -0,0 +1,48 @@ +"""Apply exterior-calculus operators to symbolic differential forms.""" + +from sympy import Symbol + +from sympde.api import Constant, DifferentialForm, Domain, VectorFunctionSpace +from sympde.api import d, delta, element_of, hodge, ip, jp, latex, ld, wedge + + +dimension = Symbol('n') +alpha = Constant('alpha') + +u_0, v_0 = [ + DifferentialForm(name, index=0, dim=dimension) + for name in ('u_0', 'v_0') +] +u_1, v_1 = [ + DifferentialForm(name, index=1, dim=dimension) + for name in ('u_1', 'v_1') +] +u_2 = DifferentialForm('u_2', index=2, dim=dimension) +u_n = DifferentialForm('u_n', index=dimension, dim=dimension) + +# The exterior derivative is linear, nilpotent, and zero on top forms. +assert d(d(u_0)) == 0 +assert d(u_0 + v_0) == d(u_0) + d(v_0) +assert d(alpha * u_0 + v_0) == alpha * d(u_0) + d(v_0) +assert d(u_n) == 0 + +# Its adjoint is linear and zero on zero-forms. +assert delta(u_0) == 0 +assert delta(u_1 + v_1) == delta(u_1) + delta(v_1) +assert delta(alpha * u_1 + v_1) == alpha * delta(u_1) + delta(v_1) + +domain = Domain('Omega', dim=3) +vector_space = VectorFunctionSpace('W', domain) +beta = element_of(vector_space, name='beta') + +expressions = ( + d(u_1) + u_2, + wedge(d(u_1), u_2), + hodge(u_1), + ip(beta, u_1), + jp(beta, u_1), + ld(beta, u_1), +) + +for expression in expressions: + print(latex(expression)) diff --git a/doc/examples/index.rst b/doc/examples/index.rst new file mode 100644 index 00000000..08fcf281 --- /dev/null +++ b/doc/examples/index.rst @@ -0,0 +1,67 @@ +Examples +======== + +These examples are complete Python scripts that can be run on their own. The +documentation workflow runs all of them before building the HTML pages, +ensuring that their imports and demonstrated APIs remain current. + +Variational forms +----------------- + +This example constructs a scalar Poisson problem and a Nitsche-type form on a +two-patch domain. + +.. literalinclude:: variational_forms.py + :language: python + :linenos: + +Differential calculus +--------------------- + +This example demonstrates the currently supported symbolic vector-calculus +identities. + +.. literalinclude:: differential_calculus.py + :language: python + :linenos: + +Analytical mappings +------------------- + +This example evaluates a built-in analytical mapping and defines a custom +mapping from coordinate expressions. + +.. literalinclude:: analytical_mappings.py + :language: python + :linenos: + +Exterior calculus +----------------- + +This example demonstrates differential forms, exterior derivatives, adjoint +operators, products, contractions, and Lie derivatives. + +.. literalinclude:: exterior_calculus.py + :language: python + :linenos: + +Expression trees +---------------- + +This example writes the expression trees of a differential operator and of an +expanded derivative in the DOT language of Graphviz. The quickstart guide draws +both trees. + +.. literalinclude:: expression_trees.py + :language: python + :linenos: + +Linearization of nonlinear problems +----------------------------------- + +This example constructs a nonlinear Poisson residual and derives its Jacobian +form symbolically. + +.. literalinclude:: nonlinear_poisson.py + :language: python + :linenos: diff --git a/doc/examples/nonlinear_poisson.py b/doc/examples/nonlinear_poisson.py new file mode 100644 index 00000000..f169fbe0 --- /dev/null +++ b/doc/examples/nonlinear_poisson.py @@ -0,0 +1,25 @@ +"""Linearize the residual of a nonlinear Poisson problem.""" + +from sympy import exp + +from sympde.api import BilinearForm, Domain, LinearForm, ScalarFunctionSpace +from sympde.api import dot, elements_of, grad, integral, linearize + + +domain = Domain('Omega', dim=2) +space = ScalarFunctionSpace('V', domain) +field, increment, test = elements_of(space, names='u, delta_u, v') + + +residual = LinearForm( + test, + integral(domain, dot(grad(test), grad(field)) - 4.0 * exp(-field) * test), +) + +jacobian = linearize(residual, field, trials=increment) +assert isinstance(jacobian, BilinearForm) +assert jacobian(increment, test) == integral( + domain, + dot(grad(test), grad(increment)) + + 4.0 * exp(-field) * increment * test +) diff --git a/doc/examples/quickstart.py b/doc/examples/quickstart.py new file mode 100644 index 00000000..528ac64f --- /dev/null +++ b/doc/examples/quickstart.py @@ -0,0 +1,50 @@ +"""Build a scalar Poisson problem with the current public API.""" + +from sympy import cos + +from sympde.api import BilinearForm, LinearForm, ScalarFunctionSpace, Square +from sympde.api import TerminalExpr, div, dot, elements_of, find, grad +from sympde.api import integral, latex + + +# [domains-start] +domain = Square('Omega') +space = ScalarFunctionSpace('V', domain) +trial, test = elements_of(space, names='u, v') + +strong_expression = -div(grad(trial)) + trial +assert strong_expression.has(trial) +# [domains-end] + + +# [forms-start] +bilinear_form = BilinearForm( + (trial, test), + integral(domain, dot(grad(trial), grad(test)) + trial * test), +) + +x, y = domain.coordinates +linear_form = LinearForm(test, integral(domain, cos(x + y) * test)) + +equation = find( + trial, + forall=test, + lhs=bilinear_form(trial, test), + rhs=linear_form(test), +) +# [forms-end] + + +# [lowering-start] +coordinate_expression = TerminalExpr( + dot(grad(trial), grad(test)), + domain, +) +assert coordinate_expression.has(trial, test) +# [lowering-end] + + +# [printing-start] +latex_equation = latex(equation) +print(latex_equation) +# [printing-end] diff --git a/doc/examples/tensorization.py b/doc/examples/tensorization.py new file mode 100644 index 00000000..e7ee963b --- /dev/null +++ b/doc/examples/tensorization.py @@ -0,0 +1,21 @@ +"""Expose the tensor-product structure of a scalar bilinear form.""" + +from sympde.api import BilinearForm, Constant, Domain, ScalarFunctionSpace +from sympde.api import TensorExpr, dot, elements_of, grad, integral + + +domain = Domain('Omega', dim=2) +space = ScalarFunctionSpace('V', domain) +trial, test = elements_of(space, names='u, v') +coefficient = Constant('mu', real=True) + +form = BilinearForm( + (trial, test), + integral( + domain, + coefficient * trial * test + dot(grad(trial), grad(test)), + ), +) + +tensor_expression = TensorExpr(form, domain=domain) +print(tensor_expression) diff --git a/doc/examples/variational_forms.py b/doc/examples/variational_forms.py new file mode 100644 index 00000000..b2f2b938 --- /dev/null +++ b/doc/examples/variational_forms.py @@ -0,0 +1,59 @@ +"""Construct scalar variational forms on single- and multi-patch domains.""" + +from sympy import cos + +from sympde.api import BilinearForm, Constant, Domain, LinearForm, ScalarFunctionSpace +from sympde.api import Square, Dn, dot, elements_of, find, grad, integral +from sympde.api import jump, latex, minus, plus + + +# A Poisson problem on a single square. +domain = Square('Omega') +space = ScalarFunctionSpace('V', domain) +trial, test = elements_of(space, names='u, v') + +bilinear_form = BilinearForm( + (trial, test), + integral(domain, dot(grad(trial), grad(test))), +) + +x, y = domain.coordinates +linear_form = LinearForm(test, integral(domain, cos(x + y) * test)) +equation = find( + trial, + forall=test, + lhs=bilinear_form(trial, test), + rhs=linear_form(test), +) + +print(latex(equation)) + + +# A Nitsche-type interface contribution on two joined patches. +patch_a = Square('A') +patch_b = Square('B') +domain = Domain.join( + [patch_a, patch_b], + [((0, 0, 1), (1, 0, -1), 1)], + 'Omega', +) + +space = ScalarFunctionSpace('V', domain) +trial, test = elements_of(space, names='u, v') +interface = domain.interfaces +kappa = Constant('kappa') + +interface_integrand = ( + -jump(trial) * jump(Dn(test)) + + kappa * jump(trial) * jump(test) + + plus(Dn(trial)) * minus(test) + + minus(Dn(trial)) * plus(test) +) + +multi_patch_form = BilinearForm( + (trial, test), + integral(domain, dot(grad(trial), grad(test))) + + integral(interface, interface_integrand), +) + +print(latex(multi_patch_form)) diff --git a/doc/include/graph.py b/doc/include/graph.py deleted file mode 100644 index 84e1a5c9..00000000 --- a/doc/include/graph.py +++ /dev/null @@ -1,234 +0,0 @@ -# coding: utf-8 - -import os - -from sympy import Symbol -from sympy.core.containers import Tuple -from sympy import symbols -from sympy import IndexedBase -from sympy import Matrix -from sympy import Function -from sympy import pi, cos, sin, exp -from sympy import srepr -from sympy.physics.quantum import TensorProduct -from sympy.printing.dot import dotprint - -from sympde.core import Constant -from sympde.calculus import grad, dot, inner, cross, rot, curl, div -from sympde.calculus import laplace, hessian, bracket, convect -from sympde.topology import (dx, dy, dz) -from sympde.topology import FunctionSpace, VectorFunctionSpace -from sympde.topology import Field, VectorField -from sympde.topology import ProductSpace -from sympde.topology import TestFunction -from sympde.topology import VectorTestFunction -from sympde.topology import Unknown -from sympde.topology import InteriorDomain, Union -from sympde.topology import Boundary, NormalVector, TangentVector -from sympde.topology import Domain -from sympde.topology import Trace, trace_0, trace_1 -from sympde.topology import Mapping -from sympde.topology import Square -from sympde.topology import ElementDomain -from sympde.topology import Area - -from sympde.expr.expr import LinearExpr, BilinearExpr -from sympde.expr.expr import LinearForm, BilinearForm -from sympde.expr.expr import DomainIntegral, BoundaryIntegral -from sympde.expr.expr import Functional, Norm -from sympde.expr.expr import linearize -from sympde.expr.evaluation import TerminalExpr - - -#============================================================================== -def mkdir_p(dir): - if os.path.isdir(dir): - return - os.makedirs(dir) - - -#============================================================================== -def export(expr, fname): - """saves the graph as a png/svg file. the extension is eitheir png or svg""" - - name, ext = os.path.splitext(fname) - ext = ext[1:] - - graph = dotprint(expr) - f = open('{name}.dot'.format(name=name), 'w') - f.write(graph) - f.close() - - cmd = "dot -T{ext} {name}.dot -o{name}.{ext}".format(name=name, ext=ext) - os.system(cmd) - -#============================================================================== -def test_linear_form_2d_1(): - - domain = Domain('Omega', dim=2) - B1 = Boundary(r'\Gamma_1', domain) - - x,y = domain.coordinates - - kappa = Constant('kappa', is_real=True) - mu = Constant('mu' , is_real=True) - eps = Constant('eps', real=True) - - V = FunctionSpace('V', domain) - - u,u1,u2 = [TestFunction(V, name=i) for i in ['u', 'u1', 'u2']] - v,v1,v2 = [TestFunction(V, name=i) for i in ['v', 'v1', 'v2']] - - g = Tuple(x**2, y**2) - - # ... - d_forms = {} - - d_forms['l1'] = LinearForm(v, x*y*v) - d_forms['l2'] = LinearForm(v, v*trace_1(g, B1)) - d_forms['l3'] = LinearForm(v, v*trace_1(g, B1) + x*y*v) - - # nitsche - f = Function('f') - g = Function('g') - - l0 = LinearForm(v, f(x,y)*v) - l_B1 = LinearForm(v, g(x,y)*trace_0(v, B1)/eps - - kappa * g(x,y) * trace_1(grad(v), B1)) - - d_forms['l4'] = LinearForm(v, l0(v) + l_B1(v)) - # ... - - # ... - for name, expr in d_forms.items(): - export(expr, 'linform_2d_{}.png'.format(name)) - # ... - -#============================================================================== -def test_bilinear_form_2d_1(): - - domain = Domain('Omega', dim=2) - B1 = Boundary(r'\Gamma_1', domain) - - x,y = domain.coordinates - - kappa = Constant('kappa', is_real=True) - mu = Constant('mu' , is_real=True) - eps = Constant('eps', real=True) - - V = FunctionSpace('V', domain) - - u,u1,u2 = [TestFunction(V, name=i) for i in ['u', 'u1', 'u2']] - v,v1,v2 = [TestFunction(V, name=i) for i in ['v', 'v1', 'v2']] - - # ... - d_forms = {} - - d_forms['a1'] = BilinearForm((u,v), u*v) - d_forms['a2'] = BilinearForm((u,v), u*v + dot(grad(u), grad(v))) - d_forms['a3'] = BilinearForm((u,v), v*trace_1(grad(u), B1)) - - - # Poisson with Nitsch method - a0 = BilinearForm((u,v), dot(grad(u),grad(v))) - a_B1 = BilinearForm((u,v), - kappa * u*trace_1(grad(v), B1) - - v*trace_1(grad(u), B1) - + trace_0(u, B1) * trace_0(v, B1) / eps) - a = BilinearForm((u,v), a0(u,v) + a_B1(u,v)) - - d_forms['a4'] = a - # ... - - # ... calls - d_calls = {} - for name, a in d_forms.items(): - d_calls[name] = a(u1,v1) - # ... - - # ... export forms - for name, expr in d_forms.items(): - export(expr, 'biform_2d_{}.png'.format(name)) - # ... - - # ... export calls - for name, expr in d_calls.items(): - export(expr, 'biform_2d_call_{}.png'.format(name)) - # ... - - -#============================================================================== -# stokes -def test_bilinear_form_2d_3(): - - domain = Domain('Omega', dim=2) - - x,y = domain.coordinates - - V = VectorFunctionSpace('V', domain) - W = FunctionSpace('W', domain) - - v = VectorTestFunction(V, name='v') - u = VectorTestFunction(V, name='u') - p = TestFunction(W, name='p') - q = TestFunction(W, name='q') - - a = BilinearForm((u,v), inner(grad(v), grad(u))) - b = BilinearForm((v,p), div(v)*p) - A = BilinearForm(((u,p),(v,q)), a(v,u) - b(v,p) + b(u,q)) - - export(A, 'stokes_2d.png') - -#============================================================================== -def test_linearize_form_2d_3(): - """steady Euler equation.""" - domain = Domain('Omega', dim=2) - x,y = domain.coordinates - - U = VectorFunctionSpace('U', domain) - W = FunctionSpace('W', domain) - - v = VectorTestFunction(U, name='v') - phi = TestFunction(W, name='phi') - q = TestFunction(W, name='q') - - U_0 = VectorField(U, name='U_0') - Rho_0 = Field(W, name='Rho_0') - P_0 = Field(W, name='P_0') - - # ... - expr = div(Rho_0*U_0) * phi - l1 = LinearForm(phi, expr) - - expr = Rho_0*dot(convect(U_0, grad(U_0)), v) + dot(grad(P_0), v) - l2 = LinearForm(v, expr) - - expr = dot(U_0, grad(P_0)) * q + P_0 * div(U_0) * q - l3 = LinearForm(q, expr) - # ... - - a1 = linearize(l1, [Rho_0, U_0], trials=['d_rho', 'd_u']) - print(a1) - print('') - - a2 = linearize(l2, [Rho_0, U_0, P_0], trials=['d_rho', 'd_u', 'd_p']) - print(a2) - print('') - - a3 = linearize(l3, [P_0, U_0], trials=['d_p', 'd_u']) - print(a3) - print('') - - l = LinearForm((phi, v, q), l1(phi) + l2(v) + l3(q)) - a = linearize(l, [Rho_0, U_0, P_0], trials=['d_rho', 'd_u', 'd_p']) - print(a) - - export(a, 'steady_euler.png') - -############################################ -if __name__ == '__main__': - -# test_linear_form_2d_1() -# test_bilinear_form_2d_1() -# test_bilinear_form_2d_3() - - test_linearize_form_2d_3() diff --git a/doc/include/graphs/biform_2d_a1.png b/doc/include/graphs/biform_2d_a1.png deleted file mode 100644 index b94db10f..00000000 Binary files a/doc/include/graphs/biform_2d_a1.png and /dev/null differ diff --git a/doc/include/graphs/biform_2d_a2.png b/doc/include/graphs/biform_2d_a2.png deleted file mode 100644 index 9f0ab50a..00000000 Binary files a/doc/include/graphs/biform_2d_a2.png and /dev/null differ diff --git a/doc/include/graphs/biform_2d_a3.png b/doc/include/graphs/biform_2d_a3.png deleted file mode 100644 index e4d2ed94..00000000 Binary files a/doc/include/graphs/biform_2d_a3.png and /dev/null differ diff --git a/doc/include/graphs/biform_2d_a4.png b/doc/include/graphs/biform_2d_a4.png deleted file mode 100644 index b1e33923..00000000 Binary files a/doc/include/graphs/biform_2d_a4.png and /dev/null differ diff --git a/doc/include/graphs/linform_2d_l1.png b/doc/include/graphs/linform_2d_l1.png deleted file mode 100644 index 18ba4dc0..00000000 Binary files a/doc/include/graphs/linform_2d_l1.png and /dev/null differ diff --git a/doc/include/graphs/linform_2d_l2.png b/doc/include/graphs/linform_2d_l2.png deleted file mode 100644 index b313169c..00000000 Binary files a/doc/include/graphs/linform_2d_l2.png and /dev/null differ diff --git a/doc/include/graphs/linform_2d_l3.png b/doc/include/graphs/linform_2d_l3.png deleted file mode 100644 index 05b07473..00000000 Binary files a/doc/include/graphs/linform_2d_l3.png and /dev/null differ diff --git a/doc/include/graphs/stokes_2d.png b/doc/include/graphs/stokes_2d.png deleted file mode 100644 index 42807c4a..00000000 Binary files a/doc/include/graphs/stokes_2d.png and /dev/null differ diff --git a/doc/include/quick/graph1.png b/doc/include/quick/graph1.png deleted file mode 100644 index 236cd273..00000000 Binary files a/doc/include/quick/graph1.png and /dev/null differ diff --git a/doc/include/quick/graph2.png b/doc/include/quick/graph2.png deleted file mode 100644 index 92e1ca2c..00000000 Binary files a/doc/include/quick/graph2.png and /dev/null differ diff --git a/doc/include/quick/graph_laplace.png b/doc/include/quick/graph_laplace.png deleted file mode 100644 index 7f493d57..00000000 Binary files a/doc/include/quick/graph_laplace.png and /dev/null differ diff --git a/doc/index.rst b/doc/index.rst index 4f56451d..6f19fd85 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -12,6 +12,7 @@ Welcome to sympde's documentation! quick advanced + examples/index Mathematical background ======================= @@ -28,6 +29,7 @@ API .. toctree:: :maxdepth: 1 + api source/modules Indices and tables diff --git a/doc/quick.rst b/doc/quick.rst index 3b288657..a4bf4fb6 100644 --- a/doc/quick.rst +++ b/doc/quick.rst @@ -1,422 +1,107 @@ Quick start *********** -**sympde** allows you to create symbolic expressions using differential/algebraic operators. It can be used to define strong or weak forms, when dealing with finite elements methods. - -Strong forms -^^^^^^^^^^^^ - -Assume you want to create the symbolic expression that is equivalent to - -.. math:: - - \mathcal{L} u := - \nabla^2 u + u - -where :math:`u` denotes the *unknown* of 2 variables (2D). - -In **sympde**, you can declare :math:`u` using - -.. code-block:: python - - from sympde import Unknown - - u = Unknown('u', ldim=2) - -The expression associated to the operator :math:`\mathcal{L}` is then defined as - -.. code-block:: python - - from sympde import grad, div - - expr = - div(grad(u)) + u - -you can also use a *lambda expression* - -.. code-block:: python - - from sympde import grad, div - - L = lambda u: - div(grad(u)) + u - -As any sympy expression, you can take a look at the diagram of the expression tree: - -.. figure:: include/quick/graph1.png - :scale: 70 % - :alt: map to buried treasure - - Expression tree for the :math:`\mathcal{L} u := - \nabla^2 u + u` operator. - -.. note:: - - The above diagram was made using `Graphviz `_ and - the `dotprint `_ function. - -**sympde** allows to write more complicated expressions and it knows many differential calculus rules to perform the computation on its own. - -Let us consider the following mathematical expression - -.. math:: - - \partial_x \partial_y uv - -The symbolic associated expression can be achieved using - -.. code-block:: python - - from sympde import Unknown - from sympde import dx, dy - - u = Unknown('u', ldim=2) - v = Unknown('v', ldim=2) - - expr = dx(dy(u*v)) - -Since we are using the atomic operators **dx** and **dy**, the expression will be automatically evaluated, in opposition to the use of **grad** or **div**. Hence, result is - -.. code-block:: python - - >>> u*dx(dy(v)) + v*dx(dy(u)) + dx(u)*dy(v) + dx(v)*dy(u) - -and the associated expression tree is - -.. figure:: include/quick/graph2.png - :scale: 70 % - :alt: map to buried treasure - - Expression tree for :math:`\partial_x \partial_y uv`. - -When evaluated, **sympde** differential operators are linear operators. Which means that the following code: - -.. code-block:: python - - from sympde import Unknown - from sympde import dx, dy - - u = Unknown('u', ldim=2) - v = Unknown('v', ldim=2) - - expr = dy(2*u+3*v) - -will return: - -.. code-block:: python - - >>> 2*dy(u) + 3*dy(v) - -**sympde** introduces the notion of a *constant* through the class **Constant**. As expected, applying a differential operator on it will return **0**: - -.. code-block:: python - - from sympde import Unknown, Constant - from sympde import dx - - u = Unknown('u', ldim=1) - alpha = Constant('alpha') - - expr = dx(alpha*u) + dx(dx(2*u)) - -The result is: - -.. code-block:: python - - >>> alpha*dx(u) + 2*dx(dx(u)) - -You can also apply a differential operator on an analytical function, which is useful to compute solution/rhs of a partial differential equation: - -.. code-block:: python - - from sympde import Constant - from sympde import dx, dy - from sympy.abc import x, y - from sympy import cos, exp - - alpha = Constant('alpha') - - L = lambda u: -dx(dx(u)) - dy(dy(u)) + alpha * u - - expr = L(cos(y)*exp(-x**2)) - -The result is: - -.. code-block:: python - - >>> alpha*exp(-x**2)*cos(y) - 4*x**2*exp(-x**2)*cos(y) + 3*exp(-x**2)*cos(y) - -sympy undefined can also be used: - -.. code-block:: python - - from sympde import Constant - from sympde import dx, dy - from sympy.abc import x, y - from sympy import Function - - alpha = Constant('alpha') - f = Function('f') - - L = lambda u: -dx(dx(u)) - dy(dy(u)) + alpha * u - - expr = L(f(x,y)) - -which gives: - -.. code-block:: python - - >>> alpha*f(x, y) - Derivative(f(x, y), x, x) - Derivative(f(x, y), y, y) - -Weak forms -^^^^^^^^^^ - -Other useful notions for partial differential equations are *bilinear/linear* forms, which are needed when using variational methods such as finite elements. - -Variational forms come with two very important concepts: - -* **FunctionSpace**: mathematical function space. It can be a Sobolev space for example. - -* **TestFunction**: a member of **FunctionSpace** - -Bilinear form -_____________ - -Unlike fenics, **sympde** does not distinguish between test and trial functions; there is no type for trial functions. In fact, they are implicitly infered from the arguments of a bilinear form. The later is defined like a sympy **Lambda** object. This means that the user must provide: - -* a couple describing test and trial functions - -* a symbolic expression of the bilinear form - -The nice thing about this approach is that it allows calling the bilinear form with different arguments and then ensures more modularity and reuse of the abstract mathematical models. - -The following example shows how to define the weak formulation of the Laplace operator - -.. code-block:: python - - from sympde import grad, dot - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - - V = FunctionSpace('V', ldim=2) - U = FunctionSpace('U', ldim=2) - - v = TestFunction(V, name='v') - u = TestFunction(U, name='u') - - a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) - -Let's take a look at the expression tree: - -.. figure:: include/quick/graph_laplace.png - :scale: 70 % - :alt: map to buried treasure - - Expression tree for Laplace weak form. - -We notice that the BilinearForm arguments are presented as a *Tuple*. The reason is that you can also define a bilinear form that is associated to a system of expressions. - -As any sympde expression, a BilinearForm can be printed, calling - -.. code-block:: python - - print(a) - -would give - -.. code-block:: python - - >>> u*v + Dot(Grad(v), Grad(u)) - -The following example implements a 1D wave model: - -.. code-block:: python - - from sympde import dx - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - - V = FunctionSpace('V', ldim=1) - W = FunctionSpace('W', ldim=1) - - T = Constant('T', real=True, label='Tension applied to the string') - rho = Constant('rho', real=True, label='mass density') - dt = Constant('dt', real=True, label='time step') - - # trial functions - u = TestFunction(V, name='u') - f = TestFunction(W, name='f') - - # test functions - v = TestFunction(V, name='v') - w = TestFunction(W, name='w') - - mass = BilinearForm((v,u), v*u) - adv = BilinearForm((v,u), dx(v)*u) - - expr = rho*mass(v,u) + dt*adv(v, f) + dt*adv(w,u) + mass(w,f) - a = BilinearForm(((v,w), (u,f)), expr) - -.. todo:: add example using Mapping -.. todo:: add example using vector test functions -.. todo:: add example using Field - -Linear form -___________ - -Linear forms are more simple to create, but follow the same logic: - -.. code-block:: python - - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import LinearForm - from sympy import cos - - V = FunctionSpace('V', ldim=2) - - v = TestFunction(V, name='v') - - x,y = V.coordinates - - b = LinearForm(v, cos(x-y)*v) - -Notice that the space gives access to the coordinates, which can be used for callable functions, such as the **cos** in our example. - -Function form -_____________ - -A FunctionForm allows you to write expressions that can be integrated over the compputational domain. It can be defined as follows: - -.. code-block:: python - - from sympde import grad, div - from sympde import FunctionSpace - from sympde import Field - from sympde import FunctionForm - from sympy import cos, pi - - V = FunctionSpace('V', ldim=1) - F = Field('F', space=V) - - x = V.coordinates - - b = FunctionForm(div(grad(F-cos(2*pi*x)))) - -Evaluation -^^^^^^^^^^ - -The purpose of **sympde** is to declare objects that are needed to write an abstract mathematical model for problems involving partial differential equations. It does not provide any discretization. However, it provides you also with algorithms to manipulate the symbolic expressions. -For example, when using *generic* operators such as **grad** or **div**, the expression is not evaluated. For this reason, **sympde** provides the function **evaluate** that allows you to transform your expression into atomic operators such as **dx**, **dy**, **dz**. In the following example, we *evaluate* the Laplace operator: - -.. code-block:: python - - from sympde import grad, dot - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - from sympde import evaluate - - V = FunctionSpace('V', ldim=2) - U = FunctionSpace('U', ldim=2) - - v = TestFunction(V, name='v') - u = TestFunction(U, name='u') - - a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) - print(evaluate(a)) - -The result is the following expression: - -.. code-block:: python - - >>> u_x*v_x + u_y*v_y + u*v - -You can then use this expression inside your code generation module to define the weak form associated to the bilinear form. What **sympde** does is the following: - -* converts generic operators such as **grad** or **div** to their atomic expressions - -* converts an atomic expression like **dx(u)** to **u_x** which can be used directly inside your python code (kernel of finite elements for example) - -If you only want to convert the generic operators into atomic operators, then you may use the **atomize** function: - -.. code-block:: python - - from sympde import grad, dot - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - from sympde import atomize - - V = FunctionSpace('V', ldim=2) - U = FunctionSpace('U', ldim=2) - - v = TestFunction(V, name='v') - u = TestFunction(U, name='u') - - a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) - print(atomize(a.expr)) - -The result is then: - -.. code-block:: python - - >>> u*v + dx(u)*dx(v) + dy(u)*dy(v) - -Notice that **atomize** is a low level function and is called inside **evaluate**. For this reason, **atomize** only operates on the expression of **sympde** forms. +SymPDE builds symbolic differential operators, variational forms, and PDE +problems on explicitly declared domains and function spaces. User-facing +objects are available from :mod:`sympde.api`. + +Domains and fields +^^^^^^^^^^^^^^^^^^ + +A field belongs to a scalar or vector function space. The following complete +example creates two scalar fields on a square and forms a differential +expression from them: + +.. literalinclude:: examples/quickstart.py + :language: python + :start-after: # [domains-start] + :end-before: # [domains-end] + +Differential operators remain symbolic, while linearity and product rules are +applied automatically. SymPDE also supports ``curl``, ``laplace``, normal +derivatives, traces, and interface operators. + +Variational forms +^^^^^^^^^^^^^^^^^ + +Integrals identify the domain on which an expression is evaluated. A +``BilinearForm`` declares its trial and test fields, and a ``LinearForm`` +declares its test field: + +.. literalinclude:: examples/quickstart.py + :language: python + :start-after: # [forms-start] + :end-before: # [forms-end] + +The :func:`sympde.api.find` helper combines those forms into a symbolic +equation. It is possible to impose essential boundary conditions (BCs) +on the solution (typically, homogeneous Dirichlet BCs) by passing an +``EssentialBC`` object through the optional ``bc`` argument. + +Expression lowering +^^^^^^^^^^^^^^^^^^^ + +Generic vector-calculus operators can be lowered to coordinate derivatives +for a specific domain with ``TerminalExpr``: + +.. literalinclude:: examples/quickstart.py + :language: python + :start-after: # [lowering-start] + :end-before: # [lowering-end] + +Expression trees +^^^^^^^^^^^^^^^^ + +Like any SymPy expression, a SymPDE expression is a tree of operators and +operands. SymPy's ``dotprint`` function writes this tree in the DOT language +of `Graphviz `_. For the operator +:math:`\mathcal{L} u := - \nabla^2 u + u`: + +.. literalinclude:: examples/expression_trees.py + :language: python + :start-after: # [operator-start] + :end-before: # [operator-end] + +.. expression-tree:: operator + :caption: Expression tree of :math:`\mathcal{L} u := - \nabla^2 u + u`. + :align: center + +Generic operators such as ``grad`` and ``div`` are kept unevaluated. The +atomic derivatives ``dx`` and ``dy`` are linear operators that apply the +product rule immediately, so the tree of :math:`\partial_x \partial_y (uv)` +contains the expanded result: + +.. literalinclude:: examples/expression_trees.py + :language: python + :start-after: # [derivative-start] + :end-before: # [derivative-end] + +.. expression-tree:: derivative + :caption: Expression tree of :math:`\partial_x \partial_y (uv)`. + :align: center + +To draw a tree outside of the documentation, save the output of ``dotprint`` +to a file such as ``tree.dot`` and run ``dot -Tpng tree.dot -o tree.png``. + +Mappings and multipatch domains +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Mappings turn logical domains into physical domains. Multiple patches can be +joined by declaring their connected sides and orientation. See the +:doc:`examples/index` for analytical mappings, interface forms, exterior +calculus, and the linearization of nonlinear problems. Each example is a +complete script that the documentation workflow runs before building the +pages. Printing ^^^^^^^^ -Latex -_____ - -A symbolic expression can be printed in latex. This is done by calling the function **latex** on your expression or **sympde** forms. - -.. code-block:: python - - from sympde import grad, div - from sympde import Unknown - from sympde.printing import latex - - u = Unknown('u', ldim=2) - - print(latex(- div(grad(u)) + u)) - -which gives the following result: - -.. code-block:: python - - >>> u - \nabla \cdot \nabla{u} - -.. math:: - - u - \nabla \cdot \nabla{u} - -A **BilinearForm** can also be printed: - -.. code-block:: python - - from sympde import grad, dot - from sympde import FunctionSpace - from sympde import TestFunction - from sympde import BilinearForm - from sympde import atomize - from sympde.printing import latex - - V = FunctionSpace('V', ldim=2) - U = FunctionSpace('U', ldim=2) - - v = TestFunction(V, name='v') - u = TestFunction(U, name='u') - - a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) - print(latex(a)) - -which returns - -.. code-block:: python - - >>> \int_{0}^{1}\int_{0}^{1} u v + \nabla{v} \cdot \nabla{u} dxdy +The public ``latex`` function produces LaTeX for expressions and forms: -.. math:: +.. literalinclude:: examples/quickstart.py + :language: python + :start-after: # [printing-start] + :end-before: # [printing-end] - \int_{0}^{1}\int_{0}^{1} u v + \nabla{v} \cdot \nabla{u} dxdy +The documentation workflow executes ``quickstart.py`` before building these +pages, so its imports and demonstrated API are checked continuously. diff --git a/doc/utils/advanced.py b/doc/utils/advanced.py deleted file mode 100644 index 0a747847..00000000 --- a/doc/utils/advanced.py +++ /dev/null @@ -1,38 +0,0 @@ -# coding: utf-8 -from sympy.printing.dot import dotprint -import os - -def dotexport(expr, fname): - txt = str(dotprint(expr)) - - name = os.path.splitext(fname)[0] - - f = open('{}.dot'.format(name), 'w') - f.write(txt) - f.close() - - cmd = 'dot {name}.dot -Tpng -o {name}.png'.format(name=name) - os.system(cmd) - -# ... -from sympde import grad, dot -from sympde import FunctionSpace -from sympde import TestFunction -from sympde import BilinearForm -from sympde.core import tensorize -from sympde.printing.latex import latex - -V = FunctionSpace('V', ldim=2) -U = FunctionSpace('U', ldim=2) - -v = TestFunction(V, name='v') -u = TestFunction(U, name='u') - -a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) -print('> a = ', a) -print('> tensorize(a) = ', tensorize(a)) - -print(latex(tensorize(a))) - -#dotexport(a, 'graph_laplace.png') -# ... diff --git a/doc/utils/quick.py b/doc/utils/quick.py deleted file mode 100644 index d940ad1e..00000000 --- a/doc/utils/quick.py +++ /dev/null @@ -1,228 +0,0 @@ -# coding: utf-8 -from sympy.printing.dot import dotprint -import os - -def dotexport(expr, fname): - txt = str(dotprint(expr)) - - name = os.path.splitext(fname)[0] - - f = open('{}.dot'.format(name), 'w') - f.write(txt) - f.close() - - cmd = 'dot {name}.dot -Tpng -o {name}.png'.format(name=name) - os.system(cmd) - - -# ... -#from sympde import Unknown -#from sympde import grad, div -# -#u = Unknown('u', ldim=2) -#expr = - div(grad(u)) + u -#dotexport(expr, 'graph1.png') -# ... - -# ... -#from sympde import Unknown -#from sympde import dx, dy -# -#u = Unknown('u', ldim=2) -#v = Unknown('v', ldim=2) -# -#expr = dx(dy(u*v)) -#dotexport(expr, 'graph2.png') -# ... - -# ... -#from sympde import Unknown -#from sympde import dx, dy -# -#u = Unknown('u', ldim=2) -#v = Unknown('v', ldim=2) -# -#expr = dy(2*u+3*v) -#print(expr) -# ... - -# ... -#from sympde import Unknown, Constant -#from sympde import dx -# -#u = Unknown('u', ldim=1) -#alpha = Constant('alpha') -# -#expr = dx(alpha*u) + dx(dx(2*u)) -#print(expr) -# ... - -# ... -#from sympde import Constant -#from sympde import dx, dy -#from sympy.abc import x, y -#from sympy import cos, exp -# -#alpha = Constant('alpha') -# -#L = lambda u: -dx(dx(u)) - dy(dy(u)) + alpha * u -# -#expr = L(cos(y)*exp(-x**2)) -#print(expr) -# ... - -# ... -#from sympde import Constant -#from sympde import dx, dy -#from sympy.abc import x, y -#from sympy import Function -# -#alpha = Constant('alpha') -#f = Function('f') -# -#L = lambda u: -dx(dx(u)) - dy(dy(u)) + alpha * u -# -#expr = L(f(x,y)) -#print(expr) -# ... - -# ... -#from sympde import grad, dot -#from sympde import FunctionSpace -#from sympde import TestFunction -#from sympde import BilinearForm -# -#V = FunctionSpace('V', ldim=2) -#U = FunctionSpace('U', ldim=2) -# -#v = TestFunction(V, name='v') -#u = TestFunction(U, name='u') -# -#a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) -# -#dotexport(a, 'graph_laplace.png') -# ... - -# ... -#from sympde import dx -#from sympde import FunctionSpace -#from sympde import TestFunction -#from sympde import BilinearForm -#from sympde import Constant -# -#V = FunctionSpace('V', ldim=1) -#W = FunctionSpace('W', ldim=1) -# -#T = Constant('T', real=True, label='Tension applied to the string') -#rho = Constant('rho', real=True, label='mass density') -#dt = Constant('dt', real=True, label='time step') -# -## trial functions -#u = TestFunction(V, name='u') -#f = TestFunction(W, name='f') -# -## test functions -#v = TestFunction(V, name='v') -#w = TestFunction(W, name='w') -# -#mass = BilinearForm((v,u), v*u) -#adv = BilinearForm((v,u), dx(v)*u) -# -#expr = rho*mass(v,u) + dt*adv(v, f) + dt*adv(w,u) + mass(w,f) -#a = BilinearForm(((v,w), (u,f)), expr) -# -#print(a) -##dotexport(a, 'graph_wave.png') -# ... - -# ... -#from sympde import FunctionSpace -#from sympde import TestFunction -#from sympde import LinearForm -#from sympy import cos -# -#V = FunctionSpace('V', ldim=2) -# -#v = TestFunction(V, name='v') -# -#x,y = V.coordinates -# -#b = LinearForm(v, cos(x-y)*v) -# ... - -# ... -#from sympde import grad, div -#from sympde import FunctionSpace -#from sympde import Field -#from sympde import FunctionForm -#from sympy import cos, pi -# -#V = FunctionSpace('V', ldim=1) -#F = Field('F', space=V) -# -#x = V.coordinates -# -#b = FunctionForm(div(grad(F-cos(2*pi*x)))) -# ... - -# ... -#from sympde import grad, dot -#from sympde import FunctionSpace -#from sympde import TestFunction -#from sympde import BilinearForm -#from sympde import evaluate -# -#V = FunctionSpace('V', ldim=2) -#U = FunctionSpace('U', ldim=2) -# -#v = TestFunction(V, name='v') -#u = TestFunction(U, name='u') -# -#a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) -#print(evaluate(a)) -# ... - -# ... -#from sympde import grad, dot -#from sympde import FunctionSpace -#from sympde import TestFunction -#from sympde import BilinearForm -#from sympde import atomize -# -#V = FunctionSpace('V', ldim=2) -#U = FunctionSpace('U', ldim=2) -# -#v = TestFunction(V, name='v') -#u = TestFunction(U, name='u') -# -#a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) -#print(atomize(a.expr)) -# ... - -# ... -#from sympde import grad, div -#from sympde import Unknown -#from sympde.printing import latex -# -#u = Unknown('u', ldim=2) -# -#print(latex(- div(grad(u)) + u)) -# ... - -# ... -from sympde import grad, dot -from sympde import FunctionSpace -from sympde import TestFunction -from sympde import BilinearForm -from sympde import atomize -from sympde.printing import latex - -V = FunctionSpace('V', ldim=2) -U = FunctionSpace('U', ldim=2) - -v = TestFunction(V, name='v') -u = TestFunction(U, name='u') - -a = BilinearForm((v,u), dot(grad(v), grad(u)) + v*u) -print(latex(a)) -# ... diff --git a/notebooks/Analytical_Mapping.ipynb b/notebooks/Analytical_Mapping.ipynb deleted file mode 100644 index 59719637..00000000 --- a/notebooks/Analytical_Mapping.ipynb +++ /dev/null @@ -1,291 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Analytical mapping" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "metadata": {}, - "outputs": [], - "source": [ - "from sympy.core.containers import Tuple\n", - "from sympy import Matrix\n", - "from sympy.tensor import IndexedBase\n", - "from sympy import symbols, simplify, Symbol\n", - "from sympy import expand\n", - "from sympy import cos, sin, sqrt, pi\n", - "from sympy import lambdify\n", - "\n", - "from numpy import linspace, meshgrid\n", - "from matplotlib import pyplot as plt\n", - "\n", - "from sympde.topology import Mapping, DetJacobian\n", - "from sympde.topology import Domain\n", - "\n", - "from sympde.core import Constant\n", - "from sympde.calculus import grad, dot, inner, cross, rot, curl, div\n", - "from sympde.calculus import laplace, hessian, bracket, convect\n", - "from sympde.topology import (dx, dy, dz)\n", - "from sympde.topology import (dx1, dx2, dx3)\n", - "from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace\n", - "from sympde.topology import ScalarTestFunction\n", - "from sympde.topology import VectorTestFunction\n", - "from sympde.topology import LogicalExpr\n", - "from sympde.topology import SymbolicExpr\n", - "from sympde.topology import element_of\n", - "from sympde.topology import IdentityMapping\n", - "from sympde.topology import PolarMapping\n", - "from sympde.topology import TargetMapping\n", - "from sympde.topology import CzarnyMapping\n", - "from sympde.topology import CollelaMapping\n", - "from sympde.topology import TorusMapping\n", - "from sympde.topology import TwistedTargetMapping" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Collela mapping" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "metadata": {}, - "outputs": [], - "source": [ - "DIM = 2\n", - "M = CollelaMapping('M', DIM)" - ] - }, - { - "cell_type": "code", - "execution_count": 67, - "metadata": {}, - "outputs": [], - "source": [ - "x1, x2 = symbols('x1, x2')\n", - "\n", - "constants = ['eps', 'k1', 'k2']\n", - "eps, k1, k2 = [Constant(i) for i in constants]" - ] - }, - { - "cell_type": "code", - "execution_count": 94, - "metadata": {}, - "outputs": [], - "source": [ - "x = LogicalExpr(M, M[0])\n", - "y = LogicalExpr(M, M[1])" - ] - }, - { - "cell_type": "code", - "execution_count": 70, - "metadata": {}, - "outputs": [], - "source": [ - "fx = lambdify((x1,x2,eps,k1,k2), x, 'numpy')\n", - "fy = lambdify((x1,x2,eps,k1,k2), y, 'numpy')" - ] - }, - { - "cell_type": "code", - "execution_count": 71, - "metadata": {}, - "outputs": [], - "source": [ - "eps = 0.1 ; k1 = 1. ; k2 = 1." - ] - }, - { - "cell_type": "code", - "execution_count": 77, - "metadata": {}, - "outputs": [], - "source": [ - "T1 = linspace(0., 1., 50)\n", - "T2 = linspace(0., 1., 50)" - ] - }, - { - "cell_type": "code", - "execution_count": 93, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "ax = plt.axes()\n", - "ax.set_aspect('equal') \n", - "\n", - "# plot columns\n", - "for t1 in T1:\n", - " X = fx(t1, T2, eps, k1, k2)\n", - " Y = fy(t1, T2, eps, k1, k2)\n", - " plt.plot(X, Y, '-b',linewidth=0.7)\n", - "\n", - "# plot lines\n", - "for t2 in T2:\n", - " X = fx(T1, t2, eps, k1, k2)\n", - " Y = fy(T1, t2, eps, k1, k2)\n", - " plt.plot(X, Y, '-b', linewidth=0.7) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## User mapping" - ] - }, - { - "cell_type": "code", - "execution_count": 129, - "metadata": {}, - "outputs": [], - "source": [ - "class CustomMapping(Mapping):\n", - " _expressions = {'x': 'A*(x1**2-x2**2)',\n", - " 'y': '2*A*x1*x2'}" - ] - }, - { - "cell_type": "code", - "execution_count": 130, - "metadata": {}, - "outputs": [], - "source": [ - "DIM = 2\n", - "M = CustomMapping('M', DIM)" - ] - }, - { - "cell_type": "code", - "execution_count": 131, - "metadata": {}, - "outputs": [], - "source": [ - "x1, x2 = symbols('x1, x2')\n", - "A = Constant('A')" - ] - }, - { - "cell_type": "code", - "execution_count": 132, - "metadata": {}, - "outputs": [], - "source": [ - "fx = lambdify((x1,x2,A), LogicalExpr(M, M[0]), 'numpy')\n", - "fy = lambdify((x1,x2,A), LogicalExpr(M, M[1]), 'numpy')" - ] - }, - { - "cell_type": "code", - "execution_count": 133, - "metadata": {}, - "outputs": [], - "source": [ - "A = 1." - ] - }, - { - "cell_type": "code", - "execution_count": 134, - "metadata": {}, - "outputs": [], - "source": [ - "T1 = linspace(-1., 1., 50)\n", - "T2 = linspace(-1., 1., 50)" - ] - }, - { - "cell_type": "code", - "execution_count": 135, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "ax = plt.axes()\n", - "ax.set_aspect('equal') \n", - "\n", - "# plot columns\n", - "for t1 in T1:\n", - " X = fx(t1, T2, A)\n", - " Y = fy(t1, T2, A)\n", - " plt.plot(X, Y, '-b',linewidth=0.7)\n", - "\n", - "# plot lines\n", - "for t2 in T2:\n", - " X = fx(T1, t2, A)\n", - " Y = fy(T1, t2, A)\n", - " plt.plot(X, Y, '-b', linewidth=0.7) " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/Exterior_Calculus.ipynb b/notebooks/Exterior_Calculus.ipynb deleted file mode 100644 index b7de5187..00000000 --- a/notebooks/Exterior_Calculus.ipynb +++ /dev/null @@ -1,547 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Exterior Calculus" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Math\n", - "\n", - "from sympy import symbols\n", - "from sympy import Tuple\n", - "from sympy import Matrix\n", - "from sympy import srepr\n", - "from sympy import Symbol\n", - "\n", - "from sympde import Constant\n", - "from sympde.exterior import d, wedge, ip, jp, delta, hodge\n", - "from sympde.exterior import DifferentialForm\n", - "from sympde.printing.latex import latex" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "x, y, z = symbols('x y z')\n", - "a = Constant('a')\n", - "n = Symbol('n')" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "u_0, v_0 = [DifferentialForm(i, index=0, dim=n) for i in ['u_0', 'v_0']]\n", - "u_1, v_1 = [DifferentialForm(i, index=1, dim=n) for i in ['u_1', 'v_1']]\n", - "u_2, v_2 = [DifferentialForm(i, index=2, dim=n) for i in ['u_2', 'v_2']]\n", - "u_3, v_3 = [DifferentialForm(i, index=3, dim=n) for i in ['u_3', 'v_3']]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "u_n, v_n = [DifferentialForm(i, index=n, dim=n) for i in ['u_n', 'v_n']]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Operators" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exterior derivative" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**\n", - "\n", - "* $ \\mathrm{d} \\circ \\mathrm{d} ~ u_i = 0$\n", - "* $\\mathrm{d}~ (\\alpha u_i + \\beta v_i) = \\alpha \\mathrm{d}~ u_i + \\beta \\mathrm{d}~ v_i \\quad$ [*linearity*]\n", - "* $\\mathrm{d}~ u_n = 0$" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "assert(d(d(u_0)) == 0)\n", - "assert(d(u_0+v_0) == d(u_0) + d(v_0))\n", - "assert(d(2*u_0) == 2*d(u_0))\n", - "assert(d(a*u_0+v_0) == a*d(u_0) + d(v_0))\n", - "assert(d(u_n) == 0)\n", - "assert(not(d(u_0) == 0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjoint of the exterior derivative" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**\n", - "\n", - "* $ \\delta \\circ \\delta ~ u_i = 0$\n", - "* $\\delta~ (\\alpha u_i + \\beta v_i) = \\alpha \\delta~ u_i + \\beta \\delta~ v_i \\quad$ [*linearity*]\n", - "* $\\delta~ u_0 = 0$" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "assert(delta(delta(u_3)) == 0)\n", - "assert(delta(u_0) == 0)\n", - "assert(delta(u_1+v_1) == delta(u_1) + delta(v_1))\n", - "assert(delta(2*u_1) == 2*delta(u_1))\n", - "assert(delta(a*u_1+v_1) == a*delta(u_1) + delta(v_1))\n", - "assert(not(delta(u_n) == 0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exterior product" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Euclidean Hodge" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Interior product/contraction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**\n" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "from sympde.topology import Domain\n", - "from sympde.topology import VectorFunctionSpace\n", - "from sympde.topology import element_of\n", - "\n", - "domain = Domain('Omega', dim=3)\n", - "\n", - "V = VectorFunctionSpace('V', domain)\n", - "beta = element_of(V, r'\\beta')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjoint of the contraction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Lie derivative" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adjoint of Lie derivative" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Trace" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**properties**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Latex printing" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathrm{d} u_{1} + u_{2}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expr = d(u_1) + u_2\n", - "Math(latex(expr))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathrm{d} u_{1} \\wedge u_{2}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expr = wedge(d(u_1), u_2)\n", - "Math(latex(expr))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathrm{d} \\star\\mathrm{d} u_{3}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expr = d(delta(u_3))\n", - "Math(latex(expr))" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\star\\mathrm{d} \\mathrm{d} u_{2} + \\mathrm{d} \\star\\mathrm{d} u_{2}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "expr = d(delta(u_2)) + delta(d(u_2))\n", - "Math(latex(expr))" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathbf{\\beta} \\lrcorner u_{1}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(ip(beta,u_1)))" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# css style\n", - "from IPython.core.display import HTML\n", - "def css_styling():\n", - " styles = open(\"styles/custom.html\", \"r\").read()\n", - " return HTML(styles)\n", - "css_styling()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/Introduction.ipynb b/notebooks/Introduction.ipynb deleted file mode 100644 index 7639697f..00000000 --- a/notebooks/Introduction.ipynb +++ /dev/null @@ -1,642 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# some imports\n", - "from IPython.display import Math\n", - "from sympy import sin, cos, pi\n", - "from sympy.abc import x,y\n", - "\n", - "from sympde.core import Constant\n", - "from sympde.calculus import grad, dot, inner, cross, rot, curl, div\n", - "from sympde.calculus import laplace, hessian, bracket, D, conv\n", - "from sympde.calculus import jump, avg, minus, plus, Dn\n", - "\n", - "from sympde.topology import (dx, dy, dz)\n", - "from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace\n", - "from sympde.topology import ScalarField, VectorField\n", - "from sympde.topology import ProductSpace\n", - "from sympde.topology import Domain, Square\n", - "from sympde.topology import element_of, elements_of\n", - "from sympde.expr import BilinearForm, LinearForm, integral, find\n", - "from sympde.expr import TerminalExpr\n", - "from sympde.printing.latex import latex" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "DIM = 2\n", - "domain = Domain('Omega', dim=DIM)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "domain = Square('Omega')" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "V = ScalarFunctionSpace('V', domain)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "v = element_of(V, name='v')\n", - "u = element_of(V, name='u')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "a = BilinearForm((v,u), integral(domain, dot(grad(v), grad(u))))" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left( v, u \\right) \\mapsto \\int_{\\Omega} \\nabla{u} \\cdot \\nabla{v} ~d\\mathbf{x}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(a))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "b = LinearForm(v, integral(domain, cos(x+y)*v))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle v \\mapsto \\int_{\\Omega} v \\cos{\\left (x + y \\right )} ~d\\mathbf{x}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(b))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle [\\![ u ]\\!]$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(jump(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\{ u \\}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(avg(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle u_{-}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(minus(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\nabla u \\cdot \\mathbf{n}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(Dn(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle [\\![ \\nabla u \\cdot \\mathbf{n} ]\\!]$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(jump(Dn(u))))" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\nabla{u_{-}} \\cdot \\mathbf{n}_{-}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(minus(Dn(u))))" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\nabla{u_{+}} \\cdot \\mathbf{n}_{+}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(plus(Dn(u))))" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "a = BilinearForm((v,u), integral(domain, dot(grad(v), grad(u)) + jump(u)*avg(v) - avg(u)*jump(v)))" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left( v, u \\right) \\mapsto \\int_{\\Omega} - \\{ u \\} [\\![ v ]\\!] ~d\\mathbf{x} + \\int_{\\Omega} \\{ v \\} [\\![ u ]\\!] ~d\\mathbf{x} + \\int_{\\Omega} \\nabla{u} \\cdot \\nabla{v} ~d\\mathbf{x}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(a))" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\nabla^2u$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(laplace(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathrm{hess}( u )$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(hessian(u)))" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle u \\ast v$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(conv(u, v)))" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "W = VectorFunctionSpace('W', domain)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "w = element_of(W, name='w')" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mathrm{D}\\left( \\mathbf{w} \\right)$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(D(w)))" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "l = LinearForm(v, integral(domain.boundary, cos(x+y)*v))" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle v \\mapsto \\int_{\\Gamma_1} v \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_2} v \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_3} v \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_4} v \\cos{\\left (x + y \\right )} ~ds$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(l))" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "equation = find(u, forall=v, lhs=a(u,v), rhs=l(v))" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\mbox{find} ~u \\in V ~\\mbox{such that}\\\\\\int_{\\Omega} - \\{ v \\} [\\![ u ]\\!] ~d\\mathbf{x} + \\int_{\\Omega} \\{ u \\} [\\![ v ]\\!] ~d\\mathbf{x} + \\int_{\\Omega} \\nabla{u} \\cdot \\nabla{v} ~d\\mathbf{x} = \\int_{\\Gamma_1} \\left ( v\\right ) \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_2} \\left ( v\\right ) \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_3} \\left ( v\\right ) \\cos{\\left (x + y \\right )} ~ds + \\int_{\\Gamma_4} \\left ( v\\right ) \\cos{\\left (x + y \\right )} ~ds,\\quad \\forall~ v \\in V$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(equation))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Multiple domains" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "A = Square(r'A')\n", - "B = Square(r'B')\n", - "\n", - "domain = A.join(B, name = 'Omega',\n", - " bnd_minus = A.get_boundary(axis=0, ext=1),\n", - " bnd_plus = B.get_boundary(axis=0, ext=-1))\n", - "\n", - "I = domain.interfaces" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [], - "source": [ - "V = ScalarFunctionSpace('V', domain, kind=None)" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [], - "source": [ - "u, v = elements_of(V, names='u, v')" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [], - "source": [ - "# Nitsch\n", - "kappa = Constant('kappa')\n", - "expr_I = ( - jump(u) * jump(Dn(v))\n", - " + kappa * jump(u) * jump(v)\n", - " + plus(Dn(u)) * minus(v)\n", - " + minus(Dn(u)) * plus(v) )\n", - "a = BilinearForm((u,v), integral(domain, dot(grad(u),grad(v)))\n", - " + integral(I, expr_I))" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left( u, v \\right) \\mapsto \\int_{\\Omega} \\nabla{u} \\cdot \\nabla{v} ~d\\mathbf{x} + \\int_{A|B} - [\\![ u ]\\!] [\\![ \\nabla v \\cdot \\mathbf{n} ]\\!] ~ds + \\int_{A|B} \\left(\\nabla{u_{-}} \\cdot \\mathbf{n}_{-}\\right) v_{+} ~ds + \\int_{A|B} \\left(\\nabla{u_{+}} \\cdot \\mathbf{n}_{+}\\right) v_{-} ~ds + \\int_{A|B} \\kappa [\\![ u ]\\!] [\\![ v ]\\!] ~ds$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(a))" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [], - "source": [ - "expr = TerminalExpr(a)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\textbf{target}~A|B\\\\- \\kappa u_{-} v_{+} + \\left(\\nabla{v_{+}} \\cdot \\mathbf{n}_{+}\\right) u_{-}\\\\\\textbf{target}~A|B\\\\- \\kappa v_{-} u_{+} + \\left(\\nabla{v_{-}} \\cdot \\mathbf{n}_{-}\\right) u_{+}\\\\\\textbf{target}~\\Gamma_2\\\\\\kappa u v - u \\left(\\nabla{v} \\cdot \\mathbf{n}_{-}\\right)\\\\\\textbf{target}~\\Gamma_1\\\\\\kappa u v - u \\left(\\nabla{v} \\cdot \\mathbf{n}_{+}\\right)\\\\\\textbf{target}~A\\\\\\partial_x u_{A} \\partial_x v_{A} + \\partial_y u_{A} \\partial_y v_{A}\\\\\\textbf{target}~B\\\\\\partial_x u_{B} \\partial_x v_{B} + \\partial_y u_{B} \\partial_y v_{B}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "code = r'\\\\'.join(latex(e) for e in expr)\n", - "Math(code)\n", - "#print(code)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/differential_culculus.ipynb b/notebooks/differential_culculus.ipynb deleted file mode 100644 index 9b3a7ace..00000000 --- a/notebooks/differential_culculus.ipynb +++ /dev/null @@ -1,208 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Differential calculus " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from sympy import Symbol\n", - "from sympy.core.containers import Tuple\n", - "from sympy import symbols\n", - "from sympy import IndexedBase\n", - "from sympy import Matrix\n", - "from sympy import Function\n", - "from sympy import pi, cos, sin\n", - "from sympy import srepr\n", - "from sympy import expand\n", - "from sympy import Function\n", - "from sympy import Integer, Float, Rational\n", - "from sympy.physics.quantum import TensorProduct\n", - "\n", - "from sympde.core import Constant\n", - "from sympde.calculus import grad, dot, inner, outer, cross, rot, curl, div\n", - "from sympde.calculus import laplace, hessian, bracket, convect, D, conv\n", - "from sympde.calculus import ArgumentTypeError\n", - "from sympde.calculus import jump, avg, Dn, minus, plus\n", - "from sympde.topology import Domain\n", - "from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace\n", - "from sympde.topology import ProductSpace\n", - "from sympde.topology import H1Space, HcurlSpace, HdivSpace, L2Space, UndefinedSpace\n", - "from sympde.topology import TestFunction, ScalarTestFunction, VectorTestFunction\n", - "from sympde.topology import ScalarField, VectorField\n", - "from sympde.topology import element_of, elements_of" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "domain = Domain('Omega', dim=3)\n", - "\n", - "V = ScalarFunctionSpace('V', domain)\n", - "W = VectorFunctionSpace('W', domain)\n", - "\n", - "alpha, beta, gamma = [Constant(i) for i in ['alpha','beta','gamma']]\n", - "\n", - "f,g,h = [element_of(V, name=i) for i in ['f','g','h']]\n", - "F,G,H = [element_of(W, i) for i in ['F','G','H']]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Gradient properties" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$ \\nabla (f+g) = \\nabla f + \\nabla g $\n", - "\n", - "$ \\nabla (\\alpha f) = \\alpha \\nabla f$\n", - "\n", - "$ \\nabla (\\alpha f+ \\beta g) = \\alpha \\nabla f + \\beta \\nabla g $\n", - "\n", - "$ \\nabla (fg) = f\\nabla g + g\\nabla f $\n", - "\n", - "$ \\nabla (\\frac{f}{g}) = -\\frac{f}{g^2} \\nabla g + \\frac{1}{g} \\nabla f$" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "assert( grad(f+g) == grad(f) + grad(g) )\n", - "assert( grad(alpha*f) == alpha*grad(f) )\n", - "assert( grad(alpha*f + beta*g) == alpha*grad(f) + beta*grad(g) )\n", - "\n", - "assert( grad(f*g) == f*grad(g) + g*grad(f) )\n", - "assert( grad(f/g) == -f*grad(g)/g**2 + grad(f)/g )\n", - "\n", - "assert( expand(grad(f*g*h)) == f*g*grad(h) + f*h*grad(g) + g*h*grad(f) )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Curl properties" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "assert( curl(F+G) == curl(F) + curl(G) )\n", - "assert( curl(alpha*F) == alpha*curl(F) )\n", - "assert( curl(alpha*F + beta*G) == alpha*curl(F) + beta*curl(G) )\n", - "\n", - "assert( curl(cross(F,G)) == F*div(G) - G*div(F) - convect(F, G) + convect(G, F) )\n", - "assert( curl(f*F) == f*curl(F) + cross(grad(f), F) )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Laplace properties" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "assert( laplace(f+g) == laplace(f) + laplace(g) )\n", - "assert( laplace(alpha*f) == alpha*laplace(f) )\n", - "assert( laplace(alpha*f + beta*g) == alpha*laplace(f) + beta*laplace(g) )\n", - "assert( laplace(f*g) == f*laplace(g) + g*laplace(f) + 2*dot(grad(f), grad(g)) )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Divergence properties" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "assert( div(F+G) == div(F) + div(G) )\n", - "assert( div(alpha*F) == alpha*div(F) )\n", - "assert( div(alpha*F + beta*G) == alpha*div(F) + beta*div(G) )\n", - "\n", - "assert( div(cross(F,G)) == -dot(F, curl(G)) + dot(G, curl(F)) )\n", - "assert( div(f*F) == f*div(F) + dot(F, grad(f)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Other properties" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "assert( curl(grad(f)) == 0 )\n", - "assert( div(curl(F)) == 0 )\n", - "assert( div(cross(grad(f), grad(g))) == 0 )\n", - "assert( curl(curl(F)) == grad(div(F)) - laplace(F))\n", - "assert( curl(f*grad(g)) == cross(grad(f), grad(g)) )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/nonlinear_examples.ipynb b/notebooks/nonlinear_examples.ipynb deleted file mode 100644 index a831f5c1..00000000 --- a/notebooks/nonlinear_examples.ipynb +++ /dev/null @@ -1,145 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Math\n", - "\n", - "from sympy import Symbol\n", - "from sympy.core.containers import Tuple\n", - "from sympy import pi, cos, sin, exp\n", - "from sympy import expand\n", - "\n", - "from sympde.core import Constant\n", - "from sympde.calculus import grad, dot, inner, outer, cross, rot, curl, div\n", - "from sympde.calculus import laplace, hessian, bracket, convect, D, conv\n", - "from sympde.calculus import ArgumentTypeError\n", - "from sympde.calculus import jump, avg, Dn, minus, plus\n", - "from sympde.topology import Domain, Boundary\n", - "from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace\n", - "from sympde.topology import ProductSpace\n", - "from sympde.topology import H1Space, HcurlSpace, HdivSpace, L2Space, UndefinedSpace\n", - "from sympde.topology import TestFunction, ScalarTestFunction, VectorTestFunction\n", - "from sympde.topology import ScalarField, VectorField\n", - "from sympde.topology import element_of, elements_of\n", - "from sympde.expr import LinearForm\n", - "from sympde.expr import integral\n", - "from sympde.expr import linearize\n", - "from sympde.printing.latex import latex" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Nonlinear problems" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Nonlinear poisson" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let us consider the weak formulation $l(v)=0$, where\n", - "\n", - "\\begin{align}\n", - " l(v) = \\int_{\\Omega} \\nabla u \\cdot \\nabla v + e^{-u}v ~d\\Omega \n", - " \\label{eq:nonlinear-example-wf}\n", - "\\end{align}\n", - "\n", - "The linearization of such a linear form means that we replace $u$ by an expansion $u + \\epsilon \\delta u$ then we compute the Taylor expansion with respect to $\\epsilon$.\n", - "The linearisation leads to the construction of a bilinear form that has arguments $\\left( \\delta u, v \\right)$, as shown in the follozing Python code:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "domain = Domain('Omega', dim=2)\n", - "Gamma_N = Boundary('Gamma_N', domain.interior)\n", - "x,y = domain.coordinates\n", - "\n", - "V = ScalarFunctionSpace('V', domain)\n", - "\n", - "u, du,v = elements_of(V, names='u, delta_u, v')\n", - "\n", - "int_0 = lambda expr: integral(domain, expr)\n", - "\n", - "expr = int_0(dot(grad(v), grad(u)) -4.*exp(-u)*v) \n", - "l = LinearForm(v, expr )" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "# linearising l around u, using du\n", - "a = linearize(l, u, trials=du)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left( \\delta_{u}, v \\right) \\mapsto \\int_{\\Omega} 4.0 \\delta_{u} v e^{- u} ~d\\mathbf{x} + \\int_{\\Omega} \\nabla{\\delta_{u}} \\cdot \\nabla{v} ~d\\mathbf{x}$" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Math(latex(a))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/styles/custom.html b/notebooks/styles/custom.html deleted file mode 100644 index 0da4d105..00000000 --- a/notebooks/styles/custom.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - diff --git a/sympde/__init__.py b/sympde/__init__.py index f7fc8041..58f3ace6 100644 --- a/sympde/__init__.py +++ b/sympde/__init__.py @@ -1,7 +1 @@ -from .version import __version__ -from .old_sympy_utilities import with_metaclass -from .core import * -from .topology import * -from .exterior import * -from .printing import * -from .utilities import * +from .version import __version__ diff --git a/sympde/api/__init__.py b/sympde/api/__init__.py new file mode 100644 index 00000000..4cebf564 --- /dev/null +++ b/sympde/api/__init__.py @@ -0,0 +1,289 @@ +"""Public user-facing API for SymPDE. + +This module is the one supported location for convenient, flat imports such +as:: + + from sympde.api import BilinearForm, Square, elements_of, grad + +It contains the objects used by the projects that depend on SymPDE (PSYDAC, +IGA-Python and GeLaTo) and by the SymPDE documentation. Every object is +imported from the module that defines it. + +Internal SymPDE code and the unit tests import objects from their defining +modules instead, to keep their dependencies explicit. +""" + +from sympde.core.basic import Constant + +from sympde.calculus.core import ( + Dn, + Inner, + PlusInterfaceOperator, + avg, + bracket, + convect, + cross, + curl, + div, + dot, + grad, + hessian, + inner, + is_zero, + jump, + laplace, + minus, + outer, + plus, + rot, +) +from sympde.calculus.matrices import SymbolicDeterminant, Transpose + +from sympde.topology.basic import ( + BasicDomain, + Boundary, + Interface, + InteriorDomain, + Union, +) +from sympde.topology.domain import ( + Area, + Cube, + Domain, + ElementDomain, + Line, + NCube, + NCubeInterior, + NormalVector, + Square, + TangentVector, +) +from sympde.topology.mapping import ( + BasicCallableMapping, + InterfaceMapping, + LogicalExpr, + Mapping, + MultiPatchMapping, + SymbolicExpr, + SymbolicWeightedVolume, +) +from sympde.topology.space import ( + BasicFunctionSpace, + Derham, + IndexedVectorFunction, + ProductSpace, + ScalarFunction, + ScalarFunctionSpace, + Trace, + VectorFunction, + VectorFunctionSpace, + element_of, + elements_of, + trace_0, + trace_1, +) +from sympde.topology.derivatives import ( + dx, + dx1, + dx2, + dx3, + dy, + dz, + get_atom_derivatives, + get_atom_logical_derivatives, + get_index_derivatives, + get_index_derivatives_atom, + get_index_logical_derivatives, + get_index_logical_derivatives_atom, + get_max_logical_partial_derivatives, + logical_partial_derivatives, + partial_derivatives, +) +from sympde.topology.datatype import ( + H1Space, + H1SpaceType, + HcurlSpace, + HcurlSpaceType, + HdivSpaceType, + L2SpaceType, + SpaceType, + UndefinedSpaceType, +) +from sympde.topology.analytical_mapping import ( + AffineMapping, + CollelaMapping2D, + CzarnyMapping, + IdentityMapping, + PolarMapping, + SphericalMapping, + TargetMapping, + TorusSurfaceMapping, + TwistedTargetSurfaceMapping, +) +from sympde.topology.callable_mapping import CallableMapping + +from sympde.expr.basic import BasicForm +from sympde.expr.equation import Equation, EssentialBC, find +from sympde.expr.evaluation import ( + Advection, + AdvectionT, + Basic1dForm, + Bilaplacian, + DomainExpression, + KernelExpression, + Mass, + Stiffness, + TensorExpr, + TerminalExpr, + split_test_function, +) +from sympde.expr.expr import ( + BilinearForm, + Functional, + Integral, + LinearExpr, + LinearForm, + Norm, + SemiNorm, + integral, + linearize, +) + +from sympde.exterior.calculus import d, delta, hodge, ip, jp, ld, wedge +from sympde.exterior.form import DifferentialForm + +from sympde.printing.latex import latex +from sympde.utilities.utils import plot_domain + +__all__ = ( + 'Constant', + # Calculus + 'Dn', + 'Inner', + 'PlusInterfaceOperator', + 'SymbolicDeterminant', + 'Transpose', + 'avg', + 'bracket', + 'convect', + 'cross', + 'curl', + 'div', + 'dot', + 'grad', + 'hessian', + 'inner', + 'is_zero', + 'jump', + 'laplace', + 'minus', + 'outer', + 'plus', + 'rot', + # Topology + 'AffineMapping', + 'Area', + 'BasicCallableMapping', + 'BasicDomain', + 'BasicFunctionSpace', + 'Boundary', + 'CallableMapping', + 'CollelaMapping2D', + 'Cube', + 'CzarnyMapping', + 'Derham', + 'Domain', + 'ElementDomain', + 'H1Space', + 'H1SpaceType', + 'HcurlSpace', + 'HcurlSpaceType', + 'HdivSpaceType', + 'IdentityMapping', + 'IndexedVectorFunction', + 'Interface', + 'InterfaceMapping', + 'InteriorDomain', + 'L2SpaceType', + 'Line', + 'LogicalExpr', + 'Mapping', + 'MultiPatchMapping', + 'NCube', + 'NCubeInterior', + 'NormalVector', + 'PolarMapping', + 'ProductSpace', + 'ScalarFunction', + 'ScalarFunctionSpace', + 'SpaceType', + 'SphericalMapping', + 'Square', + 'SymbolicExpr', + 'SymbolicWeightedVolume', + 'TangentVector', + 'TargetMapping', + 'TorusSurfaceMapping', + 'Trace', + 'TwistedTargetSurfaceMapping', + 'UndefinedSpaceType', + 'Union', + 'VectorFunction', + 'VectorFunctionSpace', + 'dx', + 'dx1', + 'dx2', + 'dx3', + 'dy', + 'dz', + 'element_of', + 'elements_of', + 'get_atom_derivatives', + 'get_atom_logical_derivatives', + 'get_index_derivatives', + 'get_index_derivatives_atom', + 'get_index_logical_derivatives', + 'get_index_logical_derivatives_atom', + 'get_max_logical_partial_derivatives', + 'logical_partial_derivatives', + 'partial_derivatives', + 'trace_0', + 'trace_1', + # Expressions + 'Advection', + 'AdvectionT', + 'Basic1dForm', + 'BasicForm', + 'Bilaplacian', + 'BilinearForm', + 'DomainExpression', + 'Equation', + 'EssentialBC', + 'Functional', + 'Integral', + 'KernelExpression', + 'LinearExpr', + 'LinearForm', + 'Mass', + 'Norm', + 'SemiNorm', + 'Stiffness', + 'TensorExpr', + 'TerminalExpr', + 'find', + 'integral', + 'linearize', + 'split_test_function', + # Exterior calculus + 'DifferentialForm', + 'd', + 'delta', + 'hodge', + 'ip', + 'jp', + 'ld', + 'wedge', + # Output and utilities + 'latex', + 'plot_domain', +) diff --git a/sympde/calculus/__init__.py b/sympde/calculus/__init__.py index d468d7df..e69de29b 100644 --- a/sympde/calculus/__init__.py +++ b/sympde/calculus/__init__.py @@ -1,3 +0,0 @@ -from .core import * -from .errors import * -from .matrices import * diff --git a/sympde/calculus/core.py b/sympde/calculus/core.py index d7c38f9c..df13f455 100644 --- a/sympde/calculus/core.py +++ b/sympde/calculus/core.py @@ -4,11 +4,11 @@ but knowning properties between them. For instance, the following statement will naturally give 0 ->>> from sympde.calculus import grad, curl ->>> from sympde.topology import Domain ->>> from sympde.topology import ScalarFunctionSpace ->>> from sympde.topology import ScalarFunction ->>> from sympde.topology import element_of +>>> from sympde.api import grad, curl +>>> from sympde.api import Domain +>>> from sympde.api import ScalarFunctionSpace +>>> from sympde.api import ScalarFunction +>>> from sympde.api import element_of >>> domain = Domain('Omega', dim=2) >>> V = ScalarFunctionSpace('V', domain) @@ -105,7 +105,7 @@ from sympy.utilities.iterables import is_sequence from sympde.core.basic import CalculusFunction -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.topology.space import ScalarFunction, VectorFunction from sympde.topology.domain import NormalVector, MinusNormalVector, PlusNormalVector from sympde.topology.datatype import H1SpaceType, HcurlSpaceType @@ -145,17 +145,16 @@ # 'D', 'Dn', - '_diff_ops', - '_generic_ops', - '_is_op_test_function', 'avg', 'bracket', 'conv', 'convect', 'cross', 'curl', + 'diff_ops', 'div', 'dot', + 'generic_ops', 'grad', 'hessian', 'inner', @@ -250,12 +249,12 @@ def __radd__(self, o): def is_constant(atom): """ Determine whether the given atom represents a constant number. """ - if isinstance(atom, _coeffs_registery): + if isinstance(atom, coefficient_types): return True if (isinstance(atom, Pow) and - isinstance(atom.base, _coeffs_registery) and - isinstance(atom.exp , _coeffs_registery)): + isinstance(atom.base, coefficient_types) and + isinstance(atom.exp , coefficient_types)): return True return False @@ -276,7 +275,7 @@ class Dot(BasicOperator): Examples - >>> from sympde.calculus import Dot + >>> from sympde.calculus.core import Dot >>> from sympy import Tuple >>> from sympy.abc import x,y @@ -287,10 +286,10 @@ class Dot(BasicOperator): This operator implements the properties of addition and multiplication - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = VectorFunctionSpace('V', domain) @@ -444,10 +443,10 @@ class Inner(BasicOperator): Examples - >>> from sympde.calculus import inner, grad - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import inner, grad + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = VectorFunctionSpace('V', domain) @@ -673,10 +672,10 @@ class Grad(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = ScalarFunctionSpace('V', domain) @@ -791,10 +790,10 @@ class Curl(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = VectorFunctionSpace('V', domain) @@ -880,10 +879,10 @@ class Rot(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = ScalarFunctionSpace('V', domain) @@ -959,10 +958,10 @@ class Div(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = VectorFunctionSpace('V', domain) @@ -1064,10 +1063,10 @@ class Laplace(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = ScalarFunctionSpace('V', domain) @@ -1148,10 +1147,10 @@ class Hessian(DiffOperator): Examples - >>> from sympde.core import Constant - >>> from sympde.topology import Domain - >>> from sympde.topology import VectorFunctionSpace - >>> from sympde.topology import VectorFunction + >>> from sympde.api import Constant + >>> from sympde.api import Domain + >>> from sympde.api import VectorFunctionSpace + >>> from sympde.api import VectorFunction >>> domain = Domain('Omega', dim=2) >>> V = ScalarFunctionSpace('V', domain) @@ -1255,7 +1254,7 @@ def eval(cls, arg1, arg2): # Derivative of product: d(a*b*c) = da * (b*c) + a * d(b*c) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] fields = [a for a in expr.args if a not in coeffs] terms = [] @@ -1270,7 +1269,7 @@ def eval(cls, arg1, arg2): return Mul(a, b) # Derivative of constant: d(const) = 0 - elif isinstance(expr, _coeffs_registery): + elif isinstance(expr, coefficient_types): return S.Zero # Automatic evaluation to canonical form: reorder arguments by using @@ -1343,11 +1342,11 @@ def eval(cls, *_args): # return is done at the end alpha = S.One if isinstance(left, Mul): - coeffs = [a for a in left.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in left.args if isinstance(a, coefficient_types)] for a in left.args: if ( isinstance(a, Pow) and - isinstance(a.base, _coeffs_registery) and - isinstance(a.exp, _coeffs_registery) ): + isinstance(a.base, coefficient_types) and + isinstance(a.exp, coefficient_types) ): coeffs += [a] vectors = [a for a in left.args if not(a in coeffs)] @@ -1364,11 +1363,11 @@ def eval(cls, *_args): left = b if isinstance(right, Mul): - coeffs = [a for a in right.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in right.args if isinstance(a, coefficient_types)] for a in right.args: if ( isinstance(a, Pow) and - isinstance(a.base, _coeffs_registery) and - isinstance(a.exp, _coeffs_registery) ): + isinstance(a.base, coefficient_types) and + isinstance(a.exp, coefficient_types) ): coeffs += [a] vectors = [a for a in right.args if not(a in coeffs)] @@ -1434,7 +1433,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -1507,7 +1506,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -1580,7 +1579,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -1649,7 +1648,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -1744,7 +1743,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -1806,10 +1805,10 @@ def __getitem__(self, key): #============================================================================== -_generic_ops = (Dot, Cross, Inner, Outer, Convect, Convolution) +generic_ops = (Dot, Cross, Inner, Outer, Convect, Convolution) -_diff_ops = (Grad, Curl, Rot, Div, - Bracket, Laplace, Hessian) +diff_ops = (Grad, Curl, Rot, Div, + Bracket, Laplace, Hessian) # ... alias for ufl compatibility # user friendly functions @@ -1837,9 +1836,6 @@ def __getitem__(self, key): plus = PlusInterfaceOperator -_is_op_test_function = lambda op: (isinstance(op, (Grad, Curl, Div)) and - isinstance(op._args[0], (ScalarFunction, VectorFunction))) - _is_sympde_atom = lambda a: isinstance(a, (ScalarFunction, VectorFunction, minus, plus)) def add_basicop(expr): diff --git a/sympde/calculus/matrices.py b/sympde/calculus/matrices.py index 2ba04189..11152987 100644 --- a/sympde/calculus/matrices.py +++ b/sympde/calculus/matrices.py @@ -2,7 +2,7 @@ from sympy import Add, Mul, Pow from sympy import sympify from sympy.core.decorators import call_highest_priority -from sympde.core.basic import _coeffs_registery, new_basic, Basic +from sympde.core.basic import coefficient_types, new_basic, Basic class MatrixSymbolicExpr(Expr): is_commutative = False @@ -257,7 +257,7 @@ def __new__(cls, *args, **options): return Add(*[SymbolicTrace(a) for a in arg.args]) elif isinstance(arg, Mul): - coeffs = [a for a in arg.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in arg.args if isinstance(a, coefficient_types)] mats = [a for a in arg.args if not a in coeffs] if coeffs: return Mul(*coeffs)*SymbolicTrace(arg.func(*mats)) diff --git a/sympde/calculus/tests/test_calculus.py b/sympde/calculus/tests/test_calculus.py index 8f016acc..c4eb9800 100644 --- a/sympde/calculus/tests/test_calculus.py +++ b/sympde/calculus/tests/test_calculus.py @@ -6,15 +6,15 @@ from sympy import Integer, Float, Rational from sympy import expand -from sympde.core import Constant -from sympde.calculus import grad, dot, inner, outer, cross, rot, curl, div -from sympde.calculus import laplace, hessian, bracket, convect, D, conv -from sympde.calculus import ArgumentTypeError -from sympde.calculus import jump, avg, Dn, minus, plus -from sympde.topology import Domain -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import ProductSpace -from sympde.topology import element_of, elements_of +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, inner, outer, cross, rot, curl, div +from sympde.calculus.core import laplace, hessian, bracket, convect, D, conv +from sympde.calculus.errors import ArgumentTypeError +from sympde.calculus.core import jump, avg, Dn, minus, plus +from sympde.topology.domain import Domain +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import ProductSpace +from sympde.topology.space import element_of, elements_of #============================================================================== @pytest.mark.parametrize('dim', [1, 2, 3]) diff --git a/sympde/core/__init__.py b/sympde/core/__init__.py index 4bc8bacf..e69de29b 100644 --- a/sympde/core/__init__.py +++ b/sympde/core/__init__.py @@ -1,3 +0,0 @@ -from .algebra import * -from .basic import * -from .utils import * diff --git a/sympde/core/algebra.py b/sympde/core/algebra.py index 8108bba6..ada0c875 100644 --- a/sympde/core/algebra.py +++ b/sympde/core/algebra.py @@ -1,42 +1,14 @@ # coding: utf-8 -import numpy as np -from itertools import groupby - -#from sympy.core.sympify import sympify -from sympy.simplify.simplify import simplify -from sympy import Symbol -from sympy import Lambda -from sympy import Function -from sympy import bspline_basis -from sympy import lambdify -from sympy import cos -from sympy import sin -from sympy import Rational -from sympy import diff -from sympy import Matrix, ImmutableDenseMatrix -from sympy import latex -from sympy import I as sympy_I -from sympy.core import Basic -from sympy.core.singleton import S -from sympy.simplify.simplify import nsimplify -from sympy.utilities.lambdify import implemented_function -from sympy.matrices.dense import MutableDenseMatrix -from sympy import Mul, Add -from sympy import postorder_traversal -from sympy import preorder_traversal -from sympy.core.expr import Expr +from sympy import Indexed, Matrix, ImmutableDenseMatrix +from sympy.core import Add, Basic, Mul from sympy.core.containers import Tuple -from sympy import Integer, Float -from sympy import Add, Mul -from sympy import simplify -from sympy import S -from sympy import Basic -from sympy import Indexed, IndexedBase +from sympy.core.singleton import S +from sympy.simplify.simplify import simplify from sympy.utilities.iterables import is_sequence from .basic import CalculusFunction -from .basic import _coeffs_registery +from .basic import coefficient_types # TODO update default name @@ -75,7 +47,7 @@ def eval(cls, expr): return Add(*args) if isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One diff --git a/sympde/core/basic.py b/sympde/core/basic.py index 1c371e56..f8b07cf8 100644 --- a/sympde/core/basic.py +++ b/sympde/core/basic.py @@ -113,4 +113,4 @@ class BasicDerivable(Basic): pass #============================================================================== -_coeffs_registery = (int, float, complex, Number, NumberSymbol, Constant) +coefficient_types = (int, float, complex, Number, NumberSymbol, Constant) diff --git a/sympde/core/tests/test_api.py b/sympde/core/tests/test_api.py new file mode 100644 index 00000000..efa71b43 --- /dev/null +++ b/sympde/core/tests/test_api.py @@ -0,0 +1,50 @@ +from pathlib import Path + + +def test_package_initializers_are_minimal(): + package_dir = Path(__file__).parents[2] + initializers = package_dir.rglob('__init__.py') + + for initializer in initializers: + if initializer.parent == package_dir: + # The top-level package only exposes its version + assert initializer.read_text() == 'from .version import __version__\n' + elif initializer.parent != package_dir / 'api': + assert initializer.read_text() == '' + + +def test_version_is_available_from_top_level_package(): + from sympde import __version__ + from sympde.version import __version__ as version + + assert __version__ is version + + +def test_api_has_only_explicit_exports(): + import sympde.api as api + + assert len(api.__all__) == len(set(api.__all__)) + assert all(hasattr(api, name) for name in api.__all__) + assert {name for name in vars(api) if not name.startswith('_')} == { + name for name in api.__all__ if not name.startswith('_') + } + + +def test_api_objects_come_from_defining_modules(): + from sympde.api import BilinearForm, Constant, Cube, Mapping, elements_of + from sympde.api import ScalarFunctionSpace, VectorFunctionSpace + from sympde.core.basic import Constant as CoreConstant + from sympde.expr.expr import BilinearForm as ExprBilinearForm + from sympde.topology.domain import Cube as TopologyCube + from sympde.topology.mapping import Mapping as TopologyMapping + from sympde.topology.space import elements_of as topology_elements_of + from sympde.topology.space import ScalarFunctionSpace as TopologyScalarSpace + from sympde.topology.space import VectorFunctionSpace as TopologyVectorSpace + + assert BilinearForm is ExprBilinearForm + assert Constant is CoreConstant + assert Cube is TopologyCube + assert Mapping is TopologyMapping + assert elements_of is topology_elements_of + assert ScalarFunctionSpace is TopologyScalarSpace + assert VectorFunctionSpace is TopologyVectorSpace diff --git a/sympde/expr/__init__.py b/sympde/expr/__init__.py index a092e4cd..e69de29b 100644 --- a/sympde/expr/__init__.py +++ b/sympde/expr/__init__.py @@ -1,5 +0,0 @@ -from .basic import * -from .equation import * -from .errors import * -from .evaluation import * -from .expr import * diff --git a/sympde/expr/basic.py b/sympde/expr/basic.py index 7aa5f529..4342a219 100644 --- a/sympde/expr/basic.py +++ b/sympde/expr/basic.py @@ -7,10 +7,10 @@ from sympde.topology.space import ScalarFunction from sympde.topology.space import VectorFunction -__all__ = ('BasicExpr', 'BasicForm', '_sanitize_arguments') +__all__ = ('BasicExpr', 'BasicForm', 'sanitize_arguments') #============================================================================== -def _sanitize_arguments(arguments, is_bilinear=False, is_linear=False): +def sanitize_arguments(arguments, is_bilinear=False, is_linear=False): if is_bilinear or is_linear: if is_bilinear: diff --git a/sympde/expr/equation.py b/sympde/expr/equation.py index 608dc509..4ab9474b 100644 --- a/sympde/expr/equation.py +++ b/sympde/expr/equation.py @@ -5,14 +5,13 @@ from sympy.core import Expr from sympy.parsing.sympy_parser import parse_expr -from sympde.topology.basic import Boundary, Union -from sympde.topology.space import VectorFunctionSpace, ScalarFunctionSpace +from sympde.topology.basic import Union +from sympde.topology.domain import NormalVector +from sympde.topology.space import IndexedVectorFunction from sympde.topology.space import ScalarFunction -from sympde.topology.space import VectorFunction, IndexedVectorFunction -from sympde.topology import Boundary, NormalVector, TangentVector -from sympde.topology import Trace, trace_0, trace_1 -from sympde.calculus import grad, dot -from sympde.core.utils import random_string +from sympde.topology.space import Trace +from sympde.topology.space import VectorFunction, VectorFunctionSpace +from sympde.calculus.core import dot, grad from sympde.core.basic import ( new_basic, restore_optional_argument, @@ -20,8 +19,8 @@ from .expr import BilinearForm, LinearForm from .expr import linearize -from .errors import ( UnconsistentLhsError, UnconsistentRhsError, - UnconsistentArgumentsError, UnconsistentBCError ) +from .errors import (UnconsistentLhsError, UnconsistentRhsError, + UnconsistentArgumentsError) #============================================================================== diff --git a/sympde/expr/evaluation.py b/sympde/expr/evaluation.py index 7cecb060..f5b50f34 100644 --- a/sympde/expr/evaluation.py +++ b/sympde/expr/evaluation.py @@ -6,7 +6,7 @@ from sympy.core.containers import Tuple from sympy.simplify.simplify import simplify -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.core.basic import CalculusFunction from sympde.core.basic import new_basic from sympde.core.algebra import (Dot_1d, @@ -14,9 +14,9 @@ Dot_3d, Inner_3d, Cross_3d) #from sympde.core.utils import random_string -from sympde.calculus import jump, avg, minus, plus -from sympde.calculus import Jump, is_zero -from sympde.calculus.core import _generic_ops, _diff_ops +from sympde.calculus.core import Jump, is_zero +from sympde.calculus.core import jump, minus, plus +from sympde.calculus.core import generic_ops, diff_ops from sympde.calculus.core import MinusInterfaceOperator, PlusInterfaceOperator from sympde.calculus.matrices import SymbolicDeterminant, Inverse, Transpose from sympde.calculus.matrices import MatrixElement, SymbolicTrace @@ -36,8 +36,8 @@ from sympde.topology.space import Trace from sympde.topology.space import ScalarFunctionSpace -from sympde.topology.derivatives import _partial_derivatives -from sympde.topology.derivatives import _logical_partial_derivatives +from sympde.topology.derivatives import partial_derivatives +from sympde.topology.derivatives import logical_partial_derivatives from sympde.topology.derivatives import get_atom_derivatives from sympde.topology.derivatives import get_atom_logical_derivatives from sympde.topology.derivatives import dx, dy, dz @@ -59,7 +59,7 @@ from .expr import BilinearForm from .expr import Integral from .expr import Functional -from .expr import _get_domain +from .expr import get_domain __all__ = ( 'Advection', @@ -74,14 +74,8 @@ 'Stiffness', 'TensorExpr', 'TerminalExpr', - '_get_trials_tests', - '_replace_atomic_expr', - '_split_expr_over_interface', - '_split_test_function', - '_tensorize_atomic_expr', - '_to_matrix_form', - '_unpack_functions', 'is_sequence', + 'split_test_function', ) #============================================================================== @@ -688,7 +682,7 @@ def eval(cls, expr, domain): if isinstance(expr.expr, Add): for a in expr.expr.args: - domains = _get_domain(a) + domains = get_domain(a) if not isinstance(domains, Union): domains = (domains, ) @@ -700,7 +694,7 @@ def eval(cls, expr, domain): domains = expr.domain else: - domains = _get_domain(expr.expr) + domains = get_domain(expr.expr) if isinstance(domains, Union): domains = list(domains._args) @@ -806,7 +800,7 @@ def eval(cls, expr, domain): elif isinstance(expr, BasicExpr): return cls.eval(expr.expr, domain=domain) - elif isinstance(expr, _diff_ops): + elif isinstance(expr, diff_ops): op = type(expr) if domain.mapping is None: new = eval('Logical{0}_{1}d'.format(op, dim)) @@ -816,7 +810,7 @@ def eval(cls, expr, domain): args = [cls.eval(i, domain=domain) for i in expr.args] return new(*args) - elif isinstance(expr, _generic_ops): + elif isinstance(expr, generic_ops): # if i = Dot(...) then type(i) is Grad op = type(expr) new = eval('{0}_{1}d'.format(op, dim)) @@ -870,7 +864,7 @@ def eval(cls, expr, domain): #============================================================================== # TODO use random_string for space name and use it for 1d test function -def _split_test_function(expr): +def split_test_function(expr): if isinstance(expr, ScalarFunction): @@ -921,10 +915,10 @@ def _split_test_function(expr): #============================================================================== def _tensorize_atomic_expr(expr, d_atoms): - if (isinstance(expr, _partial_derivatives) or - isinstance(expr,_logical_partial_derivatives)): + if (isinstance(expr, partial_derivatives) or + isinstance(expr,logical_partial_derivatives)): - if isinstance(expr, _partial_derivatives): + if isinstance(expr, partial_derivatives): atom = get_atom_derivatives(expr) else: @@ -1107,7 +1101,7 @@ def eval(cls, *_args, **kwargs): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] stests = [a for a in expr.args if not(a in coeffs) and a.atoms(ScalarFunction)] vtests = [a for a in expr.args if not(a in coeffs) and a.atoms(VectorFunction)] vectors = [a for a in expr.args if (not(a in coeffs) and @@ -1136,7 +1130,7 @@ def eval(cls, *_args, **kwargs): return Mul(a, b, sb, vb) - elif isinstance(expr, _coeffs_registery): + elif isinstance(expr, coefficient_types): return expr elif isinstance(expr, Pow): @@ -1183,7 +1177,7 @@ def eval(cls, *_args, **kwargs): # Prepare dictionary for '_tensorize_atomic_expr', which should # process all variables but leave fields unchanged: d_atoms = dict( - [(v,_split_test_function(v)[v]) for v in variables] + [(f, (f,)) for f in fields]) + [(v,split_test_function(v)[v]) for v in variables] + [(f, (f,)) for f in fields]) expr = cls.eval(terminal_expr, d_atoms=d_atoms, domain=domain) diff --git a/sympde/expr/expr.py b/sympde/expr/expr.py index 8bf05491..7a735340 100644 --- a/sympde/expr/expr.py +++ b/sympde/expr/expr.py @@ -14,11 +14,10 @@ from sympde.core.basic import CalculusFunction from sympde.core.basic import Constant from sympde.core.utils import random_string -from sympde.calculus import Dot, Inner, BasicOperator -from sympde.calculus import Grad, Hessian -from sympde.topology import BasicDomain, Union -from sympde.topology import NormalVector -from sympde.topology import Boundary, Interface, Domain, InteriorDomain +from sympde.calculus.core import BasicOperator, Dot, Grad, Hessian, Inner +from sympde.topology.basic import BasicDomain, Boundary, Interface +from sympde.topology.basic import InteriorDomain, Union +from sympde.topology.domain import Domain, NormalVector from sympde.topology.space import ScalarFunction from sympde.topology.space import VectorFunction from sympde.topology.space import Trace, trace_0, trace_1 @@ -26,7 +25,7 @@ from .errors import UnconsistentLinearExpressionError from .basic import BasicForm from .basic import BasicExpr -from .basic import _sanitize_arguments +from .basic import sanitize_arguments __all__ = ( 'BilinearForm', @@ -38,9 +37,9 @@ 'SemiNorm', 'Norm', # - '_get_domain', 'add_int', 'expand', + 'get_domain', 'integral', 'is_linear_expression', 'linearize', @@ -101,7 +100,7 @@ def expand(expr): return expr #============================================================================== -def _get_domain(expr): +def get_domain(expr): # expr is an integral of BasicExpr or Add of Integral of BasicExpr if isinstance(expr, Integral): return expr.domain @@ -109,7 +108,7 @@ def _get_domain(expr): elif isinstance(expr, (Add, Mul)): domains = [] for a in expr.args: - a = _get_domain(a) + a = get_domain(a) if isinstance(a, Union): domains.extend(list(a.args)) elif isinstance(a, BasicDomain): @@ -125,7 +124,7 @@ def __new__(cls, arguments, expr, **options): if expr.atoms(Integral): raise TypeError('') - args = _sanitize_arguments(arguments, is_linear=True) + args = sanitize_arguments(arguments, is_linear=True) if not is_linear_expression(expr, args): msg = '> Expression is not linear' @@ -142,7 +141,7 @@ def expr(self): return self._args[1] def __call__(self, *args): - args = _sanitize_arguments(args, is_linear=True) + args = sanitize_arguments(args, is_linear=True) expr, _ = self.expr._xreplace(dict(list(zip(self.variables, args)))) return expr @@ -344,7 +343,7 @@ def __new__(cls, arguments, expr, check_linearity=True, ignore_linearity_errors= raise ValueError('Expecting an integral Expression') # TODO: why do we 'sanitize' here? - args = _sanitize_arguments(arguments, is_linear=True) + args = sanitize_arguments(arguments, is_linear=True) # Check linearity with respect to the given arguments if check_linearity and not is_linear_expression(expr, args): @@ -361,7 +360,7 @@ def __new__(cls, arguments, expr, check_linearity=True, ignore_linearity_errors= # Compute 'domain' property (scalar or tuple) # TODO: is this is useful? - obj._domain = _get_domain(expr) + obj._domain = get_domain(expr) return obj @@ -430,7 +429,7 @@ def __new__(cls, arguments, expr, check_linearity=True, ignore_linearity_errors= raise ValueError('Expecting integral Expression') # TODO: why do we 'sanitize' here? - args = _sanitize_arguments(arguments, is_bilinear=True) + args = sanitize_arguments(arguments, is_bilinear=True) # Distinguish between trial and test functions trial_functions, test_functions = args @@ -457,7 +456,7 @@ def __new__(cls, arguments, expr, check_linearity=True, ignore_linearity_errors= # Compute 'domain' property (scalar or tuple) # TODO: is this is useful? - obj._domain = _get_domain(expr) + obj._domain = get_domain(expr) return obj @@ -854,4 +853,3 @@ def add_int(expr): Basic._constructor_postprocessor_mapping[IntAdd] = { "Mul": [mul_add], } - diff --git a/sympde/expr/tests/test_equation_2d.py b/sympde/expr/tests/test_equation_2d.py index d6666071..1765ee05 100644 --- a/sympde/expr/tests/test_equation_2d.py +++ b/sympde/expr/tests/test_equation_2d.py @@ -6,18 +6,20 @@ from sympy import pi, cos, sin from sympy import ImmutableDenseMatrix as Matrix -from sympde.core import Constant -from sympde.calculus import grad, dot, inner -from sympde.calculus import laplace, bracket -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of -from sympde.topology import ProductSpace -from sympde.topology import Domain, Boundary, NormalVector -from sympde.topology import Square -from sympde.topology import ElementDomain -from sympde.topology import Area -from sympde.expr import BilinearForm, LinearForm, integral -from sympde.expr import Equation, EssentialBC +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, inner +from sympde.calculus.core import laplace, bracket +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of +from sympde.topology.space import ProductSpace +from sympde.topology.domain import Domain +from sympde.topology.basic import Boundary +from sympde.topology.domain import NormalVector +from sympde.topology.domain import Square +from sympde.topology.domain import ElementDomain +from sympde.topology.domain import Area +from sympde.expr.expr import BilinearForm, LinearForm, integral +from sympde.expr.equation import Equation, EssentialBC from sympde.expr.errors import UnconsistentLhsError from sympde.expr.errors import UnconsistentRhsError from sympde.expr.errors import UnconsistentBCError diff --git a/sympde/expr/tests/test_essential_bc.py b/sympde/expr/tests/test_essential_bc.py index 0e10f926..41ceafcf 100644 --- a/sympde/expr/tests/test_essential_bc.py +++ b/sympde/expr/tests/test_essential_bc.py @@ -1,11 +1,13 @@ # coding: utf-8 -from sympde.core import Constant -from sympde.calculus import grad, dot -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of -from sympde.topology import Domain, Boundary, NormalVector -from sympde.expr import EssentialBC +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of +from sympde.topology.domain import Domain +from sympde.topology.basic import Boundary +from sympde.topology.domain import NormalVector +from sympde.expr.equation import EssentialBC #============================================================================== def test_essential_bc_1(): diff --git a/sympde/expr/tests/test_expr.py b/sympde/expr/tests/test_expr.py index 6e9ac50d..8d0114cd 100644 --- a/sympde/expr/tests/test_expr.py +++ b/sympde/expr/tests/test_expr.py @@ -7,23 +7,24 @@ from sympy import pi, cos, sin, exp from sympy import ImmutableDenseMatrix as Matrix -from sympde.core import Constant -from sympde.calculus import grad, dot, inner, rot, div -from sympde.calculus import laplace, bracket, convect -from sympde.calculus import jump, avg, Dn, minus, plus - -from sympde.topology import dx1, dx2, dx3 -from sympde.topology import dx, dy, dz -from sympde.topology import Mapping -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of, elements_of -from sympde.topology import InteriorDomain, Union -from sympde.topology import Boundary, NormalVector -from sympde.topology import Domain +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, inner, rot, div +from sympde.calculus.core import laplace, bracket, convect +from sympde.calculus.core import jump, avg, Dn, minus, plus + +from sympde.topology.derivatives import dx1, dx2, dx3 +from sympde.topology.derivatives import dx, dy, dz +from sympde.topology.mapping import Mapping +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of, elements_of +from sympde.topology.basic import InteriorDomain, Union +from sympde.topology.basic import Boundary +from sympde.topology.domain import NormalVector +from sympde.topology.domain import Domain #from sympde.topology import trace_1 # TODO [YG, 27.01.2021]: fix trace -from sympde.topology import Square -from sympde.topology import ElementDomain -from sympde.topology import Area +from sympde.topology.domain import Square +from sympde.topology.domain import ElementDomain +from sympde.topology.domain import Area from sympde.expr.expr import LinearExpr from sympde.expr.expr import LinearForm, BilinearForm diff --git a/sympde/expr/tests/test_find_2d.py b/sympde/expr/tests/test_find_2d.py index a2b05630..0258bcd4 100644 --- a/sympde/expr/tests/test_find_2d.py +++ b/sympde/expr/tests/test_find_2d.py @@ -3,16 +3,17 @@ from sympy import cos #from sympy import exp -from sympde.core import Constant -from sympde.calculus import grad, dot +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot #from sympde.topology import dx, dy -from sympde.topology import ScalarFunctionSpace -from sympde.topology import element_of -from sympde.topology import Domain, Boundary -from sympde.expr import BilinearForm, LinearForm, integral -from sympde.expr import TerminalExpr -from sympde.expr import find -from sympde.expr import EssentialBC +from sympde.topology.space import ScalarFunctionSpace +from sympde.topology.space import element_of +from sympde.topology.domain import Domain +from sympde.topology.basic import Boundary +from sympde.expr.expr import BilinearForm, LinearForm, integral +from sympde.expr.evaluation import TerminalExpr +from sympde.expr.equation import find +from sympde.expr.equation import EssentialBC #from sympde.expr import NewtonIteration DIM = 2 diff --git a/sympde/expr/tests/test_newton_2d.py b/sympde/expr/tests/test_newton_2d.py index 59ccb7d8..c6b0cbbe 100644 --- a/sympde/expr/tests/test_newton_2d.py +++ b/sympde/expr/tests/test_newton_2d.py @@ -2,12 +2,13 @@ from sympy import exp -from sympde.calculus import grad, dot -from sympde.topology import ScalarFunctionSpace -from sympde.topology import element_of -from sympde.topology import Domain, Boundary -from sympde.expr import LinearForm, integral -from sympde.expr import EssentialBC, NewtonIteration +from sympde.calculus.core import grad, dot +from sympde.topology.space import ScalarFunctionSpace +from sympde.topology.space import element_of +from sympde.topology.domain import Domain +from sympde.topology.basic import Boundary +from sympde.expr.expr import LinearForm, integral +from sympde.expr.equation import EssentialBC, NewtonIteration DIM = 2 domain = Domain('Omega', dim=DIM) diff --git a/sympde/expr/tests/test_tensor_2d.py b/sympde/expr/tests/test_tensor_2d.py index f1032cba..c055f8df 100644 --- a/sympde/expr/tests/test_tensor_2d.py +++ b/sympde/expr/tests/test_tensor_2d.py @@ -4,15 +4,15 @@ from sympy.core.containers import Tuple -from sympde.core import Constant -from sympde.calculus import grad, dot, curl, div +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, curl, div #from sympde.calculus import laplace #from sympde.topology import dx -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import elements_of -from sympde.topology import Boundary -from sympde.topology import Domain -from sympde.topology import Mapping +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import elements_of +from sympde.topology.basic import Boundary +from sympde.topology.domain import Domain +from sympde.topology.mapping import Mapping from sympde.expr.expr import BilinearForm, integral from sympde.expr.evaluation import TensorExpr diff --git a/sympde/exterior/__init__.py b/sympde/exterior/__init__.py index c026ed14..e69de29b 100644 --- a/sympde/exterior/__init__.py +++ b/sympde/exterior/__init__.py @@ -1,6 +0,0 @@ -#from .basic import * -from .calculus import * -from .datatype import * -from .form import * -from .compiler import * -from .inference import * diff --git a/sympde/exterior/calculus.py b/sympde/exterior/calculus.py index 26be613a..ae1e35f9 100644 --- a/sympde/exterior/calculus.py +++ b/sympde/exterior/calculus.py @@ -2,22 +2,14 @@ # TODO - use BasicOperator instead of LinearOperator -from numpy import unique - from sympy.core import Basic -from sympy.tensor import Indexed, IndexedBase -from sympy.core import Symbol -from sympy.core import Expr -from sympy.core.containers import Tuple -from sympy import Function -from sympy import Integer, Float -from sympy.core.singleton import Singleton +from sympy.tensor import Indexed from sympy.core import Add, Mul from sympy.core.singleton import S from sympy.utilities.iterables import is_sequence -from sympde.core.basic import _coeffs_registery -from sympde.core import LinearOperator +from sympde.core.basic import coefficient_types +from sympde.core.algebra import LinearOperator from sympde.core.basic import CalculusFunction from .form import DifferentialForm @@ -70,7 +62,7 @@ def eval(cls, *_args): if isinstance(expr, ExteriorDerivative): return 0 - if isinstance(expr, _coeffs_registery): + if isinstance(expr, coefficient_types): return 0 if isinstance(expr, DifferentialForm): @@ -83,7 +75,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -147,7 +139,7 @@ def eval(cls, *_args): # return is done at the end alpha = S.One if isinstance(left, Mul): - coeffs = [a for a in left.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in left.args if isinstance(a, coefficient_types)] vectors = [a for a in left.args if not(a in coeffs)] a = S.One @@ -162,7 +154,7 @@ def eval(cls, *_args): left = b if isinstance(right, Mul): - coeffs = [a for a in right.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in right.args if isinstance(a, coefficient_types)] vectors = [a for a in right.args if not(a in coeffs)] a = S.One @@ -397,7 +389,7 @@ def eval(cls, *_args): if isinstance(expr, AdjointExteriorDerivative): return 0 - if isinstance(expr, _coeffs_registery): + if isinstance(expr, coefficient_types): return 0 if isinstance(expr, DifferentialForm): @@ -410,7 +402,7 @@ def eval(cls, *_args): return Add(*args) elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One @@ -548,7 +540,7 @@ def eval(cls, *_args): c = (-1)**(k*(n-k)) return c*arg - if isinstance(expr, _coeffs_registery): + if isinstance(expr, coefficient_types): return 0 elif isinstance(expr, Add): @@ -558,7 +550,7 @@ def eval(cls, *_args): # TODO improve elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] a = S.One diff --git a/sympde/exterior/compiler.py b/sympde/exterior/compiler.py index da9b9012..d1475efe 100644 --- a/sympde/exterior/compiler.py +++ b/sympde/exterior/compiler.py @@ -9,13 +9,13 @@ from sympy import Tuple from sympy.utilities.iterables import is_sequence -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.core.basic import CalculusFunction -from sympde.topology import H1SpaceType, HcurlSpaceType, HdivSpaceType -from sympde.topology import L2SpaceType, UndefinedSpaceType -from sympde.topology import ScalarFunction, VectorFunction -from sympde.calculus import Grad, Curl, Div -from sympde.calculus import Dot, Inner, Cross +from sympde.topology.datatype import H1SpaceType, HcurlSpaceType, HdivSpaceType +from sympde.topology.datatype import L2SpaceType, UndefinedSpaceType +from sympde.topology.space import ScalarFunction, VectorFunction +from sympde.calculus.core import Curl, Div, Grad +from sympde.calculus.core import Cross, Dot, Inner #from sympde.calculus import grad, dot, inner, cross, rot, curl, div from .form import DifferentialForm @@ -140,7 +140,7 @@ def eval(cls, *_args, **kwargs): if isinstance(expr, (tuple, list, Tuple)): return expr - if isinstance(expr, _coeffs_registery): + if isinstance(expr, coefficient_types): return expr if _is_test_function(expr): @@ -436,7 +436,7 @@ def eval(cls, *_args, **kwargs): if isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] alpha = S.One diff --git a/sympde/exterior/datatype.py b/sympde/exterior/datatype.py index da613010..77bad61e 100644 --- a/sympde/exterior/datatype.py +++ b/sympde/exterior/datatype.py @@ -1,21 +1,9 @@ # coding: utf-8 # TODO: - Unknown is not used here (mlhipy) remove it? -from numpy import unique - from sympy.core import Basic -from sympy.tensor import Indexed, IndexedBase from sympy.core import Symbol -from sympy.core import Expr -from sympy.core.containers import Tuple -from sympy import Function -from sympy import Integer, Float from sympy.core.singleton import Singleton -from sympy.core import Add, Mul -from sympy.core.singleton import S - -from sympde.core.basic import _coeffs_registery -from sympde.core import LinearOperator #============================================================================== class FormType(Basic, metaclass=Singleton): diff --git a/sympde/exterior/form.py b/sympde/exterior/form.py index b45b9f76..65b738c4 100644 --- a/sympde/exterior/form.py +++ b/sympde/exterior/form.py @@ -1,20 +1,7 @@ # coding: utf-8 -from numpy import unique - -from sympy.core import Basic -from sympy.tensor import Indexed, IndexedBase +from sympy.core import Integer from sympy.core import Symbol -from sympy.core import Expr -from sympy.core.containers import Tuple -from sympy import Function -from sympy import Integer, Float -from sympy.core.singleton import Singleton -from sympy.core import Add, Mul -from sympy.core.singleton import S - -from sympde.core.basic import _coeffs_registery -from sympde.core import LinearOperator from .datatype import get_index_form diff --git a/sympde/exterior/inference.py b/sympde/exterior/inference.py index 92fa4a7d..f17ff44f 100644 --- a/sympde/exterior/inference.py +++ b/sympde/exterior/inference.py @@ -1,22 +1,7 @@ # coding: utf-8 -from numpy import unique - -from sympy.core import Basic -from sympy.tensor import Indexed, IndexedBase -from sympy.core import Symbol -from sympy.core import Expr -from sympy.core.containers import Tuple -from sympy import Function -from sympy import Integer, Float -from sympy.core.singleton import Singleton -from sympy.core import Add, Mul -from sympy.core.singleton import S - -from sympde.core.basic import _coeffs_registery -from sympde.core import LinearOperator -from sympde.core.basic import CalculusFunction +from sympy.core import Add from .form import DifferentialForm from .datatype import get_index_form diff --git a/sympde/exterior/tests/test_compiler.py b/sympde/exterior/tests/test_compiler.py index e0011a15..3e4d8eb1 100644 --- a/sympde/exterior/tests/test_compiler.py +++ b/sympde/exterior/tests/test_compiler.py @@ -2,15 +2,15 @@ import pytest -from sympde.topology import Domain -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of -from sympde.calculus import grad, dot, inner, cross, curl, div +from sympde.topology.domain import Domain +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of +from sympde.calculus.core import grad, dot, inner, cross, curl, div #from sympde.exterior import d, wedge, ip, delta, jp -from sympde.exterior import ld -from sympde.exterior import DifferentialForm -from sympde.exterior import ExteriorCalculusExpr, augmented_expression +from sympde.exterior.calculus import ld +from sympde.exterior.form import DifferentialForm +from sympde.exterior.compiler import ExteriorCalculusExpr, augmented_expression from sympde.calculus.errors import ArgumentTypeError #============================================================================== diff --git a/sympde/exterior/tests/test_datatype.py b/sympde/exterior/tests/test_datatype.py index 9bea28d1..0c0f2c17 100644 --- a/sympde/exterior/tests/test_datatype.py +++ b/sympde/exterior/tests/test_datatype.py @@ -7,11 +7,11 @@ from sympy import srepr from sympy import Symbol -from sympde import Constant +from sympde.core.basic import Constant -from sympde.exterior import d, wedge, ip, jp, delta, hodge -from sympde.exterior import ZeroForm, OneForm, TwoForm -from sympde.exterior import ThreeForm, FourForm, FiveForm, SixForm +from sympde.exterior.calculus import d, wedge, ip, jp, delta, hodge +from sympde.exterior.datatype import ZeroForm, OneForm, TwoForm +from sympde.exterior.datatype import ThreeForm, FourForm, FiveForm, SixForm #============================================================================== diff --git a/sympde/exterior/tests/test_exterior.py b/sympde/exterior/tests/test_exterior.py index 84c2f21d..a500717d 100644 --- a/sympde/exterior/tests/test_exterior.py +++ b/sympde/exterior/tests/test_exterior.py @@ -7,10 +7,10 @@ from sympy import srepr from sympy import Symbol -from sympde import Constant +from sympde.core.basic import Constant -from sympde.exterior import d, wedge, ip, jp, delta, hodge -from sympde.exterior import DifferentialForm +from sympde.exterior.calculus import d, wedge, ip, jp, delta, hodge +from sympde.exterior.form import DifferentialForm def test_differential_form_symbol_identity(): diff --git a/sympde/exterior/tests/test_inference.py b/sympde/exterior/tests/test_inference.py index f0e64364..14aa1f00 100644 --- a/sympde/exterior/tests/test_inference.py +++ b/sympde/exterior/tests/test_inference.py @@ -7,14 +7,19 @@ from sympy import srepr from sympy import Symbol -from sympde import Constant - -from sympde.exterior import d, wedge, ip, jp, delta, hodge -from sympde.exterior import DifferentialForm -from sympde.exterior import PullBack -from sympde.exterior import infere_type -from sympde.exterior import ZeroFormType, OneFormType, TwoFormType, ThreeFormType -from sympde.exterior import FourFormType, FiveFormType, SixFormType +from sympde.core.basic import Constant + +from sympde.exterior.calculus import d, wedge, ip, jp, delta, hodge +from sympde.exterior.form import DifferentialForm +from sympde.exterior.calculus import PullBack +from sympde.exterior.inference import infere_type +from sympde.exterior.datatype import ( + ZeroFormType, + OneFormType, + TwoFormType, + ThreeFormType, +) +from sympde.exterior.datatype import FourFormType, FiveFormType, SixFormType #============================================================================== diff --git a/sympde/old_sympy_utilities.py b/sympde/old_sympy_utilities.py deleted file mode 100644 index afa12ef6..00000000 --- a/sympde/old_sympy_utilities.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Reimplementations of constructs introduced in later versions of Python than -we support. Also some functions that are needed SymPy-wide and are located -here for easy import. -""" - -import operator -from collections import defaultdict - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - - For example, if you have the metaclass - - >>> class Meta(type): - ... pass - - Use this as the metaclass by doing - - >>> from sympde.old_sympy_utilities import with_metaclass - >>> class MyClass(with_metaclass(Meta, object)): - ... pass - - This is equivalent to the Python 2:: - - class MyClass(object): - __metaclass__ = Meta - - or Python 3:: - - class MyClass(object, metaclass=Meta): - pass - - That is, the first argument is the metaclass, and the remaining arguments - are the base classes. Note that if the base class is just ``object``, you - may omit it. - - >>> MyClass.__mro__ - (, <... 'object'>) - >>> type(MyClass) - - - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - # Code copied from the 'six' library. - class metaclass(meta): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, "NewBase", (), {}) - -class NotIterable: - """ - Use this as mixin when creating a class which is not supposed to - return true when iterable() is called on its instances because - calling list() on the instance, for example, would result in - an infinite loop. - """ - -def iterable(i, exclude=(str, dict, NotIterable), vector=False): - """ - Return a boolean indicating whether ``i`` is SymPy iterable. - True also indicates that the iterator is finite, e.g. you can - call list(...) on the instance. - - When SymPy is working with iterables, it is almost always assuming - that the iterable is not a string or a mapping, so those are excluded - by default. If you want a pure Python definition, make exclude=None. To - exclude multiple items, pass them as a tuple. - - You can also set the _iterable attribute to True or False on your class, - which will override the checks here, including the exclude test. - - As a rule of thumb, some SymPy functions use this to check if they should - recursively map over an object. If an object is technically iterable in - the Python sense but does not desire this behavior (e.g., because its - iteration is not finite, or because iteration might induce an unwanted - computation), it should disable it by setting the _iterable attribute to False. - - See also: is_sequence - - Examples - ======== - - >>> from sympy.utilities.iterables import iterable - >>> from sympy import Tuple - >>> things = [[1], (1,), set([1]), Tuple(1), (j for j in [1, 2]), {1:2}, '1', 1] - >>> for i in things: - ... print('%s %s' % (iterable(i), type(i))) - True <... 'list'> - True <... 'tuple'> - True <... 'set'> - True - True <... 'generator'> - False <... 'dict'> - False <... 'str'> - False <... 'int'> - - >>> iterable({}, exclude=None) - True - >>> iterable({}, exclude=str) - True - >>> iterable("no", exclude=str) - False - - """ - if hasattr(i, '_iterable'): - return i._iterable - try: - iter(i) - except TypeError: - return False - if exclude: - #TODO: check why the isinstance(i, exclude) return False - if vector: - return False - return not isinstance(i, exclude) - return True - -def is_sequence(i, include=None, *, vector=False): - """ - Return a boolean indicating whether ``i`` is a sequence in the SymPy - sense. If anything that fails the test below should be included as - being a sequence for your application, set 'include' to that object's - type; multiple types should be passed as a tuple of types. - - Note: although generators can generate a sequence, they often need special - handling to make sure their elements are captured before the generator is - exhausted, so these are not included by default in the definition of a - sequence. - - See also: iterable - - Examples - ======== - - >>> from sympy.utilities.iterables import is_sequence - >>> from types import GeneratorType - >>> is_sequence([]) - True - >>> is_sequence(set()) - False - >>> is_sequence('abc') - False - >>> is_sequence('abc', include=str) - True - >>> generator = (c for c in 'abc') - >>> is_sequence(generator) - False - >>> is_sequence(generator, include=(str, GeneratorType)) - True - - """ - return ( hasattr(i, '__getitem__') and - iterable(i, vector=vector) or - bool(include) and - isinstance(i, include)) diff --git a/sympde/printing/__init__.py b/sympde/printing/__init__.py index 94107209..e69de29b 100644 --- a/sympde/printing/__init__.py +++ b/sympde/printing/__init__.py @@ -1 +0,0 @@ -from .latex import * diff --git a/sympde/printing/latex.py b/sympde/printing/latex.py index 56b38a94..25cb92f2 100644 --- a/sympde/printing/latex.py +++ b/sympde/printing/latex.py @@ -2,16 +2,12 @@ # from sympy.core import Symbol -from sympy import Mul, Tuple +from sympy import Tuple from sympy.printing.latex import LatexPrinter as LatexPrinterSympy from sympy.printing.latex import translate -from sympy import Indexed, IndexedBase, Matrix, ImmutableDenseMatrix +from sympy import Indexed, IndexedBase -from sympde.topology import NormalVector, TangentVector -from sympde.topology import Line, Square, Cube, Domain -from sympde.topology.derivatives import sort_partial_derivatives -from sympde.topology.derivatives import get_index_derivatives -from sympde.topology.derivatives import get_atom_derivatives +from sympde.topology.domain import NormalVector from sympde.topology.space import ProductSpace from sympde.topology.space import ScalarFunction, VectorFunction diff --git a/sympde/printing/tests/test_latex.py b/sympde/printing/tests/test_latex.py index 0f8b7c0b..e35ab4a3 100644 --- a/sympde/printing/tests/test_latex.py +++ b/sympde/printing/tests/test_latex.py @@ -2,14 +2,16 @@ from sympy import sin, cos, pi -from sympde.calculus import grad, dot, inner, rot, div +from sympde.calculus.core import grad, dot, inner, rot, div #from sympde.topology import (dx, dy, dz) -from sympde.topology import Domain, Boundary, Square -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of -from sympde.expr import BilinearForm, LinearForm, integral -from sympde.exterior import d, wedge, ip, jp, delta, hodge -from sympde.exterior import DifferentialForm +from sympde.topology.domain import Domain +from sympde.topology.basic import Boundary +from sympde.topology.domain import Square +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of +from sympde.expr.expr import BilinearForm, LinearForm, integral +from sympde.exterior.calculus import d, wedge, ip, jp, delta, hodge +from sympde.exterior.form import DifferentialForm from sympde.printing.latex import latex diff --git a/sympde/topology/__init__.py b/sympde/topology/__init__.py index de44bd3c..e69de29b 100644 --- a/sympde/topology/__init__.py +++ b/sympde/topology/__init__.py @@ -1,9 +0,0 @@ -from .basic import * -from .derivatives import * -from .datatype import * -from .domain import * -from .mapping import * -from .measure import * -from .space import * -from .analytical_mapping import * -from .callable_mapping import * diff --git a/sympde/topology/basic.py b/sympde/topology/basic.py index a0147b2b..522e0862 100644 --- a/sympde/topology/basic.py +++ b/sympde/topology/basic.py @@ -4,9 +4,8 @@ from collections import abc -from sympy.core import Basic, Symbol, Expr +from sympy.core import Basic, Symbol from sympy.core.containers import Tuple -from sympy.tensor import IndexedBase from sympde.core.basic import ( new_basic, @@ -672,4 +671,3 @@ def __lt__(self, other): return 0 # ========================================== - diff --git a/sympde/topology/datatype.py b/sympde/topology/datatype.py index 3d7722bf..887c5616 100644 --- a/sympde/topology/datatype.py +++ b/sympde/topology/datatype.py @@ -5,13 +5,7 @@ # derivatives -from numpy import unique - from sympy.core import Basic -from sympy.tensor import Indexed, IndexedBase -from sympy.core import Symbol -from sympy.core import Expr -from sympy.core.containers import Tuple from sympy.core.singleton import Singleton #============================================================================== @@ -107,4 +101,3 @@ class L2RegularityType(RegularityType): 'hcurl': HcurlRegularity, 'hdiv': HdivRegularity, 'l2': L2Regularity} - diff --git a/sympde/topology/derivatives.py b/sympde/topology/derivatives.py index 8badda26..41713a92 100644 --- a/sympde/topology/derivatives.py +++ b/sympde/topology/derivatives.py @@ -2,8 +2,6 @@ # TODO add action of diff operators on sympy known functions -from itertools import groupby - import numpy as np from sympy import Basic @@ -21,7 +19,7 @@ from sympy.utilities.iterables import is_sequence from sympde.core.basic import CalculusFunction -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.core.basic import BasicMapping from sympde.core.algebra import LinearOperator from sympde.calculus.core import minus, plus @@ -54,21 +52,21 @@ def eval(cls, expr): types = (VectorFunction, ScalarFunction, DifferentialOperator) - if isinstance(expr, _logical_partial_derivatives): + if isinstance(expr, logical_partial_derivatives): atom = get_atom_logical_derivatives(expr) indices = get_index_logical_derivatives(expr) - if cls in _logical_partial_derivatives: + if cls in logical_partial_derivatives: indices[cls.coordinate] += 1 for i,n in enumerate(list(indices.values())[::-1]): - d = _logical_partial_derivatives[-i-1] + d = logical_partial_derivatives[-i-1] for _ in range(n): atom = d(atom, evaluate=False) - if cls in _partial_derivatives: + if cls in partial_derivatives: atom = cls(atom, evaluate=False) - elif cls not in _logical_partial_derivatives: + elif cls not in logical_partial_derivatives: raise NotImplementedError('TODO') return atom @@ -106,6 +104,10 @@ def eval(cls, expr): elif isinstance(expr, Expr): x = Symbol(cls.coordinate, real=True) if cls.logical: + # Imported lazily to avoid a circular dependency: mapping + # depends on the differential operators in this module. + from .mapping import Jacobian, Mapping + M = expr.atoms(Mapping) if len(M)>0: M = list(M)[0] @@ -122,7 +124,7 @@ def eval(cls, expr): return v elif isinstance(expr, Mul): - coeffs = [a for a in expr.args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in expr.args if isinstance(a, coefficient_types)] vectors = [a for a in expr.args if not(a in coeffs)] c = S.One @@ -186,7 +188,7 @@ class dz(DifferentialOperator): grad_index = 2 # index in grad -_partial_derivatives = (dx, dy, dz) +partial_derivatives = (dx, dy, dz) #============================================================================== class dx1(DifferentialOperator): @@ -204,7 +206,7 @@ class dx3(DifferentialOperator): grad_index = 2 # index in grad logical = True -_logical_partial_derivatives = (dx1, dx2, dx3) +logical_partial_derivatives = (dx1, dx2, dx3) #============================================================================== @cacheit @@ -224,10 +226,10 @@ def find_partial_derivatives(expr): args += find_partial_derivatives(a) return args - elif isinstance(expr, _partial_derivatives): + elif isinstance(expr, partial_derivatives): return (expr,) - elif isinstance(expr, _logical_partial_derivatives): + elif isinstance(expr, logical_partial_derivatives): return (expr,) return () @@ -241,7 +243,7 @@ def get_number_derivatives(expr): form d(a) where a is a single atom. """ n = 0 - if isinstance(expr, _partial_derivatives): + if isinstance(expr, partial_derivatives): assert(len(expr.args) == 1) n += 1 + get_number_derivatives(expr.args[0]) @@ -298,7 +300,7 @@ def get_index_derivatives(expr): for c in coord: d[c] = 0 - ops = [a for a in preorder_traversal(expr) if isinstance(a, _partial_derivatives)] + ops = [a for a in preorder_traversal(expr) if isinstance(a, partial_derivatives)] for i in ops: if isinstance(i, dx): @@ -318,7 +320,7 @@ def get_atom_derivatives(expr): """ """ - if isinstance(expr, _partial_derivatives): + if isinstance(expr, partial_derivatives): assert(len(expr.args) == 1) return get_atom_derivatives(expr.args[0]) @@ -336,7 +338,7 @@ def get_index_logical_derivatives(expr): for c in coord: d[c] = 0 - ops = [a for a in preorder_traversal(expr) if isinstance(a, _logical_partial_derivatives)] + ops = [a for a in preorder_traversal(expr) if isinstance(a, logical_partial_derivatives)] for i in ops: if isinstance(i, dx1): @@ -356,7 +358,7 @@ def get_atom_logical_derivatives(expr): """ """ - if isinstance(expr, _logical_partial_derivatives): + if isinstance(expr, logical_partial_derivatives): assert(len(expr.args) == 1) return get_atom_logical_derivatives(expr.args[0]) @@ -1281,6 +1283,3 @@ def get_max_logical_partial_derivatives(expr, F=None): for k,v in dd.items(): if v > d[k]: d[k] = v return d - -from .mapping import Mapping, Jacobian - diff --git a/sympde/topology/domain.py b/sympde/topology/domain.py index 9618fadf..1a16f4f2 100644 --- a/sympde/topology/domain.py +++ b/sympde/topology/domain.py @@ -1,13 +1,10 @@ # coding: utf-8 from __future__ import annotations -import numpy as np -import h5py -import yaml import os -from collections import abc, OrderedDict -from typing import Union as TypeUnion, Optional, List, Dict, Iterable, TYPE_CHECKING +from collections import OrderedDict +from typing import Union as TypeUnion, Optional, List, Iterable, TYPE_CHECKING # Union clashes with core.basic.Union from sympy import Integer @@ -15,7 +12,7 @@ from sympy.core import Basic, symbols from sympy.core.containers import Tuple from sympy.tensor import IndexedBase, Indexed -from sympy.core import Add, Mul, Pow +from sympy.core import Add from sympy.core.expr import AtomicExpr from sympy.utilities.iterables import is_sequence @@ -358,6 +355,9 @@ def todict(self): return dict(sorted(d.items())) def export( self, filename ): + import h5py + import numpy as np + import yaml yml = self.todict() @@ -390,6 +390,9 @@ def from_file(cls, filename): Domain Multipatch domain. """ + import h5py + import yaml + # ... check extension of the file _, ext = os.path.splitext(filename) diff --git a/sympde/topology/mapping.py b/sympde/topology/mapping.py index 5eeb51ee..103d9cd4 100644 --- a/sympde/topology/mapping.py +++ b/sympde/topology/mapping.py @@ -16,16 +16,16 @@ from sympy.polys.polytools import parallel_poly_from_expr from sympy.utilities.iterables import is_sequence -from sympde.core import Constant +from sympde.core.basic import Constant from sympde.core.basic import BasicMapping from sympde.core.basic import CalculusFunction -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.core.basic import new_basic from sympde.calculus.core import PlusInterfaceOperator, MinusInterfaceOperator from sympde.calculus.core import grad, div, curl, laplace #, hessian -from sympde.calculus.core import dot, inner, outer, _diff_ops +from sympde.calculus.core import dot, inner, outer, diff_ops from sympde.calculus.core import has, DiffOperator -from sympde.calculus.matrices import MatrixSymbolicExpr, MatrixElement, SymbolicTrace, Inverse +from sympde.calculus.matrices import MatrixSymbolicExpr, MatrixElement, SymbolicTrace from sympde.calculus.matrices import SymbolicDeterminant, Transpose from .basic import BasicDomain, Union, InteriorDomain @@ -36,9 +36,9 @@ from .space import Trace from .datatype import HcurlSpaceType, H1SpaceType, L2SpaceType, HdivSpaceType, UndefinedSpaceType from .derivatives import dx, dy, dz, DifferentialOperator -from .derivatives import _partial_derivatives +from .derivatives import partial_derivatives from .derivatives import get_atom_derivatives, get_index_derivatives_atom -from .derivatives import _logical_partial_derivatives +from .derivatives import logical_partial_derivatives from .derivatives import get_atom_logical_derivatives, get_index_logical_derivatives_atom from .derivatives import LogicalGrad_1d, LogicalGrad_2d, LogicalGrad_3d @@ -916,7 +916,7 @@ def eval(cls, expr, domain, **options): """.""" from sympde.expr.evaluation import TerminalExpr, DomainExpression - from sympde.expr.expr import BilinearForm, LinearForm, BasicForm, Norm + from sympde.expr.expr import BilinearForm, LinearForm, Norm from sympde.expr.expr import Integral types = (ScalarFunction, VectorFunction, DifferentialOperator, Trace, Integral) @@ -966,7 +966,7 @@ def eval(cls, expr, domain, **options): v *= i return v - elif isinstance(expr, _logical_partial_derivatives): + elif isinstance(expr, logical_partial_derivatives): if mapping.is_analytical: Ms = [mapping[i] for i in range(dim)] expr = expr.subs(list(zip(Ms, mapping.expressions))) @@ -1099,7 +1099,7 @@ def eval(cls, expr, domain, **options): args = [cls.eval(arg, domain) for arg in expr.args] return type(expr)(*args) - elif isinstance(expr, _diff_ops): + elif isinstance(expr, diff_ops): raise NotImplementedError('TODO') # TODO MUST BE MOVED AFTER TREATING THE CASES OF GRAD, CURL, DIV IN FEEC @@ -1327,7 +1327,7 @@ def eval(cls, *_args, **kwargs): v = Pow(cls.eval(b, code=code), e) return v - elif isinstance(expr, _coeffs_registery): + elif isinstance(expr, coefficient_types): return expr elif isinstance(expr, (list, tuple, Tuple)): @@ -1380,7 +1380,7 @@ def eval(cls, *_args, **kwargs): return Symbol(name) - elif isinstance(expr, _partial_derivatives): + elif isinstance(expr, partial_derivatives): atom = get_atom_derivatives(expr) indices = get_index_derivatives_atom(expr, atom) code = None @@ -1393,7 +1393,7 @@ def eval(cls, *_args, **kwargs): code += k*n return cls.eval(atom, code=code) - elif isinstance(expr, _logical_partial_derivatives): + elif isinstance(expr, logical_partial_derivatives): atom = get_atom_logical_derivatives(expr) indices = get_index_logical_derivatives_atom(expr, atom) code = None diff --git a/sympde/topology/space.py b/sympde/topology/space.py index cead6f83..50423cd7 100644 --- a/sympde/topology/space.py +++ b/sympde/topology/space.py @@ -15,7 +15,7 @@ from sympde.core.utils import expand_name_patterns from sympde.core.utils import random_string from sympde.core.basic import CalculusFunction -from sympde.core.basic import _coeffs_registery +from sympde.core.basic import coefficient_types from sympde.core.basic import new_basic from .basic import BasicDomain, Union, Interval @@ -469,7 +469,7 @@ def __new__(cls, base, *args, **kw_args): return Add(*[cls(b, *args, **kw_args) for b in base.args]) elif isinstance(base, Mul): - scalar_types = (*_coeffs_registery, ScalarFunction) + scalar_types = (*coefficient_types, ScalarFunction) scalars = [s for s in base.args if isinstance(s, scalar_types)] others = [s for s in base.args if s not in scalars] return Mul(*scalars) * Mul(*[cls(b, *args, **kw_args) for b in others]) @@ -652,7 +652,7 @@ def eval(cls, expr, boundary, order): if isinstance(expr, Mul): args = expr.args - coeffs = [a for a in args if isinstance(a, _coeffs_registery)] + coeffs = [a for a in args if isinstance(a, coefficient_types)] a = Mul(*coeffs) args = [a for a in args if not(a in coeffs)] diff --git a/sympde/topology/tests/test_derivatives.py b/sympde/topology/tests/test_derivatives.py index 9796b582..1d3c6723 100644 --- a/sympde/topology/tests/test_derivatives.py +++ b/sympde/topology/tests/test_derivatives.py @@ -5,14 +5,15 @@ from sympy import Matrix from sympy import srepr -from sympde.core import Constant -from sympde.calculus import grad, dot, inner -from sympde.topology import Domain, element_of -from sympde.topology import get_index_derivatives_atom -from sympde.topology import get_max_partial_derivatives -from sympde.topology import ScalarFunctionSpace -from sympde.topology import (dx, dy, dz) -from sympde.topology import Mapping +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, inner +from sympde.topology.domain import Domain +from sympde.topology.space import element_of +from sympde.topology.derivatives import get_index_derivatives_atom +from sympde.topology.derivatives import get_max_partial_derivatives +from sympde.topology.space import ScalarFunctionSpace +from sympde.topology.derivatives import dx, dy, dz +from sympde.topology.mapping import Mapping def indices_as_str(a): diff --git a/sympde/topology/tests/test_field_2d.py b/sympde/topology/tests/test_field_2d.py index 04f8c653..ff8117be 100644 --- a/sympde/topology/tests/test_field_2d.py +++ b/sympde/topology/tests/test_field_2d.py @@ -2,11 +2,11 @@ from sympy import Matrix -from sympde.calculus import grad, inner -from sympde.topology import Domain -from sympde.topology import dx -from sympde.topology import VectorFunctionSpace -from sympde.topology import element_of +from sympde.calculus.core import grad, inner +from sympde.topology.domain import Domain +from sympde.topology.derivatives import dx +from sympde.topology.space import VectorFunctionSpace +from sympde.topology.space import element_of #============================================================================== diff --git a/sympde/topology/tests/test_gallery.py b/sympde/topology/tests/test_gallery.py index 9aaee6e3..de613a29 100644 --- a/sympde/topology/tests/test_gallery.py +++ b/sympde/topology/tests/test_gallery.py @@ -4,8 +4,9 @@ from sympy import Tuple from sympy import symbols -from sympde.topology import Interval, ProductDomain, InteriorDomain, Domain -from sympde.topology import Line, Square, Cube, NCubeInterior +from sympde.topology.basic import Interval, ProductDomain, InteriorDomain +from sympde.topology.domain import Domain +from sympde.topology.domain import Line, Square, Cube, NCubeInterior x1, x2, x3 = symbols('x1, x2, x3', real=True) diff --git a/sympde/topology/tests/test_logical_expr.py b/sympde/topology/tests/test_logical_expr.py index 6c9541ec..f3aefebe 100644 --- a/sympde/topology/tests/test_logical_expr.py +++ b/sympde/topology/tests/test_logical_expr.py @@ -5,28 +5,29 @@ from sympy import expand from sympy import cos, sin, sqrt, pi -from sympde.core import Constant -from sympde.calculus import grad, dot, inner, rot, div -from sympde.calculus import laplace, bracket, convect -from sympde.calculus import jump, avg, Dn, minus, plus -from sympde.topology import Domain, Mapping, Square -from sympde.topology import dx, dy -from sympde.topology import dx1, dx2, dx3 -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace -from sympde.topology import element_of, elements_of -from sympde.topology import LogicalExpr -from sympde.topology import SymbolicExpr -from sympde.topology import IdentityMapping -from sympde.topology import PolarMapping -from sympde.topology import TargetMapping -from sympde.topology import CzarnyMapping -from sympde.topology import CollelaMapping2D -from sympde.topology import TorusMapping -from sympde.topology import TwistedTargetMapping +from sympde.core.basic import Constant +from sympde.calculus.core import grad, dot, inner, rot, div, curl +from sympde.calculus.core import laplace, bracket, convect +from sympde.calculus.core import jump, avg, Dn, minus, plus +from sympde.topology.domain import Domain +from sympde.topology.mapping import Mapping +from sympde.topology.domain import Square +from sympde.topology.derivatives import dx, dy +from sympde.topology.derivatives import dx1, dx2, dx3 +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace +from sympde.topology.space import element_of, elements_of +from sympde.topology.mapping import LogicalExpr from sympde.topology.mapping import get_logical_test_function - -from sympde.expr import BilinearForm, integral -from sympde.calculus import grad, div, curl, dot +from sympde.topology.mapping import SymbolicExpr +from sympde.topology.analytical_mapping import IdentityMapping +from sympde.topology.analytical_mapping import PolarMapping +from sympde.topology.analytical_mapping import TargetMapping +from sympde.topology.analytical_mapping import CzarnyMapping +from sympde.topology.analytical_mapping import CollelaMapping2D +from sympde.topology.analytical_mapping import TorusMapping +from sympde.topology.analytical_mapping import TwistedTargetMapping + +from sympde.expr.expr import BilinearForm, integral from sympde.topology.mapping import Jacobian diff --git a/sympde/topology/tests/test_mapping.py b/sympde/topology/tests/test_mapping.py index 8ce6fde4..b983ee44 100644 --- a/sympde/topology/tests/test_mapping.py +++ b/sympde/topology/tests/test_mapping.py @@ -7,10 +7,12 @@ from sympy.tensor import IndexedBase from sympy import symbols, simplify -from sympde.topology import IdentityMapping, Mapping, MappedDomain, AffineMapping -from sympde.topology import dx, dy, dz -from sympde.topology import dx1, dx2, dx3 -from sympde.topology import Domain, Square +from sympde.topology.analytical_mapping import IdentityMapping +from sympde.topology.mapping import Mapping, MappedDomain +from sympde.topology.analytical_mapping import AffineMapping +from sympde.topology.derivatives import dx, dy, dz +from sympde.topology.derivatives import dx1, dx2, dx3 +from sympde.topology.domain import Domain, Square from sympde.topology.mapping import ( Contravariant, diff --git a/sympde/topology/tests/test_space.py b/sympde/topology/tests/test_space.py index a52c3077..f5cd46c0 100644 --- a/sympde/topology/tests/test_space.py +++ b/sympde/topology/tests/test_space.py @@ -1,13 +1,19 @@ # coding: utf-8 -from sympde.calculus import grad, div -from sympde.topology import Domain -from sympde.topology import ScalarFunctionSpace, VectorFunctionSpace +from sympde.calculus.core import grad, div +from sympde.topology.domain import Domain +from sympde.topology.space import ScalarFunctionSpace, VectorFunctionSpace #from sympde.topology import ProductSpace -from sympde.topology import element_of -from sympde.topology import H1Space, HcurlSpace, HdivSpace, L2Space, UndefinedSpace -from sympde.topology import ScalarFunction, VectorFunction -from sympde.topology import Projector +from sympde.topology.space import element_of +from sympde.topology.datatype import ( + H1Space, + HcurlSpace, + HdivSpace, + L2Space, + UndefinedSpace, +) +from sympde.topology.space import ScalarFunction, VectorFunction +from sympde.topology.space import Projector def test_function_symbol_identity(): diff --git a/sympde/topology/tests/test_symbolic_expr.py b/sympde/topology/tests/test_symbolic_expr.py index 43d1151c..ef1b9891 100644 --- a/sympde/topology/tests/test_symbolic_expr.py +++ b/sympde/topology/tests/test_symbolic_expr.py @@ -1,12 +1,13 @@ from sympy import Symbol from sympy import sympify -from sympde.topology import Domain, ScalarFunctionSpace, element_of -from sympde.topology import dx, dy, dz -from sympde.topology import dx1, dx2, dx3 -from sympde.topology import Mapping, Jacobian -from sympde.topology import LogicalExpr -from sympde.topology import SymbolicExpr +from sympde.topology.domain import Domain +from sympde.topology.space import ScalarFunctionSpace, element_of +from sympde.topology.derivatives import dx, dy, dz +from sympde.topology.derivatives import dx1, dx2, dx3 +from sympde.topology.mapping import Mapping, Jacobian +from sympde.topology.mapping import LogicalExpr +from sympde.topology.mapping import SymbolicExpr #============================================================================== def test_derivatives_2d_without_mapping(): diff --git a/sympde/topology/tests/test_topology.py b/sympde/topology/tests/test_topology.py index 4f9a1995..13002fa4 100644 --- a/sympde/topology/tests/test_topology.py +++ b/sympde/topology/tests/test_topology.py @@ -3,13 +3,14 @@ from sympy import preorder_traversal from sympy.core import Basic -from sympde.topology import InteriorDomain, Union -from sympde.topology import Boundary -from sympde.topology import Domain, ElementDomain -from sympde.topology import Area, Mapping -from sympde.topology import Interface -from sympde.topology import Line, Square, Cube, NCube -from sympde.topology import IdentityMapping +from sympde.topology.basic import InteriorDomain, Union +from sympde.topology.basic import Boundary +from sympde.topology.domain import Domain, ElementDomain +from sympde.topology.domain import Area +from sympde.topology.mapping import Mapping +from sympde.topology.basic import Interface +from sympde.topology.domain import Line, Square, Cube, NCube +from sympde.topology.analytical_mapping import IdentityMapping base_dir = os.path.dirname(os.path.realpath(__file__)) topo_dir = os.path.join(base_dir, 'data') diff --git a/sympde/utilities/tests/test_utilities.py b/sympde/utilities/tests/test_utilities.py index f6fbfe55..2b2e8805 100644 --- a/sympde/utilities/tests/test_utilities.py +++ b/sympde/utilities/tests/test_utilities.py @@ -4,6 +4,8 @@ from sympy import Matrix, symbols, Array from sympy import S from sympde.utilities.utils import lambdify_sympde +from sympde.utilities.utils import plot_domain +from sympde.topology.domain import Square @@ -53,6 +55,28 @@ def test_lambdify_sympde_1d(): assert np.array_equal(array_input_da, expected_a_da) +def test_plot_domain_without_mapping(monkeypatch, tmp_path): + """ + Plot a 2D domain without a mapping. + + plot_domain falls back to an identity mapping for unmapped patches. This + mapping used to be 3D, so evaluating it with two coordinates failed. + """ + monkeypatch.setenv('MPLCONFIGDIR', str(tmp_path)) + matplotlib = pytest.importorskip('matplotlib') + matplotlib.use('Agg') + import matplotlib.pyplot as plt + + plot_domain(Square('Omega'), draw=False) + + # The boundary of the unit square is drawn unchanged + ax = plt.gcf().axes[0] + x = np.concatenate([line.get_xdata() for line in ax.lines]) + y = np.concatenate([line.get_ydata() for line in ax.lines]) + assert np.allclose([x.min(), x.max(), y.min(), y.max()], [0, 1, 0, 1]) + plt.close('all') + + def test_lambdify_sympde_2d(): x,y = symbols("x, y") diff --git a/sympde/utilities/utils.py b/sympde/utilities/utils.py index 3496a1df..5236fffa 100644 --- a/sympde/utilities/utils.py +++ b/sympde/utilities/utils.py @@ -2,11 +2,9 @@ import itertools as it from sympy import lambdify -from mpl_toolkits.mplot3d import * -import matplotlib.pyplot as plt - -from sympde.topology import IdentityMapping, InteriorDomain, MultiPatchMapping -from sympde.topology.analytical_mapping import TorusMapping +from sympde.topology.analytical_mapping import IdentityMapping +from sympde.topology.basic import InteriorDomain +from sympde.topology.mapping import MultiPatchMapping def lambdify_sympde(variables, expr): """ @@ -93,7 +91,7 @@ def plot_domain(domain, draw=True, isolines=False, refinement=None): Parameters ---------- - domain : sympde.topology.Domain + domain : sympde.topology.domain.Domain Domain to plot draw : bool, default=True @@ -125,7 +123,7 @@ def plot_2d(domain, draw=True, isolines=False, refinement=40): Parameters ---------- - domain : sympde.topology.Domain + domain : sympde.topology.domain.Domain Domain to plot draw : bool @@ -134,6 +132,8 @@ def plot_2d(domain, draw=True, isolines=False, refinement=40): refinement : int Number of straight line segments used to approximate each boundary edge. """ + import matplotlib.pyplot as plt + fig = plt.figure() ax = fig.add_subplot(111) @@ -159,7 +159,7 @@ def plot_3d(domain, draw=True, refinement=15): Parameters ---------- - domain : sympde.topology.Domain + domain : sympde.topology.domain.Domain Domain to plot draw : bool @@ -168,6 +168,8 @@ def plot_3d(domain, draw=True, refinement=15): refinement : int Number of straight line segments used to approximate each boundary edge. """ + import matplotlib.pyplot as plt + mapping = domain.mapping fig = plt.figure() @@ -195,7 +197,7 @@ def plot_3d_single_patch(patch, mapping, ax, refinement=15): Parameters ---------- - patch : sympde.topology.InteriorDomain + patch : sympde.topology.basic.InteriorDomain mapping : sympde.topology.mapping @@ -251,7 +253,7 @@ def plot_2d_single_patch(patch, mapping, ax, isolines=False, refinement=40): Parameters ---------- - patch : sympde.topology.InteriorDomain + patch : sympde.topology.basic.InteriorDomain mapping : sympde.topology.mapping @@ -265,7 +267,7 @@ def plot_2d_single_patch(patch, mapping, ax, isolines=False, refinement=40): Number of straight line segments used to approximate each boundary edge. """ if mapping is None: - mapping = IdentityMapping('Id', dim=3) + mapping = IdentityMapping('Id', dim=2) refinement+=1 map_call = mapping.get_callable_mapping() @@ -291,7 +293,8 @@ def plot_2d_single_patch(patch, mapping, ax, isolines=False, refinement=40): ax.plot(X_11, Y_11, 'k') if __name__ == '__main__': - from sympde.topology import Square, PolarMapping + from sympde.topology.analytical_mapping import PolarMapping + from sympde.topology.domain import Square A = Square('A', bounds1=(0, 1), bounds2=(0, np.pi/2)) F = PolarMapping('F', c1=0, c2=0, rmin=0.5, rmax=1) Omega = F(A)