diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ed4ed7f889c6a2ffcc1e0f26221c5803b63711eb..afc4153512327c2fa4a62a1402114cf398a98785 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: kwant/testing +image: gitlab.kwant-project.org:5005/kwant/testing stages: - build @@ -12,7 +12,7 @@ variables: IGNORE_HOSTKEY: "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" -build package: +.build: &build stage: build script: - echo -e "[DEFAULT]\ndefine_macros = CYTHON_TRACE=1" >build.conf @@ -22,12 +22,74 @@ build package: untracked: true expire_in: 1 hour + +.stable-env: &stable_env + before_script: + - source deactivate + - source activate kwant-stable + - unset CFLAGS # https://github.com/conda-forge/toolchain-feedstock/issues/41 + +.no-extras-env: &no_extras_env + before_script: + - source deactivate + - source activate kwant-stable-no-extras + - unset CFLAGS # https://github.com/conda-forge/toolchain-feedstock/issues/41 + +# Note that this is 'latest' as of when the image was last built +.latest-env: &latest_env + before_script: + - source deactivate + - source activate kwant-latest + - unset CFLAGS # https://github.com/conda-forge/toolchain-feedstock/issues/41 + +.bleeding-edge-env: &bleeding_edge_env + before_script: + - source deactivate + - conda env update -f /kwant-latest.yml + - source activate kwant-latest + +.ubuntu-env: &ubuntu_env + image: gitlab.kwant-project.org:5005/kwant/testing/ubuntu + +.debian-env: &debian_env + image: gitlab.kwant-project.org:5005/kwant/testing/debian + + +## Build Jobs + +build:ubuntu: + <<: *build + <<: *ubuntu_env + +build:debian: + <<: *build + <<: *debian_env + +build:stable: + <<: *build + <<: *stable_env + +build:no-extras: + <<: *build + <<: *no_extras_env + +build:latest: + <<: *build + <<: *latest_env + +build:bleeding-edge: + <<: *build + <<: *bleeding_edge_env + only: + - schedules + +## Test Jobs + check whitespace style: - stage: build + stage: test script: ./check_whitespace allow_failure: true - check for dependencies installed: stage: test script: @@ -35,10 +97,63 @@ check for dependencies installed: allow_failure: true +.test: &test + stage: test + script: + - py.test -r w --cov=kwant --cov-report term --cov-report html --flakes kwant --junitxml=tests.xml + artifacts: + paths: + - htmlcov + reports: + junit: tests.xml + + +test:stable: + <<: *test + <<: *stable_env + dependencies: + - build:stable + +test:no-extras: + <<: *test + <<: *no_extras_env + dependencies: + - build:no-extras + +test:ubuntu: + <<: *test + <<: *ubuntu_env + dependencies: + - build:ubuntu + +test:debian: + <<: *test + <<: *debian_env + dependencies: + - build:debian + +test:latest: + <<: *test + <<: *latest_env + dependencies: + - build:latest + +test:bleeding-edge: + <<: *test + <<: *bleeding_edge_env + dependencies: + - build:bleeding-edge + only: + - schedules + +## Documentation building + build documentation: + <<: *latest_env + dependencies: + - build:latest stage: test script: - - pip3 install --upgrade matplotlib - make -C doc realclean; make -C doc html SPHINXOPTS='-A website_deploy=True -n -W' SOURCE_LINK_TEMPLATE="$CI_PROJECT_URL"/blob/\$\$r/\$\$f artifacts: paths: @@ -46,10 +161,11 @@ build documentation: expire_in: 1 month build PDF documentation: + <<: *latest_env + dependencies: + - build:latest stage: test script: - - pip3 install sympy - - pip3 install --upgrade matplotlib - make -C doc latex SPHINXOPTS='-n -W' - cd doc/build/latex - make all-pdf @@ -58,24 +174,18 @@ build PDF documentation: - doc/build/latex/kwant.pdf expire_in: 1 month -run tests: - stage: test - script: - - py.test -r w --cov=kwant --cov-report term --cov-report html --flakes kwant --junitxml=tests.xml - artifacts: - paths: - - htmlcov - reports: - junit: tests.xml - - check for broken links in doc: + <<: *latest_env + dependencies: + - build:latest stage: test script: - make -C doc linkcheck allow_failure: true +## Conda package building + .conda-template: &conda_job stage: deploy image: condaforge/linux-anvil @@ -109,6 +219,7 @@ build and upload conda package (manual): - master@kwant/kwant when: manual +## Upload coverage reports and dev documentation upload coverage: stage: deploy @@ -196,7 +307,8 @@ upload dev version docs: after_script: - rm -rf ~/.ssh -# tagged version deploy + +## Build documentation for tagged releases .tagged-version: &tagged_version only: diff --git a/kwant/continuum/_common.py b/kwant/continuum/_common.py index 3c724eba16a28d1602c718aa90c1949c9a951d6e..e5c2f3f4caa83edc46f1b5942b8f08652a406e4c 100644 --- a/kwant/continuum/_common.py +++ b/kwant/continuum/_common.py @@ -158,7 +158,15 @@ def sympify(expr, locals=None): '"locals" will not be used.', RuntimeWarning, stacklevel=2) - return expr + + # We assume that all present functions, like "sin", "cos", will be + # provided by user during the final evaluation through "params". + # Therefore we make sure they are defined as AppliedUndef, not built-in + # sympy types. + subs = {r: sympy.Function(str(r.func))(*r.args) + for r in expr.atoms(sympy.Function)} + + return expr.subs(subs) # if ``expr`` is not a "sympy" then we proceed with sympifying process if locals is None: @@ -189,14 +197,6 @@ def sympify(expr, locals=None): else: del converter[list] - # We assume that all present functions, like "sin", "cos", will be - # provided by user during the final evaluation through "params". - # Therefore we make sure they are define as AppliedUndef, not built-in - # sympy types. - subs = {r: sympy.Symbol(str(r.func))(*r.args) - for r in hamiltonian.atoms(sympy.Function)} - - hamiltonian = hamiltonian.subs(subs) return hamiltonian diff --git a/kwant/continuum/discretizer.py b/kwant/continuum/discretizer.py index 722ea45e3b7877183e36b6ec9655634d57d0cf6b..53f2379cd53f17588dc9e59cb0300a11e90c54c3 100644 --- a/kwant/continuum/discretizer.py +++ b/kwant/continuum/discretizer.py @@ -502,9 +502,16 @@ def _discretize_expression(expression, coords): class _NumericPrinter(LambdaPrinter): def __init__(self): - LambdaPrinter.__init__(self) + if 'allow_unknown_functions' in LambdaPrinter._default_settings: + settings = {'allow_unknown_functions': True} + else: + # We're on Sympy without "allow_unknown_functions" setting + settings = {} + + LambdaPrinter.__init__(self, settings=settings) + self.known_functions = {} - self.known_constants = {'pi': 'pi', 'Pi': 'pi'} + self.known_constants = {'pi': 'pi', 'Pi': 'pi', 'I': 'I'} def _print_ImaginaryUnit(self, expr): # prevent sympy from printing 'I' for imaginary unit @@ -678,7 +685,10 @@ def _builder_value(expr, coords, grid_spacing, onsite, header = 'def {}({}):'.format(name, site_string) func_code = separator.join([header] + list(lines)) - namespace = {'pi': np.pi} + # Add "I" to namespace just in case sympy again would miss to replace it + # with Python's 1j as it was the case with SymPy 1.2 when I was argument + # of some function. + namespace = {'pi': np.pi, 'I': 1j} namespace.update(_cache) source = [] diff --git a/kwant/continuum/tests/test_common.py b/kwant/continuum/tests/test_common.py index ee941b58c927e81f8e3d96781389c226d5790b6d..74d87746e242637a24a9fe1690962047e99081e5 100644 --- a/kwant/continuum/tests/test_common.py +++ b/kwant/continuum/tests/test_common.py @@ -20,17 +20,18 @@ from kwant.continuum._common import lambdify com_A, com_B, com_C = sympy.symbols('A B C') +fA, fB, fC = sympy.symbols('A B C', cls=sympy.Function) x_op, y_op, z_op = position_operators kx, ky, kz = momentum_operators @pytest.mark.parametrize('input_expr, output_expr', [ - ('k_x * A(x) * k_x', kx * com_A(x_op) * kx), - ('[[k_x * A(x) * k_x]]', sympy.Matrix([kx * com_A(x_op) * kx])), + ('k_x * A(x) * k_x', kx * fA(x_op) * kx), + ('[[k_x * A(x) * k_x]]', sympy.Matrix([kx * fA(x_op) * kx])), ('k_x * sigma_y + k_y * sigma_x', kx * msigma(2) + ky * msigma(1)), ('[[k_x*A(x)*k_x, B(x, y)*k_x], [k_x*B(x, y), C*k_y**2]]', - sympy.Matrix([[kx*com_A(x_op)*kx, com_B(x_op, y_op)*kx], - [kx*com_B(x_op, y_op), com_C*ky**2]])), + sympy.Matrix([[kx*fA(x_op)*kx, fB(x_op, y_op)*kx], + [kx*fB(x_op, y_op), com_C*ky**2]])), ('kron(sigma_x, sigma_y)', TensorProduct(msigma(1), msigma(2))), ('identity(2)', sympy.eye(2)), ('eye(2)', sympy.eye(2)), @@ -46,7 +47,7 @@ def test_sympify(input_expr, output_expr): ('k_x', kx + ky, {'k_x': 'k_x + k_y'}), ('x', x_op + y_op, {'x': 'x + y'}), ('A', com_A + com_B, {'A': 'A + B'}), - ('A', com_A + com_B(x_op), {'A': 'A + B(x)'}), + ('A', com_A + fB(x_op), {'A': 'A + B(x)'}), ('A', msigma(2), {'A': "[[0, -1j], [1j, 0]]"}), ]) def test_sympify_substitutions(input_expr, output_expr, subs): @@ -102,10 +103,11 @@ def test_make_commutative(): matr_monomials = sympify("[[x+y, a*x**2 + b*y], [y, x]]") x, y, z = position_operators a, b = sympy.symbols('a, b') +fA, fB = sympy.symbols('A B', cls=sympy.Function) @pytest.mark.parametrize('expr, gens, output', [ - (x * a(x) * x + x**2 * a, None, {x**2: a(x), a*x**2: 1}), - (x * a(x) * x + x**2 * a, [x], {x**2: a(x) + a}), + (x * fA(x) * x + x**2 * a, None, {x**2: fA(x), a*x**2: 1}), + (x * fA(x) * x + x**2 * a, [x], {x**2: fA(x) + a}), (x**2, [x], {x**2: 1}), (2 * x + 3 * x**2, [x], {x: 2, x**2: 3}), (2 * x + 3 * x**2, 'x', {x: 2, x**2: 3}), diff --git a/kwant/continuum/tests/test_discretizer.py b/kwant/continuum/tests/test_discretizer.py index e8dfd4dce9dc16804529b55e7aff1f4a8b01abd7..b8953e6b860f11d10ed9e68b1d6bd1736e8baba3 100644 --- a/kwant/continuum/tests/test_discretizer.py +++ b/kwant/continuum/tests/test_discretizer.py @@ -51,6 +51,7 @@ a = sympy.symbols('a') wf = _wf Psi = wf(x, y, z) A, B = sympy.symbols('A B', commutative=False) +fA, fB = sympy.symbols('A B', cls=sympy.Function) ns = {'A': A, 'B': B, 'a_x': ax, 'a_y': ay, 'az': az, 'x': x, 'y': y, 'z': z} @@ -65,8 +66,8 @@ def test_reading_coordinates(commutative): kx**2 + ky**2 + kz**2 : ['x', 'y', 'z'], ky**2 + kz**2 : ['y', 'z'], kz**2 : ['z'], - kx * A(x, y) * kx : ['x'], - kx**2 + kz * B(y) : ['x', 'z'], + kx * fA(x, y) * kx : ['x'], + kx**2 + kz * fB(y) : ['x', 'z'], } for inp, out in test.items(): ham, got = discretize_symbolic(inp) @@ -81,8 +82,8 @@ def test_reading_coordinates_matrix(): (sympy.Matrix([kx**2 + ky**2 + kz**2]) , ['x', 'y', 'z']), (sympy.Matrix([ky**2 + kz**2]) , ['y', 'z']), (sympy.Matrix([kz**2]) , ['z']), - (sympy.Matrix([kx * A(x, y) * kx]) , ['x']), - (sympy.Matrix([kx**2 + kz * B(y)]) , ['x', 'z']), + (sympy.Matrix([kx * fA(x, y) * kx]) , ['x']), + (sympy.Matrix([kx**2 + kz * fB(y)]) , ['x', 'z']), ] for inp, out in test: ham, got = discretize_symbolic(inp) @@ -121,16 +122,16 @@ def test_simple_derivations(commutative): kz**2 : {(0,): 2/az**2, (1,): -1/az**2}, } non_commutative_test = { - kx * A(x, y) * kx : {(1, ): -A(ax/2 + x, y)/ax**2, - (0, ): A(-ax/2 + x, y)/ax**2 + A(ax/2 + x, y)/ax**2}, - kx**2 + kz * B(y) : {(1, 0): -1/ax**2, (0, 1): -I*B(y)/(2*az), + kx * fA(x, y) * kx : {(1, ): -fA(ax/2 + x, y)/ax**2, + (0, ): fA(-ax/2 + x, y)/ax**2 + fA(ax/2 + x, y)/ax**2}, + kx**2 + kz * fB(y) : {(1, 0): -1/ax**2, (0, 1): -I*fB(y)/(2*az), (0, 0): 2/ax**2}, - kx * A(x) : {(0,): 0, (1,): -I*A(ax + x)/(2*ax)}, - ky * A(x) : {(1,): -I*A(x)/(2*ay), (0,): 0}, - kx * A(x) * B : {(0,): 0, (1,): -I*A(ax + x)*B/(2*ax)}, + kx * fA(x) : {(0,): 0, (1,): -I*fA(ax + x)/(2*ax)}, + ky * fA(x) : {(1,): -I*fA(x)/(2*ay), (0,): 0}, + kx * fA(x) * B : {(0,): 0, (1,): -I*fA(ax + x)*B/(2*ax)}, 5 * kx : {(0,): 0, (1,): -5*I/(2*ax)}, - kx * (A(x) + B(x)) : {(0,): 0, - (1,): -I*A(ax + x)/(2*ax) - I*B(ax + x)/(2*ax)}, + kx * (fA(x) + fB(x)) : {(0,): 0, + (1,): -I*fA(ax + x)/(2*ax) - I*fB(ax + x)/(2*ax)}, } if not commutative: @@ -170,16 +171,16 @@ def test_simple_derivations_matrix(): (1, 0): -1/ay**2}, kz**2 : {(0,): 2/az**2, (1,): -1/az**2}, - kx * A(x, y) * kx : {(1, ): -A(ax/2 + x, y)/ax**2, - (0, ): A(-ax/2 + x, y)/ax**2 + A(ax/2 + x, y)/ax**2}, - kx**2 + kz * B(y) : {(1, 0): -1/ax**2, (0, 1): -I*B(y)/(2*az), + kx * fA(x, y) * kx : {(1, ): -fA(ax/2 + x, y)/ax**2, + (0, ): fA(-ax/2 + x, y)/ax**2 + fA(ax/2 + x, y)/ax**2}, + kx**2 + kz * fB(y) : {(1, 0): -1/ax**2, (0, 1): -I*fB(y)/(2*az), (0, 0): 2/ax**2}, - kx * A(x) : {(0,): 0, (1,): -I*A(ax + x)/(2*ax)}, - ky * A(x) : {(1,): -I*A(x)/(2*ay), (0,): 0}, - kx * A(x) * B : {(0,): 0, (1,): -I*A(ax + x)*B/(2*ax)}, + kx * fA(x) : {(0,): 0, (1,): -I*fA(ax + x)/(2*ax)}, + ky * fA(x) : {(1,): -I*fA(x)/(2*ay), (0,): 0}, + kx * fA(x) * B : {(0,): 0, (1,): -I*fA(ax + x)*B/(2*ax)}, 5 * kx : {(0,): 0, (1,): -5*I/(2*ax)}, - kx * (A(x) + B(x)) : {(0,): 0, - (1,): -I*A(ax + x)/(2*ax) - I*B(ax + x)/(2*ax)}, + kx * (fA(x) + fB(x)) : {(0,): 0, + (1,): -I*fA(ax + x)/(2*ax) - I*fB(ax + x)/(2*ax)}, } new_test = [] @@ -296,10 +297,10 @@ def test_different_discrete_coordinates(): def test_non_expended_input(): - symbolic, coords = discretize_symbolic(kx * (kx + A(x))) + symbolic, coords = discretize_symbolic(kx * (kx + fA(x))) desired = { (0,): 2/ax**2, - (1,): -I*A(ax + x)/(2*ax) - 1/ax**2 + (1,): -I*fA(ax + x)/(2*ax) - 1/ax**2 } assert symbolic == desired @@ -308,8 +309,8 @@ def test_matrix_with_zeros(): Matrix = sympy.Matrix symbolic, _ = discretize_symbolic("[[k_x*A(x)*k_x, 0], [0, k_x*A(x)*k_x]]") output = { - (0,): Matrix([[A(-ax/2 + x)/ax**2 + A(ax/2 + x)/ax**2, 0], [0, A(-ax/2 + x)/ax**2 + A(ax/2 + x)/ax**2]]), - (1,): Matrix([[-A(ax/2 + x)/ax**2, 0], [0, -A(ax/2 + x)/ax**2]]), + (0,): Matrix([[fA(-ax/2 + x)/ax**2 + fA(ax/2 + x)/ax**2, 0], [0, fA(-ax/2 + x)/ax**2 + fA(ax/2 + x)/ax**2]]), + (1,): Matrix([[-fA(ax/2 + x)/ax**2, 0], [0, -fA(ax/2 + x)/ax**2]]), } assert symbolic == output @@ -354,20 +355,26 @@ def test_numeric_functions_not_discrete_coords(): assert onsite(None, k_y=2, y=1) == 2 + 1 -def test_numeric_functions_with_pi(): - # Two cases because once it is casted - # to complex, one there is a function created - - builder = discretize('A + pi', 'x') - lat = next(iter(builder.sites()))[0] - onsite = builder[lat(0)] - assert onsite(None, A=1) == 1 + np.pi - - - builder = discretize('pi', 'x') +@pytest.mark.parametrize('ham, val, params', [ + ("pi", np.pi, {}), + ("A + pi", 1 + np.pi, {"A": 1}), + ("A + B(pi)", 1 + np.pi, {"A": 1, "B": lambda x: x}), + ("A + I", 1 + 1j, {"A": 1}), + ("A + 1j", 1 + 1j, {"A": 1}), + ("A + B(I)", 1 + 1j, {"A": 1, "B": lambda x: x}), + ("A + B(1j)", 1 + 1j, {"A": 1, "B": lambda x: x}), + ("exp(1j * pi)", np.exp(1j*np.pi), {"exp": np.exp}), + (sympy.exp(sympy.sympify("1j * pi * A")), np.exp(1j*np.pi), + {"exp": np.exp, "A": 1}), +]) +def test_numeric_functions_advanced(ham, val, params): + builder = discretize(ham, 'x') lat = next(iter(builder.sites()))[0] onsite = builder[lat(0)] - assert onsite == np.pi + try: + assert np.allclose(onsite(None, **params), val) + except TypeError: + assert np.allclose(onsite, val) def test_numeric_functions_basic_string(): @@ -422,9 +429,9 @@ def test_numeric_functions_advance(): hams = [ kx**2, kx**2 + x, - A(x), - kx*A(x)*kx, - sympy.Matrix([[kx * A(x) * kx, A(x)*kx], [kx*A(x), A(x)+B]]), + fA(x), + kx*fA(x)*kx, + sympy.Matrix([[kx * fA(x) * kx, fA(x)*kx], [kx*fA(x), fA(x)+B]]), kx**2 + B * x, 'k_x**2 + sin(x)', B ** 0.5 * kx**2, @@ -434,12 +441,12 @@ def test_numeric_functions_advance(): ] for hamiltonian in hams: for a in [1, 2, 5]: - for fA in [lambda x: x, lambda x: x**2, lambda x: x**3]: + for func in [lambda x: x, lambda x: x**2, lambda x: x**3]: symbolic, coords = discretize_symbolic(hamiltonian, 'x') builder = build_discretized(symbolic, coords, grid=a) lat = next(iter(builder.sites()))[0] - p = dict(A=fA, B=5, sin=np.sin) + p = dict(A=func, B=5, sin=np.sin) # test onsite v = symbolic.pop((0,)).subs({sympy.symbols('a_x'): a, B: p['B']}) @@ -449,10 +456,10 @@ def test_numeric_functions_advance(): if callable(f_num): f_num = swallows_extra_kwargs(f_num) for n in range(-100, 100, 10): - assert np.allclose(f_sym(fA, a*n), f_num(lat(n), **p)) + assert np.allclose(f_sym(func, a*n), f_num(lat(n), **p)) else: for n in range(-100, 100, 10): - assert np.allclose(f_sym(fA, a*n), f_num) + assert np.allclose(f_sym(func, a*n), f_num) # test hoppings @@ -464,7 +471,7 @@ def test_numeric_functions_advance(): if callable(f_num): f_num = swallows_extra_kwargs(f_num) for n in range(10): - lhs = f_sym(fA, a * n) + lhs = f_sym(func, a * n) rhs = f_num(lat(n), lat(n+k[0]), **p) assert np.allclose(lhs, rhs) else: @@ -476,15 +483,15 @@ def test_numeric_functions_advance(): def test_numeric_functions_with_parameter(): - hamiltonian = kx**2 + A(B, x) + hamiltonian = kx**2 + fA(B, x) for a in [1, 2, 5]: - for fA in [lambda c, x: x+c, lambda c, x: x**2 + c]: + for func in [lambda c, x: x+c, lambda c, x: x**2 + c]: symbolic, coords = discretize_symbolic(hamiltonian, 'x') builder = build_discretized(symbolic, coords, grid=a) lat = next(iter(builder.sites()))[0] - p = dict(A=fA, B=5) + p = dict(A=func, B=5) # test onsite v = symbolic.pop((0,)).subs({sympy.symbols('a_x'): a, B: p['B']}) @@ -498,9 +505,9 @@ def test_numeric_functions_with_parameter(): s = lat(n) xi = a * n if callable(f_num): - assert np.allclose(f_sym(fA, xi), f_num(s, **p)) + assert np.allclose(f_sym(func, xi), f_num(s, **p)) else: - assert np.allclose(f_sym(fA, xi), f_num) + assert np.allclose(f_sym(func, xi), f_num) # test hoppings for k, v in symbolic.items(): @@ -515,7 +522,7 @@ def test_numeric_functions_with_parameter(): s = lat(n) xi = a * n - lhs = f_sym(fA, xi) + lhs = f_sym(func, xi) if callable(f_num): rhs = f_num(lat(n), lat(n+k[0]), **p) else: diff --git a/kwant/tests/test_plotter.py b/kwant/tests/test_plotter.py index eba1eded6fd0bc020eba9f3a840afaca6eb7584a..862dd829ade71069b5714b3c96e2799ec824284d 100644 --- a/kwant/tests/test_plotter.py +++ b/kwant/tests/test_plotter.py @@ -39,6 +39,7 @@ from kwant import plotter from kwant import _plotter # for mpl_available +@pytest.mark.skipif(not plotter.mpl_available, reason="Matplotlib unavailable.") def test_matplotlib_backend_unset(): """Simply importing Kwant should not set the matplotlib backend.""" assert matplotlib_backend_chosen is False