Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • kwant/kwant
  • jbweston/kwant
  • anton-akhmerov/kwant
  • cwg/kwant
  • Mathieu/kwant
  • slavoutich/kwant
  • pacome/kwant
  • behrmann/kwant
  • michaelwimmer/kwant
  • albeercik/kwant
  • eunjongkim/kwant
  • basnijholt/kwant
  • r-j-skolasinski/kwant
  • sahmed95/kwant
  • pablopiskunow/kwant
  • mare/kwant
  • dvarjas/kwant
  • Paul/kwant
  • bbuijtendorp/kwant
  • tkloss/kwant
  • torosdahl/kwant
  • kel85uk/kwant
  • kpoyhonen/kwant
  • Fromeworld/kwant
  • quaeritis/kwant
  • marwahaha/kwant
  • fernandodfufrpe/kwant
  • oly/kwant
  • jiamingh/kwant
  • mehdi2369/kwant
  • ValFadeev/kwant
  • Kostas/kwant
  • chelseabaptiste03/kwant
33 results
Show changes
# Copyright 2011-2018 Kwant authors.
#
# This file is part of Kwant. It is subject to the license terms in the file
# LICENSE.rst found in the top-level directory of this distribution and at
# https://kwant-project.org/license. A list of Kwant authors can be found in
# the file AUTHORS.rst at the top-level directory of this distribution and at
# https://kwant-project.org/authors.
import collections
import inspect
import cmath
import warnings
import tinyarray as ta
import numpy as np
import scipy.linalg
import scipy.spatial
from . import builder, system, plotter
from .linalg import lll
from .builder import herm_conj, HermConjOfFunc
from .lattice import TranslationalSymmetry
from ._common import get_parameters, memoize
__all__ = ['wraparound', 'plot_2d_bands']
def _set_signature(func, params):
"""Set the signature of 'func'.
Parameters
----------
func : callable
params: sequence of str
Parameter names to put in the signature. These will be added as
'POSITIONAL_ONLY' type parameters.
"""
params = [inspect.Parameter(name, inspect.Parameter.POSITIONAL_ONLY)
for name in params]
func.__signature__ = inspect.Signature(params)
## This wrapper is needed so that finalized systems that
## have been wrapped can be queried for their symmetry, which
## is needed for Brillouin zone calculations (plotting).
class WrappedBuilder(builder.Builder):
def finalized(self):
ret = super().finalized()
ret._momentum_names = self._momentum_names
ret._wrapped_symmetry = self._wrapped_symmetry
return ret
def wraparound(builder, keep=None, *, coordinate_names='xyz'):
"""Replace translational symmetries by momentum parameters.
A new Builder instance is returned. By default, each symmetry is replaced
by one scalar momentum parameter that is appended to the already existing
arguments of the system. Optionally, one symmetry may be kept by using the
`keep` argument. The momentum parameters will have names like 'k_n' where
the 'n' are specified by 'coordinate_names'.
Parameters
----------
builder : `~kwant.builder.Builder`
keep : int, optional
Which (if any) translational symmetry to keep.
coordinate_names : sequence of strings, default: 'xyz'
The names of the coordinates along the symmetry
directions of 'builder'.
Notes
-----
Wraparound is stop-gap functionality until Kwant 2.x which will include
support for higher-dimension translational symmetry in the low-level system
format. It will be deprecated in the 2.0 release of Kwant.
"""
@memoize
def bind_site(val):
def f(*args):
a, *args = args
return val(a, *args[:mnp])
assert callable(val)
_set_signature(f, get_parameters(val) + momenta)
return f
@memoize
def bind_hopping_as_site(elem, val):
def f(*args):
a, *args = args
phase = cmath.exp(1j * ta.dot(elem, args[mnp:]))
v = val(a, sym.act(elem, a), *args[:mnp]) if callable(val) else val
pv = phase * v
return pv + herm_conj(pv)
params = ('_site0',)
if callable(val):
params += get_parameters(val)[2:] # cut off both site parameters
_set_signature(f, params + momenta)
return f
@memoize
def bind_hopping(elem, val):
def f(*args):
a, b, *args = args
phase = cmath.exp(1j * ta.dot(elem, args[mnp:]))
v = val(a, sym.act(elem, b), *args[:mnp]) if callable(val) else val
return phase * v
params = ('_site0', '_site1')
if callable(val):
params += get_parameters(val)[2:] # cut off site parameters
_set_signature(f, params + momenta)
return f
@memoize
def bind_sum(num_sites, *vals):
"""Construct joint signature for all 'vals'."""
def f(*in_args):
acc = 0
for val, selection in val_selection_pairs:
if selection: # Otherwise: reuse previous out_args.
out_args = tuple(in_args[i] for i in selection)
if callable(val):
acc = acc + val(*out_args)
else:
acc = acc + val
return acc
params = collections.OrderedDict()
# Add the leading one or two 'site' parameters.
site_params = ['_site{}'.format(i) for i in range(num_sites)]
for name in site_params:
params[name] = inspect.Parameter(
name, inspect.Parameter.POSITIONAL_ONLY)
# Add all the other parameters (except for the momenta). Setup the
# 'selections'.
selections = []
for val in vals:
if not callable(val):
selections.append(())
continue
val_params = get_parameters(val)[num_sites:]
assert val_params[mnp:] == momenta
val_params = val_params[:mnp]
selections.append((*site_params, *val_params))
for p in val_params:
# Skip parameters that exist in previously added functions.
if p in params:
continue
params[p] = inspect.Parameter(
p, inspect.Parameter.POSITIONAL_ONLY)
# Sort values such that ones with the same arguments are bunched.
# Prepare 'val_selection_pairs' that is used in the function 'f' above.
params_keys = list(params.keys())
val_selection_pairs = []
prev_selection = None
argsort = sorted(range(len(selections)), key=selections.__getitem__)
momenta_sel = tuple(range(mnp, 0, 1))
for i in argsort:
selection = selections[i]
if selection and selection != prev_selection:
prev_selection = selection = tuple(
params_keys.index(s) for s in selection) + momenta_sel
else:
selection = ()
val_selection_pairs.append((vals[i], selection))
# Finally, add the momenta.
for k in momenta:
params[k] = inspect.Parameter(
k, inspect.Parameter.POSITIONAL_ONLY)
f.__signature__ = inspect.Signature(params.values())
return f
try:
momenta = ['k_{}'.format(coordinate_names[i])
for i in range(len(builder.symmetry.periods))]
except IndexError:
raise ValueError("All symmetry directions must have a name specified "
"in coordinate_names")
if keep is None:
ret = WrappedBuilder()
sym = builder.symmetry
else:
periods = list(builder.symmetry.periods)
ret = WrappedBuilder(TranslationalSymmetry(periods.pop(keep)))
sym = TranslationalSymmetry(*periods)
momenta.pop(keep)
momenta = tuple(momenta)
mnp = -len(momenta) # Used by the bound functions above.
# Store the names of the momentum parameters and the symmetry of the
# old Builder (this will be needed for band structure plotting)
ret._momentum_names = momenta
ret._wrapped_symmetry = builder.symmetry
# Wrapped around system retains conservation law and chiral symmetry.
# We use 'bind_site' to add the momenta arguments if required.
cons = builder.conservation_law
ret.conservation_law = bind_site(cons) if callable(cons) else cons
chiral = builder.chiral
ret.chiral = bind_site(chiral) if callable(chiral) else chiral
if builder.particle_hole is not None or builder.time_reversal is not None:
warnings.warn('`particle_hole` and `time_reversal` symmetries are set '
'on the input builder. However they are ignored for the '
'wrapped system, since Kwant lacks a way to express the '
'existence (or not) of a symmetry at k != 0.',
RuntimeWarning, stacklevel=2)
ret.particle_hole = None
ret.time_reversal = None
sites = {}
hops = collections.defaultdict(list)
# Store lists of values, so that multiple values can be assigned to the
# same site or hopping.
for site, val in builder.site_value_pairs():
# Every 'site' is in the FD of the original symmetry.
# Move the sites to the FD of the remaining symmetry, this guarantees that
# every site in the new system is an image of an original FD site translated
# purely by the remaining symmetry.
sites[ret.symmetry.to_fd(site)] = [bind_site(val) if callable(val) else val]
for hop, val in builder.hopping_value_pairs():
a, b = hop
# 'a' is in the FD of original symmetry.
# Get translation from FD of original symmetry to 'b',
# this is different from 'b_dom = sym.which(b)'.
b_dom = builder.symmetry.which(b)
# Throw away part that is in the remaining translation direction, so we get
# an element of 'sym' which is being wrapped
b_dom = ta.array([t for i, t in enumerate(b_dom) if i != keep])
# Pull back using the remainder, which is purely in the wrapped directions.
# This guarantees that 'b_wa' is an image of an original FD site translated
# purely by the remaining symmetry.
b_wa = sym.act(-b_dom, b)
# Move the hopping to the FD of the remaining symmetry
a, b_wa = ret.symmetry.to_fd(a, b_wa)
if a == b_wa:
# The hopping gets wrapped-around into an onsite Hamiltonian.
# Since site `a` already exists in the system, we can simply append.
sites[a].append(bind_hopping_as_site(b_dom, val))
else:
# The hopping remains a hopping.
if any(b_dom) or callable(val):
# The hopping got wrapped-around or is a function.
val = bind_hopping(b_dom, val)
# Make sure that there is only one entry for each hopping
# pointing in one direction, modulo the remaining translations.
b_wa_r, a_r = ret.symmetry.to_fd(b_wa, a)
if (b_wa_r, a_r) in hops:
assert (a, b_wa) not in hops
if callable(val):
assert not isinstance(val, HermConjOfFunc)
val = HermConjOfFunc(val)
else:
val = herm_conj(val)
hops[b_wa_r, a_r].append(val)
else:
hops[a, b_wa].append(val)
# Copy stuff into result builder, converting lists of more than one element
# into summing functions.
for site, vals in sites.items():
ret[site] = vals[0] if len(vals) == 1 else bind_sum(1, *vals)
for hop, vals in hops.items():
ret[hop] = vals[0] if len(vals) == 1 else bind_sum(2, *vals)
return ret
def plot_2d_bands(syst, k_x=31, k_y=31, params=None,
mask_brillouin_zone=False, extend_bbox=0, file=None,
show=True, dpi=None, fig_size=None, ax=None):
"""Plot 2D band structure of a wrapped around system.
This function is primarily useful for systems that have translational
symmetry vectors that are non-orthogonal (e.g. graphene). This function
will properly plot the band structure in an orthonormal basis in k-space,
as opposed to in the basis of reciprocal lattice vectors (which would
produce a "skewed" Brillouin zone).
If your system has orthogonal lattice vectors, you are probably better
off using `kwant.plotter.spectrum`.
Parameters
----------
syst : `kwant.system.FiniteSystem`
A 2D system that was finalized from a Builder produced by
`kwant.wraparound.wraparound`. Note that this *must* be a finite
system; so `kwant.wraparound.wraparound` should have been called with
``keep=None``.
k_x, k_y : int or sequence of float, default: 31
Either a number of sampling points, or a sequence of points at which
the band structure is to be evaluated, in units of inverse length.
params : dict, optional
Dictionary of parameter names and their values, not including the
momentum parameters.
mask_brillouin_zone : bool, default: False
If True, then the band structure will only be plotted over the first
Brillouin zone. By default the band structure is plotted over a
rectangular bounding box that contains the Brillouin zone.
extend_bbox : float, default: 0
Amount by which to extend the region over which the band structure is
plotted, expressed as a proportion of the Brillouin zone bounding box
length. i.e. ``extend_bbox=0.1`` will extend the region by 10% (in all
directions).
file : string or file object, optional
The output file. If None, output will be shown instead.
show : bool, default: False
Whether ``matplotlib.pyplot.show()`` is to be called, and the output is
to be shown immediately. Defaults to `True`.
dpi : float, optional
Number of pixels per inch. If not set the ``matplotlib`` default is
used.
fig_size : tuple, optional
Figure size `(width, height)` in inches. If not set, the default
``matplotlib`` value is used.
ax : ``matplotlib.axes.Axes`` instance, optional
If `ax` is not `None`, no new figure is created, but the plot is done
within the existing Axes `ax`. in this case, `file`, `show`, `dpi`
and `fig_size` are ignored.
Returns
-------
fig : matplotlib figure
A figure with the output if `ax` is not set, else None.
Notes
-----
This function produces plots where the units of momentum are inverse
length. This is contrary to `kwant.plotter.bands`, where the units
of momentum are inverse lattice constant.
If the lattice vectors for the symmetry of ``syst`` are not orthogonal,
then part of the plotted band structure will be outside the first Brillouin
zone (inside the bounding box of the brillouin zone). Setting
``mask_brillouin_zone=True`` will cause the plot to be truncated outside of
the first Brillouin zone.
See Also
--------
kwant.plotter.spectrum
"""
if not hasattr(syst, '_wrapped_symmetry'):
raise TypeError("Expecting a system that was produced by "
"'kwant.wraparound.wraparound'.")
if isinstance(syst, system.InfiniteSystem):
msg = ("All symmetry directions must be wrapped around: specify "
"'keep=None' when calling 'kwant.wraparound.wraparound'.")
raise TypeError(msg)
if isinstance(syst, builder.Builder):
msg = ("Expecting a finalized system: remember to finalize your "
"system with 'syst.finalized()'.")
raise TypeError(msg)
params = params or {}
lat_ndim, space_ndim = syst._wrapped_symmetry.periods.shape
if lat_ndim != 2:
raise ValueError("Expected a system with a 2D translational symmetry.")
if space_ndim != lat_ndim:
raise ValueError("Lattice dimension must equal realspace dimension.")
# columns of B are lattice vectors
B = np.array(syst._wrapped_symmetry.periods).T
# columns of A are reciprocal lattice vectors
A = np.linalg.pinv(B).T
## calculate the bounding box for the 1st Brillouin zone
# Get lattice points that neighbor the origin, in basis of lattice vectors
reduced_vecs, transf = lll.lll(A.T)
neighbors = ta.dot(lll.voronoi(reduced_vecs), transf)
# Add the origin to these points.
klat_points = np.concatenate(([[0] * lat_ndim], neighbors))
# Transform to cartesian coordinates and rescale.
# Will be used in 'outside_bz' function, later on.
klat_points = 2 * np.pi * np.dot(klat_points, A.T)
# Calculate the Voronoi cell vertices
vor = scipy.spatial.Voronoi(klat_points)
around_origin = vor.point_region[0]
bz_vertices = vor.vertices[vor.regions[around_origin]]
# extract bounding box
k_max = np.max(np.abs(bz_vertices), axis=0)
## build grid along each axis, if needed
ks = []
for k, km in zip((k_x, k_y), k_max):
k = np.array(k)
if not k.shape:
if extend_bbox:
km += km * extend_bbox
k = np.linspace(-km, km, k)
ks.append(k)
# TODO: It is very inefficient to call 'momentum_to_lattice' once for
# each point (for trivial Hamiltonians 60% of the time is spent
# doing this). We should instead transform the whole grid in one call.
def momentum_to_lattice(k):
k, residuals = scipy.linalg.lstsq(A, k)[:2]
if np.any(abs(residuals) > 1e-7):
raise RuntimeError("Requested momentum doesn't correspond"
" to any lattice momentum.")
return k
def ham(k_x, k_y=None, **params):
# transform into the basis of reciprocal lattice vectors
k = momentum_to_lattice([k_x] if k_y is None else [k_x, k_y])
p = dict(zip(syst._momentum_names, k), **params)
return syst.hamiltonian_submatrix(params=p, sparse=False)
def outside_bz(k_x, k_y, **_):
dm = scipy.spatial.distance_matrix(klat_points, [[k_x, k_y]])
return np.argmin(dm) != 0 # is origin no closest 'klat_point' to 'k'?
fig = plotter.spectrum(ham,
x=('k_x', ks[0]),
y=('k_y', ks[1]) if lat_ndim == 2 else None,
params=params,
mask=(outside_bz if mask_brillouin_zone else None),
file=file, show=show, dpi=dpi,
fig_size=fig_size, ax=ax)
return fig
{
"license": "https://creativecommons.org/publicdomain/zero/1.0/",
"usage-hints": [
"red-green-colorblind-safe",
"greyscale-safe",
"sequential"
],
"colors": "fcfbfdfcfafcfbf9fbfbf9f9faf8f8faf7f7f9f6f6f9f5f5f8f4f4f8f4f3f7f3f2f6f2f1f6f1eff5f0eef5efedf5efebf4eeeaf4ede9f4ece7f3ebe6f3eae4f3e9e3f2e9e2f2e8e0f2e7dff2e6ddf1e5dcf1e4daf1e3d9f0e2d8f0e2d6f0e1d5f0e0d3efdfd2efded0efddcfefdccdeedccceedbcaeedac9eed9c7edd8c6edd7c4edd7c3ecd6c1ecd5c0ecd4beecd3bdebd2bbebd2b9ebd1b8ead0b6eacfb5eaceb3eaceb2e9cdb0e9ccaee9cbade8caabe8caaae8c9a8e7c8a6e7c7a5e7c6a3e6c6a2e6c5a0e6c49ee5c39de5c39be5c299e4c198e4c096e4bf94e3bf93e3be91e3bd8fe2bc8ee2bc8ce1bb8ae1ba89e1ba87e0b985e0b884dfb782dfb780dfb67fdeb57ddeb47bddb47addb378ddb276dcb275dcb173dbb071dbaf6fdaaf6edaae6cdaad6ad9ad68d9ac67d8ab65d8ab63d7aa61d7a960d7a95ed6a85cd6a75ad5a659d5a657d5a555d4a453d4a351d4a34fd3a24dd3a14cd3a04ad3a048d29f46d29e44d29d42d29c40d29b3ed29b3cd29a3bd19939d19837d19735d19634d19532d19431d1932fd1922ed1912dd1902cd18f2bd18e2ad18d29d18c29d18b28d18a27d18927d18826d08726d08626d08525d08425d08425cf8325cf8225cf8124cf8024cf7f24ce7e24ce7d24ce7c24cd7b24cd7a24cd7a24cd7924cc7824cc7724cc7624cb7524cb7424cb7324cb7224ca7124ca7124ca7024c96f24c96e24c96d24c96c24c86b24c86a24c86924c76824c76824c76724c66624c66524c66424c56324c56224c56124c46024c45f24c45e24c45e23c35d23c35c23c35b23c25a23c25923c25823c15723c15623c15523c05423c05323c05223bf5123bf5023bf4f23be4e22be4d22be4c22bd4b22bd4a22bd4922bc4822bc4722bc4622bb4522bb4422ba4322ba4221ba4121b94021b93f21b93e21b83d21b83c21b83b21b73a21b73820b73720b63620b63520b53420b53320b53120b43020b42f20b42e1fb32c1fb32b1fb32a1fb2281fb2271fb1251fb1241fb1221fb0211eb01f1eb01d1eaf1c1eaf1a1eae181eae161eae131ead111dad0e1dac0b1dac081dac041d",
"colorspace": "sRGB",
"name": "kwant red",
"extensions": {
"https://matplotlib.org/viscm": {
"yp": [
-2.0153712378334347,
10.887492636952686,
28.27830916383833,
20.84513758379849,
15.655942329808425
],
"max_Jp": 41,
"cmtype": "linear",
"uniform_colorspace": "CAM02-UCS",
"spline_method": "CatmulClark",
"fixed": -1,
"filter_k": 100,
"min_Jp": 99,
"xp": [
-1.2674960029171558,
5.464432975232114,
4.482693332585356,
21.45276429833666,
33.65438557123224
]
}
},
"domain": "continuous",
"content-type": "application/vnd.matplotlib.colormap-v1+json"
}
[pytest]
testpaths = kwant
junit_family = xunit2
flakes-ignore =
__init__.py UnusedImport ImportStarUsed ImportStarUsage
kwant/_plotter.py UnusedImport
graph/tests/test_scotch.py UndefinedName
[metadata]
name = kwant
author = C. W. Groth (CEA), M. Wimmer, A. R. Akhmerov, X. Waintal (CEA), and others
author_email = authors@kwant-project.org
description = Kwant: a package for quantum transport calculations
long_description = file: README.rst
long_description_content_type = text/x-rst
url = https://kwant-project.org
license = BSD-2-Clause
license_files =
LICENSE.rst
platforms =
Unix
Linux
MacOS X
Windows
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Science/Research
Intended Audience :: Developers
Programming Language :: Python :: 3 :: Only
License :: OSI Approved :: BSD License
Topic :: Software Development
Topic :: Scientific/Engineering
Operating System :: POSIX
Operating System :: Unix
Operating System :: MacOS :: MacOS X
Operating System :: Microsoft :: Windows
[options]
python_requires = >=3.8
install_requires =
numpy >= 1.18.0
scipy >= 1.3.0, != 1.6.1
tinyarray >= 1.2.2
[options.extras_require]
plotting = matplotlib >= 3.2.2; plotly >= 2.7.0
continuum = sympy >= 1.5.1
qsymm = qsymm >= 1.2.6
mumps = python-mumps < 0.1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
#!/usr/bin/env python
#!/usr/bin/env python3
# Copyright 2011-2015 Kwant authors.
# Copyright 2011-2018 Kwant authors.
#
# This file is part of Kwant. It is subject to the license terms in the file
# LICENSE.rst found in the top-level directory of this distribution and at
# http://kwant-project.org/license. A list of Kwant authors can be found in
# https://kwant-project.org/license. A list of Kwant authors can be found in
# the file AUTHORS.rst at the top-level directory of this distribution and at
# http://kwant-project.org/authors.
# https://kwant-project.org/authors.
from __future__ import print_function
import sys
import re
import os
import glob
import imp
import importlib
import subprocess
import ConfigParser
from setuptools import setup, Extension, Command
from sysconfig import get_platform
from distutils.errors import DistutilsError, DistutilsModuleError, \
CCompilerError
from distutils.command.build import build
from setuptools.command.sdist import sdist
from setuptools.command.build_ext import build_ext
try:
import numpy
except ImportError:
include_dirs = []
import configparser
import argparse
from pathlib import Path
from ctypes.util import find_library
from setuptools import setup, find_packages, Extension
from setuptools.errors import CCompilerError, BaseError as DistutilsError
from setuptools.command.sdist import sdist as sdist_orig
from setuptools.command.build_ext import build_ext as build_ext_orig
# Packaging is a dependency of setuptools, so we are free to use it.
from packaging.version import Version, parse as parse_version
import setuptools
if parse_version(setuptools.__version__) < Version("63.0"):
# TODO: remove this once we depend on setuptools >= 63.0
os.environ['SETUPTOOLS_USE_DISTUTILS'] = 'local'
from distutils.command.build import build as build_orig
else:
include_dirs = [numpy.get_include()]
CONFIG_FILE = 'build.conf'
README_FILE = 'README.rst'
MANIFEST_IN_FILE = 'MANIFEST.in'
README_END_BEFORE = 'See also in this directory:'
STATIC_VERSION_PATH = ('kwant', '_kwant_version.py')
REQUIRED_CYTHON_VERSION = (0, 22)
NO_CYTHON_OPTION = '--no-cython'
TUT_DIR = 'tutorial'
TUT_GLOB = 'doc/source/tutorial/*.py'
TUT_HIDDEN_PREFIX = '#HIDDEN'
# Let Kwant itself determine its own version. We cannot simply import kwant, as
# it is not built yet.
_dont_write_bytecode_saved = sys.dont_write_bytecode
sys.dont_write_bytecode = True
try:
imp.load_source(STATIC_VERSION_PATH[-1].split('.')[0],
os.path.join(*STATIC_VERSION_PATH))
except IOError:
pass
_common = imp.load_source('_common', 'kwant/_common.py')
sys.dont_write_bytecode = _dont_write_bytecode_saved
version = _common.version
version_is_from_git = _common.version_is_from_git
try:
sys.argv.remove(NO_CYTHON_OPTION)
cythonize = False
except ValueError:
cythonize = True
if cythonize:
from setuptools.command.build import build as build_orig
STATIC_VERSION_PATH = 'kwant/_kwant_version.py'
distr_root = Path(__file__).resolve().parent
def configure_extensions(exts, aliases=(), build_summary=None):
"""Modify extension configuration according to the configuration file
`exts` must be a dict of (name, kwargs) tuples that can be used like this:
`Extension(name, **kwargs). This function modifies the kwargs according to
the configuration file.
This function removes `--configfile` from `sys.argv`.
"""
global config_file, config_file_present
#### Determine the name of the configuration file.
default_config_file = Path('build.conf')
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--configfile', type=argparse.FileType('r'))
known, unknown = parser.parse_known_args()
config_file = known.configfile
sys.argv = [sys.argv[0]] + unknown
if config_file is None and default_config_file.is_file():
config_file = default_config_file.open()
#### Read build configuration file.
configs = configparser.ConfigParser()
if (config_file_present := config_file is not None):
configs.read_file(config_file)
config_file = config_file.name if config_file is not None else 'build.conf'
#### Handle section aliases.
for short, long in aliases:
if short in configs:
if long in configs:
sys.exit(
f'Error: both {short} and {long} '
f'sections present in {config_file}.'
)
configs[long] = configs[short]
del configs[short]
#### Apply config from file. Use [DEFAULT] section for missing sections.
defaultconfig = configs.defaults()
for name, kwargs in exts.items():
config = configs[name] if name in configs else defaultconfig
for key, value in config.items():
# Most, but not all, keys are lists of strings
if key == 'language':
pass
elif key == 'optional':
value = bool(int(value))
else:
value = value.split()
if key == 'define_macros':
value = [tuple(entry.split('=', maxsplit=1))
for entry in value]
value = [(entry[0], None) if len(entry) == 1 else entry
for entry in value]
if key in kwargs:
msg = 'Caution: user config in file {} shadows {}.{}.'
if build_summary is not None:
build_summary.append(msg.format(config_file, name, key))
kwargs[key] = value
kwargs.setdefault('depends', []).append(config_file)
if config is not defaultconfig:
del configs[name]
if (unknown_sections := ", ".join(configs.sections())):
sys.exit(f"Unknown sections in {config_file}: {unknown_sections}")
return exts
def check_versions():
global version, version_is_from_git
# Let Kwant itself determine its own version. We cannot simply import
# kwant, as it is not built yet.
spec = importlib.util.spec_from_file_location('version', 'kwant/version.py')
version_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(version_module)
version_module.ensure_python()
version = version_module.version
version_is_from_git = version_module.version_is_from_git
def init_cython():
"""Set the global variable `cythonize` (and other related globals).
The variable `cythonize` can be in three states:
* If Cython should be run and is ready, it contains the `cythonize()`
function.
* If Cython is not to be run, it contains `False`.
* If Cython should, but cannot be run it contains `None`. A help message
on how to solve the problem is stored in `cython_help`.
This function removes `--cython` from `sys.argv`.
"""
global cythonize, cython_help
cython_option = '--cython'
required_cython_version = Version("3.0")
try:
import Cython
except:
cython_version = ()
sys.argv.remove(cython_option)
cythonize = True
except ValueError:
cythonize = version_is_from_git
if cythonize:
try:
import Cython
from Cython.Build import cythonize
except ImportError:
cythonize = None
else:
if parse_version(Cython.__version__) < required_cython_version:
cythonize = None
if cythonize is None:
cython_help = (f"Install Cython >= {required_cython_version} or use"
" a source distribution (tarball) of Kwant.")
else:
match = re.match('([0-9.]*)(.*)', Cython.__version__)
cython_version = [int(n) for n in match.group(1).split('.')]
# Decrease version if the version string contains a suffix.
if match.group(2):
while cython_version[-1] == 0:
cython_version.pop()
cython_version[-1] -= 1
cython_version = tuple(cython_version)
cython_help = f"Run setup.py with the {cython_option} option to enable Cython."
distr_root = os.path.dirname(os.path.abspath(__file__))
def banner(title=''):
starred = title.center(79, '*')
return '\n' + starred if title else starred
error_msg = """{header}
The compilation of Kwant has failed. Please examine the error message
above and consult the installation instructions in README.rst.
You might have to customize {{file}}.
Build configuration was:
{{summary}}
{sep}
"""
error_msg = error_msg.format(header=banner(' Error '), sep=banner())
class kwant_build_ext(build_ext):
class build_ext(build_ext_orig):
def run(self):
if not config_file_present:
# Create an empty config file if none is present so that the
# extensions will not be rebuilt each time. Only depending on the
# config file if it is present would make it impossible to detect a
# necessary rebuild due to a deleted config file.
with open(CONFIG_FILE, 'w') as f:
f.write('# Created by setup.py - feel free to modify.\n')
with open(config_file, 'w') as f:
f.write('# Build configuration created by setup.py '
'- feel free to modify.\n')
try:
build_ext.run(self)
super().run()
except (DistutilsError, CCompilerError):
print(error_msg.format(file=CONFIG_FILE, summary=build_summary),
error_msg = self.__error_msg.format(
header=banner(' Error '), sep=banner())
print(error_msg.format(file=config_file, summary=build_summary),
file=sys.stderr)
raise
print(banner(' Build summary '))
print(build_summary)
print(banner(' Build summary '), *build_summary, sep='\n')
print(banner())
__error_msg = """{header}
The compilation of Kwant has failed. Please examine the error message
above and consult the installation instructions in README.rst.
You might have to customize {{file}}.
class kwant_build_tut(Command):
description = "build the tutorial scripts"
user_options = []
def initialize_options(self):
pass
Build configuration was:
def finalize_options(self):
pass
{{summary}}
{sep}
"""
def run(self):
if not os.path.exists(TUT_DIR):
os.mkdir(TUT_DIR)
for in_fname in glob.glob(TUT_GLOB):
out_fname = os.path.join(TUT_DIR, os.path.basename(in_fname))
with open(in_fname) as in_file:
with open(out_fname, 'w') as out_file:
for line in in_file:
if not line.startswith(TUT_HIDDEN_PREFIX):
out_file.write(line)
# Our version of the "build" command also makes sure the tutorial is made.
# Even though the tutorial is not necessary for installation, and "build" is
# supposed to make everything needed to install, this is a robust way to ensure
# that the tutorial is present.
class kwant_build(build):
sub_commands = [('build_tut', None)] + build.sub_commands
class build(build_orig):
def run(self):
build.run(self)
write_version(os.path.join(self.build_lib, *STATIC_VERSION_PATH))
super().run()
write_version(Path(self.build_lib) / STATIC_VERSION_PATH)
def git_lsfiles():
......@@ -165,34 +229,32 @@ def git_lsfiles():
if p.wait() != 0:
return
return p.communicate()[0].split('\n')[:-1]
return p.communicate()[0].decode().split('\n')[:-1]
# Make the command "sdist" depend on "build". This verifies that the
# distribution in the current state actually builds. It also makes sure that
# the Cython-made C files and the tutorial will be included in the source
# distribution and that they will be up-to-date.
class kwant_sdist(sdist):
sub_commands = [('build', None)] + sdist.sub_commands
# the Cython-made C files will be up-to-date and included in the source.
class sdist(sdist_orig):
sub_commands = [('build', None)] + sdist_orig.sub_commands
def run(self):
"""
Create MANIFEST.in from git if possible, otherwise check that MANIFEST.in
is present.
"""Create MANIFEST.in from git if possible, otherwise check that
MANIFEST.in is present.
Right now (2015) generating MANIFEST.in seems to be the only way to
include files in the source distribution that setuptools does not think
should be there. Setting include_package_data to True makes setuptools
include *.pyx and other source files in the binary distribution.
"""
manifest = os.path.join(distr_root, MANIFEST_IN_FILE)
manifest_in_file = 'MANIFEST.in'
manifest = distr_root / manifest_in_file
names = git_lsfiles()
if names is None:
if not (os.path.isfile(manifest) and os.access(manifest, os.R_OK)):
print("Error:", MANIFEST_IN_FILE,
"file is missing and Git is not available"
" to regenerate it.", file=sys.stderr)
exit(1)
if not (manifest.is_file() and os.access(manifest, os.R_OK)):
sys.exit(f"Error: {manifest_in_file}"
"file is missing and Git is not available"
" to regenerate it.")
else:
with open(manifest, 'w') as f:
for name in names:
......@@ -205,260 +267,213 @@ class kwant_sdist(sdist):
f.write(''.join([' ', a, sep, stem, dot, 'c']))
f.write('\n')
sdist.run(self)
super().run()
if names is None:
print(banner(' Warning '),
"""Git was not available to generate the list of files to be included in the
source distribution. The old MANIFEST.in was used.""",
banner(),
sep='\n', file=sys.stderr)
msg = ("Git was not available to generate the list of files to be "
"included in the\nsource distribution. The old {} was used.")
msg = msg.format(manifest_in_file)
print(banner(' Caution '), msg, banner(), sep='\n', file=sys.stderr)
def make_release_tree(self, base_dir, files):
sdist.make_release_tree(self, base_dir, files)
write_version(os.path.join(base_dir, *STATIC_VERSION_PATH))
super().make_release_tree(base_dir, files)
write_version(Path(base_dir) / STATIC_VERSION_PATH)
def write_version(fname):
def write_version(path):
# This could be a hard link, so try to delete it first. Is there any way
# to do this atomically together with opening?
try:
os.remove(fname)
except OSError:
pass
with open(fname, 'w') as f:
f.write("# This file has been created by setup.py.\n")
f.write("version = '{}'\n".format(version))
def long_description():
text = []
try:
with open(README_FILE) as f:
for line in f:
if line.startswith(README_END_BEFORE):
break
text.append(line.rstrip())
while text[-1] == "":
text.pop()
except:
return ''
return '\n'.join(text)
def packages():
return [root.replace('/', '.')
for root, dnames, fnames in os.walk('kwant')
if '__init__.py' in fnames or root.endswith('/tests')]
path.unlink(missing_ok=True)
path.write_text(
f"# This file has been created by setup.py.\n{version = }\n"
)
def search_mumps():
"""Return the configuration for MUMPS if it is available in a known way.
This is known to work with the MUMPS provided by the Debian package
libmumps-scotch-dev."""
libs = ['zmumps_scotch', 'mumps_common_scotch', 'pord', 'mpiseq_scotch',
'gfortran']
cmd = ['gcc']
cmd.extend(['-l' + lib for lib in libs])
cmd.extend(['-o/dev/null', '-xc', '-'])
try:
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError:
pass
else:
p.communicate(input='int main() {}\n')
if p.wait() == 0:
return {'libraries': libs}
return {}
def extensions():
"""Return a list of tuples (args, kwrds) to be passed to Extension."""
global build_summary, config_file_present
build_summary = []
#### Add components of Kwant without external compile-time dependencies.
result = [
(['kwant._system', ['kwant/_system.pyx']],
{'include_dirs': ['kwant/graph']}),
(['kwant.graph.core', ['kwant/graph/core.pyx']],
{'depends': ['kwant/graph/core.pxd', 'kwant/graph/defs.h',
'kwant/graph/defs.pxd']}),
(['kwant.graph.utils', ['kwant/graph/utils.pyx']],
{'depends': ['kwant/graph/defs.h', 'kwant/graph/defs.pxd',
'kwant/graph/core.pxd']}),
(['kwant.graph.slicer', ['kwant/graph/slicer.pyx',
'kwant/graph/c_slicer/partitioner.cc',
'kwant/graph/c_slicer/slicer.cc']],
{'depends': ['kwant/graph/defs.h', 'kwant/graph/defs.pxd',
'kwant/graph/core.pxd',
'kwant/graph/c_slicer.pxd',
'kwant/graph/c_slicer/bucket_list.h',
'kwant/graph/c_slicer/graphwrap.h',
'kwant/graph/c_slicer/partitioner.h',
'kwant/graph/c_slicer/slicer.h']})]
#### Add components of Kwant with external compile-time dependencies.
config = ConfigParser.ConfigParser()
try:
with open(CONFIG_FILE) as f:
config.readfp(f)
except IOError:
config_file_present = False
else:
config_file_present = True
kwrds_by_section = {}
for section in config.sections():
kwrds_by_section[section] = kwrds = {}
for name, value in config.items(section):
kwrds[name] = value.split()
# Setup LAPACK.
lapack = kwrds_by_section.get('lapack')
if lapack:
build_summary.append('User-configured LAPACK and BLAS')
else:
lapack = {'libraries': ['lapack', 'blas']}
build_summary.append('Default LAPACK and BLAS')
kwrds = lapack.copy()
kwrds.setdefault('depends', []).extend(
[CONFIG_FILE, 'kwant/linalg/f_lapack.pxd'])
result.append((['kwant.linalg.lapack', ['kwant/linalg/lapack.pyx']],
kwrds))
# Setup MUMPS.
kwrds = kwrds_by_section.get('mumps')
if kwrds:
libmumps-scotch-dev and the MUMPS binaries in the conda-forge channel."""
lib_sets = [
# Debian
['zmumps_scotch', 'mumps_common_scotch', 'mpiseq_scotch',
'pord', 'gfortran'],
# Conda (via conda-forge).
['zmumps_seq', 'mumps_common_seq'],
]
return next(
(libs for libs in lib_sets if all(find_library(lib) for lib in libs)),
[]
)
def configure_special_extensions(exts, build_summary):
#### Special config for MUMPS.
mumps = exts['kwant.linalg._mumps']
if 'libraries' in mumps:
build_summary.append('User-configured MUMPS')
else:
kwrds = search_mumps()
if kwrds:
mumps_libs = search_mumps()
if mumps_libs:
mumps['libraries'] = mumps_libs
build_summary.append('Auto-configured MUMPS')
if kwrds:
for name, value in lapack.iteritems():
kwrds.setdefault(name, []).extend(value)
kwrds.setdefault('depends', []).extend(
[CONFIG_FILE, 'kwant/linalg/cmumps.pxd'])
result.append((['kwant.linalg._mumps', ['kwant/linalg/_mumps.pyx']],
kwrds))
else:
build_summary.append('No MUMPS support')
else:
mumps = None
del exts['kwant.linalg._mumps']
build_summary.append('No MUMPS support')
build_summary = '\n'.join(build_summary)
return result
return exts
def maybe_cythonize(exts):
"""Prepare a list of `Extension` instances, ready for `setup()`.
The argument `exts` must be a mapping of names to kwargs to be passed
on to `Extension`.
def complain_cython_unavailable():
assert not cythonize or cython_version < REQUIRED_CYTHON_VERSION
If Cython is to be run, create the extensions and calls `cythonize()` on
them. If Cython is not to be run, replace .pyx file with .c or .cpp,
check timestamps, and create the extensions.
"""
if cythonize:
msg = ("Install Cython {0} or newer so it can be made\n"
"or use a source distribution of Kwant.")
ver = '.'.join(str(e) for e in REQUIRED_CYTHON_VERSION)
print(msg.format(ver), file=sys.stderr)
else:
print("Run setup.py without {}.".format(NO_CYTHON_OPTION),
file=sys.stderr)
return cythonize([Extension(name, **kwargs)
for name, kwargs in exts.items()],
language_level=3,
compiler_directives={'linetrace': True})
# Cython is not going to be run: replace pyx extension by that of
# the shipped translated file.
def ext_modules(extensions):
"""Prepare the ext_modules argument for distutils' setup."""
result = []
problematic_files = []
for args, kwrds in extensions:
if not cythonize or cython_version < REQUIRED_CYTHON_VERSION:
# Cython is not going to be run: replace pyx extension by that of
# the shipped translated file.
if 'language' in kwrds:
if kwrds['language'] == 'c':
ext = '.c'
elif kwrds['language'] == 'c++':
ext = '.cpp'
else:
print('Unknown language', file=sys.stderr)
exit(1)
else:
ext = '.c'
pyx_files = []
cythonized_files = []
sources = []
for f in args[1]:
if f[-4:] == '.pyx':
pyx_files.append(f)
f = f[:-4] + ext
cythonized_files.append(f)
sources.append(f)
args[1] = sources
# Complain if cythonized files are older than Cython source files.
try:
cythonized_oldest = min(os.stat(f).st_mtime
for f in cythonized_files)
except OSError:
print("error: Cython-generated file {} is missing.".format(f),
file=sys.stderr)
complain_cython_unavailable()
exit(1)
for f in pyx_files + kwrds.get('depends', []):
if f == CONFIG_FILE:
# The config file is only a dependency for the compilation
# of the cythonized file, not for the cythonization.
continue
if os.stat(f).st_mtime > cythonized_oldest:
problematic_files.append(f)
result.append(Extension(*args, **kwrds))
for name, kwargs in exts.items():
language = kwargs.get('language')
if language is None:
ext = '.c'
elif language == 'c':
ext = '.c'
elif language == 'c++':
ext = '.cpp'
else:
sys.exit(f"Unknown language: {language}")
pyx_files = []
cythonized_files = []
sources = []
for f in kwargs['sources']:
if f.endswith('.pyx'):
pyx_files.append(f)
f = f.rstrip('.pyx') + ext
cythonized_files.append(f)
sources.append(f)
kwargs['sources'] = sources
# Complain if cythonized files are older than Cython source files.
try:
cythonized_oldest = min(os.stat(f).st_mtime
for f in cythonized_files)
except OSError:
sys.exit("\n".join([
banner(" Error "),
f"Cython-generated file {f} is missing.",
"",
cython_help,
banner(),
]))
for f in pyx_files + kwargs.get('depends', []):
if f == config_file:
# The config file is only a dependency for the compilation
# of the cythonized file, not for the cythonization.
continue
if os.stat(f).st_mtime > cythonized_oldest:
problematic_files.append(f)
result.append(Extension(name, **kwargs))
if problematic_files:
problematic_files = ", ".join(problematic_files)
msg = ("Some Cython source files are newer than files that should have\n"
" been derived from them, but {}.\n"
"\n"
"Affected files: {}")
if cythonize:
if not cython_version:
reason = "Cython is not installed"
else:
reason = "the installed Cython is too old"
print(banner(" Error "), msg.format(reason, problematic_files),
banner(), sep="\n", file=sys.stderr)
print()
complain_cython_unavailable()
exit(1)
else:
reason = "the option --no-cython has been given"
print(banner(" Warning "), msg.format(reason, problematic_files),
banner(), sep='\n', file=sys.stderr)
msg = ("Some Cython source files are newer than files that have "
"been derived from them:\n{}")
msg = msg.format(", ".join(problematic_files))
# Cython should be run but won't. Signal an error if this is because
# Cython *cannot* be run, warn otherwise.
error = cythonize is None
if cythonize is False:
dontworry = ('(Do not worry about this if you are building Kwant '
'from unmodified sources,\n'
'e.g. with "pip install".)\n\n')
msg = dontworry + msg
print(banner(" Error " if error else " Caution "), msg, "",
cython_help, banner(), sep="\n", file=sys.stderr)
if error:
sys.exit(1)
return result
def maybe_add_numpy_include(exts):
# Add NumPy header path to include_dirs of all the extensions.
try:
import numpy
except ImportError:
print(banner(' Caution '), 'NumPy header directory cannot be determined'
' ("import numpy" failed).', banner(), sep='\n', file=sys.stderr)
else:
numpy_include = numpy.get_include()
for ext in exts.values():
ext.setdefault('include_dirs', []).append(numpy_include)
return exts
def main():
setup(name='kwant',
version=version,
author='C. W. Groth (CEA), M. Wimmer, '
'A. R. Akhmerov, X. Waintal (CEA), and others',
author_email='authors@kwant-project.org',
description="Package for numerical quantum transport calculations.",
long_description=long_description(),
platforms=["Unix", "Linux", "Mac OS-X", "Windows"],
url="http://kwant-project.org/",
license="BSD",
packages=packages(),
test_suite = 'nose.collector',
cmdclass={'build': kwant_build,
'sdist': kwant_sdist,
'build_ext': kwant_build_ext,
'build_tut': kwant_build_tut},
ext_modules=ext_modules(extensions()),
include_dirs=include_dirs,
setup_requires=['numpy > 1.6.1', 'nose >= 1.0'],
install_requires=['numpy > 1.6.1', 'scipy >= 0.9', 'tinyarray'],
extras_require={'plotting': 'matplotlib >= 1.2'})
check_versions()
exts = dict([
('kwant._system',
dict(sources=['kwant/_system.pyx'],
include_dirs=['kwant/graph'])),
('kwant.operator',
dict(sources=['kwant/operator.pyx'],
include_dirs=['kwant/graph'])),
('kwant.graph.core',
dict(sources=['kwant/graph/core.pyx'],
depends=['kwant/graph/core.pxd', 'kwant/graph/defs.h',
'kwant/graph/defs.pxd'])),
('kwant.graph.dijkstra',
dict(sources=['kwant/graph/dijkstra.pyx'])),
('kwant.linalg.lapack',
dict(sources=['kwant/linalg/lapack.pyx'])),
('kwant.linalg._mumps',
dict(sources=['kwant/linalg/_mumps.pyx'],
depends=['kwant/linalg/cmumps.pxd']))])
# Stop relying on numpy deprecated API.
for ext in exts.values():
ext['define_macros'] = [
('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')
]
aliases = [('mumps', 'kwant.linalg._mumps')]
init_cython()
global build_summary
build_summary = []
exts = configure_extensions(exts, aliases, build_summary)
exts = configure_special_extensions(exts, build_summary)
exts = maybe_add_numpy_include(exts)
exts = maybe_cythonize(exts)
packages = find_packages('.')
setup(version=version,
packages=packages,
package_data={p: ['*.pxd', '*.h'] for p in packages},
cmdclass={'build': build,
'sdist': sdist,
'build_ext': build_ext},
ext_modules=exts,
)
if __name__ == '__main__':
main()